You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

269 lines
6.0 KiB

  1. // Copyright 2012 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build darwin dragonfly freebsd linux netbsd openbsd plan9
  5. package test
  6. // functional test harness for unix.
  7. import (
  8. "bytes"
  9. "fmt"
  10. "io/ioutil"
  11. "log"
  12. "net"
  13. "os"
  14. "os/exec"
  15. "os/user"
  16. "path/filepath"
  17. "testing"
  18. "text/template"
  19. "golang.org/x/crypto/ssh"
  20. "golang.org/x/crypto/ssh/testdata"
  21. )
  22. const sshd_config = `
  23. Protocol 2
  24. HostKey {{.Dir}}/id_rsa
  25. HostKey {{.Dir}}/id_dsa
  26. HostKey {{.Dir}}/id_ecdsa
  27. Pidfile {{.Dir}}/sshd.pid
  28. #UsePrivilegeSeparation no
  29. KeyRegenerationInterval 3600
  30. ServerKeyBits 768
  31. SyslogFacility AUTH
  32. LogLevel DEBUG2
  33. LoginGraceTime 120
  34. PermitRootLogin no
  35. StrictModes no
  36. RSAAuthentication yes
  37. PubkeyAuthentication yes
  38. AuthorizedKeysFile {{.Dir}}/authorized_keys
  39. TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub
  40. IgnoreRhosts yes
  41. RhostsRSAAuthentication no
  42. HostbasedAuthentication no
  43. PubkeyAcceptedKeyTypes=*
  44. `
  45. var configTmpl = template.Must(template.New("").Parse(sshd_config))
  46. type server struct {
  47. t *testing.T
  48. cleanup func() // executed during Shutdown
  49. configfile string
  50. cmd *exec.Cmd
  51. output bytes.Buffer // holds stderr from sshd process
  52. // Client half of the network connection.
  53. clientConn net.Conn
  54. }
  55. func username() string {
  56. var username string
  57. if user, err := user.Current(); err == nil {
  58. username = user.Username
  59. } else {
  60. // user.Current() currently requires cgo. If an error is
  61. // returned attempt to get the username from the environment.
  62. log.Printf("user.Current: %v; falling back on $USER", err)
  63. username = os.Getenv("USER")
  64. }
  65. if username == "" {
  66. panic("Unable to get username")
  67. }
  68. return username
  69. }
  70. type storedHostKey struct {
  71. // keys map from an algorithm string to binary key data.
  72. keys map[string][]byte
  73. // checkCount counts the Check calls. Used for testing
  74. // rekeying.
  75. checkCount int
  76. }
  77. func (k *storedHostKey) Add(key ssh.PublicKey) {
  78. if k.keys == nil {
  79. k.keys = map[string][]byte{}
  80. }
  81. k.keys[key.Type()] = key.Marshal()
  82. }
  83. func (k *storedHostKey) Check(addr string, remote net.Addr, key ssh.PublicKey) error {
  84. k.checkCount++
  85. algo := key.Type()
  86. if k.keys == nil || bytes.Compare(key.Marshal(), k.keys[algo]) != 0 {
  87. return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo])
  88. }
  89. return nil
  90. }
  91. func hostKeyDB() *storedHostKey {
  92. keyChecker := &storedHostKey{}
  93. keyChecker.Add(testPublicKeys["ecdsa"])
  94. keyChecker.Add(testPublicKeys["rsa"])
  95. keyChecker.Add(testPublicKeys["dsa"])
  96. return keyChecker
  97. }
  98. func clientConfig() *ssh.ClientConfig {
  99. config := &ssh.ClientConfig{
  100. User: username(),
  101. Auth: []ssh.AuthMethod{
  102. ssh.PublicKeys(testSigners["user"]),
  103. },
  104. HostKeyCallback: hostKeyDB().Check,
  105. }
  106. return config
  107. }
  108. // unixConnection creates two halves of a connected net.UnixConn. It
  109. // is used for connecting the Go SSH client with sshd without opening
  110. // ports.
  111. func unixConnection() (*net.UnixConn, *net.UnixConn, error) {
  112. dir, err := ioutil.TempDir("", "unixConnection")
  113. if err != nil {
  114. return nil, nil, err
  115. }
  116. defer os.Remove(dir)
  117. addr := filepath.Join(dir, "ssh")
  118. listener, err := net.Listen("unix", addr)
  119. if err != nil {
  120. return nil, nil, err
  121. }
  122. defer listener.Close()
  123. c1, err := net.Dial("unix", addr)
  124. if err != nil {
  125. return nil, nil, err
  126. }
  127. c2, err := listener.Accept()
  128. if err != nil {
  129. c1.Close()
  130. return nil, nil, err
  131. }
  132. return c1.(*net.UnixConn), c2.(*net.UnixConn), nil
  133. }
  134. func (s *server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) {
  135. sshd, err := exec.LookPath("sshd")
  136. if err != nil {
  137. s.t.Skipf("skipping test: %v", err)
  138. }
  139. c1, c2, err := unixConnection()
  140. if err != nil {
  141. s.t.Fatalf("unixConnection: %v", err)
  142. }
  143. s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e")
  144. f, err := c2.File()
  145. if err != nil {
  146. s.t.Fatalf("UnixConn.File: %v", err)
  147. }
  148. defer f.Close()
  149. s.cmd.Stdin = f
  150. s.cmd.Stdout = f
  151. s.cmd.Stderr = &s.output
  152. if err := s.cmd.Start(); err != nil {
  153. s.t.Fail()
  154. s.Shutdown()
  155. s.t.Fatalf("s.cmd.Start: %v", err)
  156. }
  157. s.clientConn = c1
  158. conn, chans, reqs, err := ssh.NewClientConn(c1, "", config)
  159. if err != nil {
  160. return nil, err
  161. }
  162. return ssh.NewClient(conn, chans, reqs), nil
  163. }
  164. func (s *server) Dial(config *ssh.ClientConfig) *ssh.Client {
  165. conn, err := s.TryDial(config)
  166. if err != nil {
  167. s.t.Fail()
  168. s.Shutdown()
  169. s.t.Fatalf("ssh.Client: %v", err)
  170. }
  171. return conn
  172. }
  173. func (s *server) Shutdown() {
  174. if s.cmd != nil && s.cmd.Process != nil {
  175. // Don't check for errors; if it fails it's most
  176. // likely "os: process already finished", and we don't
  177. // care about that. Use os.Interrupt, so child
  178. // processes are killed too.
  179. s.cmd.Process.Signal(os.Interrupt)
  180. s.cmd.Wait()
  181. }
  182. if s.t.Failed() {
  183. // log any output from sshd process
  184. s.t.Logf("sshd: %s", s.output.String())
  185. }
  186. s.cleanup()
  187. }
  188. func writeFile(path string, contents []byte) {
  189. f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
  190. if err != nil {
  191. panic(err)
  192. }
  193. defer f.Close()
  194. if _, err := f.Write(contents); err != nil {
  195. panic(err)
  196. }
  197. }
  198. // newServer returns a new mock ssh server.
  199. func newServer(t *testing.T) *server {
  200. if testing.Short() {
  201. t.Skip("skipping test due to -short")
  202. }
  203. dir, err := ioutil.TempDir("", "sshtest")
  204. if err != nil {
  205. t.Fatal(err)
  206. }
  207. f, err := os.Create(filepath.Join(dir, "sshd_config"))
  208. if err != nil {
  209. t.Fatal(err)
  210. }
  211. err = configTmpl.Execute(f, map[string]string{
  212. "Dir": dir,
  213. })
  214. if err != nil {
  215. t.Fatal(err)
  216. }
  217. f.Close()
  218. for k, v := range testdata.PEMBytes {
  219. filename := "id_" + k
  220. writeFile(filepath.Join(dir, filename), v)
  221. writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k]))
  222. }
  223. var authkeys bytes.Buffer
  224. for k, _ := range testdata.PEMBytes {
  225. authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k]))
  226. }
  227. writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes())
  228. return &server{
  229. t: t,
  230. configfile: f.Name(),
  231. cleanup: func() {
  232. if err := os.RemoveAll(dir); err != nil {
  233. t.Error(err)
  234. }
  235. },
  236. }
  237. }