ssh.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux || (darwin && !ios) || freebsd || openbsd
  4. package ipnlocal
  5. import (
  6. "bytes"
  7. "crypto/ecdsa"
  8. "crypto/ed25519"
  9. "crypto/elliptic"
  10. "crypto/rand"
  11. "crypto/rsa"
  12. "crypto/x509"
  13. "encoding/pem"
  14. "errors"
  15. "fmt"
  16. "os"
  17. "os/exec"
  18. "path/filepath"
  19. "runtime"
  20. "slices"
  21. "strings"
  22. "sync"
  23. "github.com/tailscale/golang-x-crypto/ssh"
  24. "go4.org/mem"
  25. "tailscale.com/tailcfg"
  26. "tailscale.com/util/lineread"
  27. "tailscale.com/util/mak"
  28. )
  29. // keyTypes are the SSH key types that we either try to read from the
  30. // system's OpenSSH keys or try to generate for ourselves when not
  31. // running as root.
  32. var keyTypes = []string{"rsa", "ecdsa", "ed25519"}
  33. // getSSHUsernames discovers and returns the list of usernames that are
  34. // potential Tailscale SSH user targets.
  35. //
  36. // Invariant: must not be called with b.mu held.
  37. func (b *LocalBackend) getSSHUsernames(req *tailcfg.C2NSSHUsernamesRequest) (*tailcfg.C2NSSHUsernamesResponse, error) {
  38. res := new(tailcfg.C2NSSHUsernamesResponse)
  39. if !b.tailscaleSSHEnabled() {
  40. return res, nil
  41. }
  42. max := 10
  43. if req != nil && req.Max != 0 {
  44. max = req.Max
  45. }
  46. add := func(u string) {
  47. if req != nil && req.Exclude[u] {
  48. return
  49. }
  50. switch u {
  51. case "nobody", "daemon", "sync":
  52. return
  53. }
  54. if slices.Contains(res.Usernames, u) {
  55. return
  56. }
  57. if len(res.Usernames) > max {
  58. // Enough for a hint.
  59. return
  60. }
  61. res.Usernames = append(res.Usernames, u)
  62. }
  63. if opUser := b.operatorUserName(); opUser != "" {
  64. add(opUser)
  65. }
  66. // Check popular usernames and see if they exist with a real shell.
  67. switch runtime.GOOS {
  68. case "darwin":
  69. out, err := exec.Command("dscl", ".", "list", "/Users").Output()
  70. if err != nil {
  71. return nil, err
  72. }
  73. lineread.Reader(bytes.NewReader(out), func(line []byte) error {
  74. line = bytes.TrimSpace(line)
  75. if len(line) == 0 || line[0] == '_' {
  76. return nil
  77. }
  78. add(string(line))
  79. return nil
  80. })
  81. default:
  82. lineread.File("/etc/passwd", func(line []byte) error {
  83. line = bytes.TrimSpace(line)
  84. if len(line) == 0 || line[0] == '#' || line[0] == '_' {
  85. return nil
  86. }
  87. if mem.HasSuffix(mem.B(line), mem.S("/nologin")) ||
  88. mem.HasSuffix(mem.B(line), mem.S("/false")) {
  89. return nil
  90. }
  91. colon := bytes.IndexByte(line, ':')
  92. if colon != -1 {
  93. add(string(line[:colon]))
  94. }
  95. return nil
  96. })
  97. }
  98. return res, nil
  99. }
  100. func (b *LocalBackend) GetSSH_HostKeys() (keys []ssh.Signer, err error) {
  101. var existing map[string]ssh.Signer
  102. if os.Geteuid() == 0 {
  103. existing = b.getSystemSSH_HostKeys()
  104. }
  105. return b.getTailscaleSSH_HostKeys(existing)
  106. }
  107. // getTailscaleSSH_HostKeys returns the three (rsa, ecdsa, ed25519) SSH host
  108. // keys, reusing the provided ones in existing if present in the map.
  109. func (b *LocalBackend) getTailscaleSSH_HostKeys(existing map[string]ssh.Signer) (keys []ssh.Signer, err error) {
  110. var keyDir string // lazily initialized $TAILSCALE_VAR/ssh dir.
  111. for _, typ := range keyTypes {
  112. if s, ok := existing[typ]; ok {
  113. keys = append(keys, s)
  114. continue
  115. }
  116. if keyDir == "" {
  117. root := b.TailscaleVarRoot()
  118. if root == "" {
  119. return nil, errors.New("no var root for ssh keys")
  120. }
  121. keyDir = filepath.Join(root, "ssh")
  122. if err := os.MkdirAll(keyDir, 0700); err != nil {
  123. return nil, err
  124. }
  125. }
  126. hostKey, err := b.hostKeyFileOrCreate(keyDir, typ)
  127. if err != nil {
  128. return nil, fmt.Errorf("error creating SSH host key type %q in %q: %w", typ, keyDir, err)
  129. }
  130. signer, err := ssh.ParsePrivateKey(hostKey)
  131. if err != nil {
  132. return nil, fmt.Errorf("error parsing SSH host key type %q from %q: %w", typ, keyDir, err)
  133. }
  134. keys = append(keys, signer)
  135. }
  136. return keys, nil
  137. }
  138. var keyGenMu sync.Mutex
  139. func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
  140. keyGenMu.Lock()
  141. defer keyGenMu.Unlock()
  142. path := filepath.Join(keyDir, "ssh_host_"+typ+"_key")
  143. v, err := os.ReadFile(path)
  144. if err == nil {
  145. return v, nil
  146. }
  147. if !os.IsNotExist(err) {
  148. return nil, err
  149. }
  150. var priv any
  151. switch typ {
  152. default:
  153. return nil, fmt.Errorf("unsupported key type %q", typ)
  154. case "ed25519":
  155. _, priv, err = ed25519.GenerateKey(rand.Reader)
  156. case "ecdsa":
  157. // curve is arbitrary. We pick whatever will at
  158. // least pacify clients as the actual encryption
  159. // doesn't matter: it's all over WireGuard anyway.
  160. curve := elliptic.P256()
  161. priv, err = ecdsa.GenerateKey(curve, rand.Reader)
  162. case "rsa":
  163. // keySize is arbitrary. We pick whatever will at
  164. // least pacify clients as the actual encryption
  165. // doesn't matter: it's all over WireGuard anyway.
  166. const keySize = 2048
  167. priv, err = rsa.GenerateKey(rand.Reader, keySize)
  168. }
  169. if err != nil {
  170. return nil, err
  171. }
  172. mk, err := x509.MarshalPKCS8PrivateKey(priv)
  173. if err != nil {
  174. return nil, err
  175. }
  176. pemGen := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: mk})
  177. err = os.WriteFile(path, pemGen, 0700)
  178. return pemGen, err
  179. }
  180. func (b *LocalBackend) getSystemSSH_HostKeys() (ret map[string]ssh.Signer) {
  181. for _, typ := range keyTypes {
  182. filename := "/etc/ssh/ssh_host_" + typ + "_key"
  183. hostKey, err := os.ReadFile(filename)
  184. if err != nil || len(bytes.TrimSpace(hostKey)) == 0 {
  185. continue
  186. }
  187. signer, err := ssh.ParsePrivateKey(hostKey)
  188. if err != nil {
  189. b.logf("warning: error reading host key %s: %v (generating one instead)", filename, err)
  190. continue
  191. }
  192. mak.Set(&ret, typ, signer)
  193. }
  194. return ret
  195. }
  196. func (b *LocalBackend) getSSHHostKeyPublicStrings() (ret []string) {
  197. signers, _ := b.GetSSH_HostKeys()
  198. for _, signer := range signers {
  199. ret = append(ret, strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey()))))
  200. }
  201. return ret
  202. }