ssh.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build ((linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9) && !ts_omit_ssh
  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. "go4.org/mem"
  24. "golang.org/x/crypto/ssh"
  25. "tailscale.com/tailcfg"
  26. "tailscale.com/util/lineiter"
  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. for line := range lineiter.Bytes(out) {
  74. line = bytes.TrimSpace(line)
  75. if len(line) == 0 || line[0] == '_' {
  76. continue
  77. }
  78. add(string(line))
  79. }
  80. default:
  81. for lr := range lineiter.File("/etc/passwd") {
  82. line, err := lr.Value()
  83. if err != nil {
  84. break
  85. }
  86. line = bytes.TrimSpace(line)
  87. if len(line) == 0 || line[0] == '#' || line[0] == '_' {
  88. continue
  89. }
  90. if mem.HasSuffix(mem.B(line), mem.S("/nologin")) ||
  91. mem.HasSuffix(mem.B(line), mem.S("/false")) {
  92. continue
  93. }
  94. colon := bytes.IndexByte(line, ':')
  95. if colon != -1 {
  96. add(string(line[:colon]))
  97. }
  98. }
  99. }
  100. return res, nil
  101. }
  102. func (b *LocalBackend) GetSSH_HostKeys() (keys []ssh.Signer, err error) {
  103. var existing map[string]ssh.Signer
  104. if os.Geteuid() == 0 {
  105. existing = b.getSystemSSH_HostKeys()
  106. }
  107. return b.getTailscaleSSH_HostKeys(existing)
  108. }
  109. // getTailscaleSSH_HostKeys returns the three (rsa, ecdsa, ed25519) SSH host
  110. // keys, reusing the provided ones in existing if present in the map.
  111. func (b *LocalBackend) getTailscaleSSH_HostKeys(existing map[string]ssh.Signer) (keys []ssh.Signer, err error) {
  112. var keyDir string // lazily initialized $TAILSCALE_VAR/ssh dir.
  113. for _, typ := range keyTypes {
  114. if s, ok := existing[typ]; ok {
  115. keys = append(keys, s)
  116. continue
  117. }
  118. if keyDir == "" {
  119. root := b.TailscaleVarRoot()
  120. if root == "" {
  121. return nil, errors.New("no var root for ssh keys")
  122. }
  123. keyDir = filepath.Join(root, "ssh")
  124. if err := os.MkdirAll(keyDir, 0700); err != nil {
  125. return nil, err
  126. }
  127. }
  128. hostKey, err := b.hostKeyFileOrCreate(keyDir, typ)
  129. if err != nil {
  130. return nil, fmt.Errorf("error creating SSH host key type %q in %q: %w", typ, keyDir, err)
  131. }
  132. signer, err := ssh.ParsePrivateKey(hostKey)
  133. if err != nil {
  134. return nil, fmt.Errorf("error parsing SSH host key type %q from %q: %w", typ, keyDir, err)
  135. }
  136. keys = append(keys, signer)
  137. }
  138. return keys, nil
  139. }
  140. var keyGenMu sync.Mutex
  141. func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
  142. keyGenMu.Lock()
  143. defer keyGenMu.Unlock()
  144. path := filepath.Join(keyDir, "ssh_host_"+typ+"_key")
  145. v, err := os.ReadFile(path)
  146. if err == nil {
  147. return v, nil
  148. }
  149. if !os.IsNotExist(err) {
  150. return nil, err
  151. }
  152. var priv any
  153. switch typ {
  154. default:
  155. return nil, fmt.Errorf("unsupported key type %q", typ)
  156. case "ed25519":
  157. _, priv, err = ed25519.GenerateKey(rand.Reader)
  158. case "ecdsa":
  159. // curve is arbitrary. We pick whatever will at
  160. // least pacify clients as the actual encryption
  161. // doesn't matter: it's all over WireGuard anyway.
  162. curve := elliptic.P256()
  163. priv, err = ecdsa.GenerateKey(curve, rand.Reader)
  164. case "rsa":
  165. // keySize is arbitrary. We pick whatever will at
  166. // least pacify clients as the actual encryption
  167. // doesn't matter: it's all over WireGuard anyway.
  168. const keySize = 2048
  169. priv, err = rsa.GenerateKey(rand.Reader, keySize)
  170. }
  171. if err != nil {
  172. return nil, err
  173. }
  174. mk, err := x509.MarshalPKCS8PrivateKey(priv)
  175. if err != nil {
  176. return nil, err
  177. }
  178. pemGen := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: mk})
  179. err = os.WriteFile(path, pemGen, 0700)
  180. return pemGen, err
  181. }
  182. func (b *LocalBackend) getSystemSSH_HostKeys() (ret map[string]ssh.Signer) {
  183. for _, typ := range keyTypes {
  184. filename := "/etc/ssh/ssh_host_" + typ + "_key"
  185. hostKey, err := os.ReadFile(filename)
  186. if err != nil || len(bytes.TrimSpace(hostKey)) == 0 {
  187. continue
  188. }
  189. signer, err := ssh.ParsePrivateKey(hostKey)
  190. if err != nil {
  191. b.logf("warning: error reading host key %s: %v (generating one instead)", filename, err)
  192. continue
  193. }
  194. mak.Set(&ret, typ, signer)
  195. }
  196. return ret
  197. }
  198. func (b *LocalBackend) getSSHHostKeyPublicStrings() ([]string, error) {
  199. signers, err := b.GetSSH_HostKeys()
  200. if err != nil {
  201. return nil, err
  202. }
  203. var keyStrings []string
  204. for _, signer := range signers {
  205. keyStrings = append(keyStrings, strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey()))))
  206. }
  207. return keyStrings, nil
  208. }
  209. // tailscaleSSHEnabled reports whether Tailscale SSH is currently enabled based
  210. // on prefs. It returns false if there are no prefs set.
  211. func (b *LocalBackend) tailscaleSSHEnabled() bool {
  212. b.mu.Lock()
  213. defer b.mu.Unlock()
  214. p := b.pm.CurrentPrefs()
  215. return p.Valid() && p.RunSSH()
  216. }