tailssh.go 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455
  1. // Copyright (c) 2021 Tailscale Inc & 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. //go:build linux || (darwin && !ios)
  5. // +build linux darwin,!ios
  6. // Package tailssh is an SSH server integrated into Tailscale.
  7. package tailssh
  8. import (
  9. "bytes"
  10. "context"
  11. "crypto/rand"
  12. "encoding/base64"
  13. "encoding/json"
  14. "errors"
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "net"
  19. "net/http"
  20. "net/netip"
  21. "net/url"
  22. "os"
  23. "os/exec"
  24. "os/user"
  25. "path/filepath"
  26. "runtime"
  27. "strconv"
  28. "strings"
  29. "sync"
  30. "sync/atomic"
  31. "time"
  32. gossh "github.com/tailscale/golang-x-crypto/ssh"
  33. "tailscale.com/envknob"
  34. "tailscale.com/ipn/ipnlocal"
  35. "tailscale.com/logtail/backoff"
  36. "tailscale.com/net/tsaddr"
  37. "tailscale.com/tailcfg"
  38. "tailscale.com/tempfork/gliderlabs/ssh"
  39. "tailscale.com/types/logger"
  40. "tailscale.com/util/clientmetric"
  41. "tailscale.com/util/mak"
  42. )
  43. var (
  44. debugPolicyFile = envknob.SSHPolicyFile()
  45. debugIgnoreTailnetSSHPolicy = envknob.SSHIgnoreTailnetPolicy()
  46. sshVerboseLogging = envknob.Bool("TS_DEBUG_SSH_VLOG")
  47. )
  48. type server struct {
  49. lb *ipnlocal.LocalBackend
  50. logf logger.Logf
  51. tailscaledPath string
  52. pubKeyHTTPClient *http.Client // or nil for http.DefaultClient
  53. timeNow func() time.Time // or nil for time.Now
  54. sessionWaitGroup sync.WaitGroup
  55. // mu protects the following
  56. mu sync.Mutex
  57. activeConns map[*conn]bool // set; value is always true
  58. fetchPublicKeysCache map[string]pubKeyCacheEntry // by https URL
  59. shutdownCalled bool
  60. }
  61. func (srv *server) now() time.Time {
  62. if srv != nil && srv.timeNow != nil {
  63. return srv.timeNow()
  64. }
  65. return time.Now()
  66. }
  67. func init() {
  68. ipnlocal.RegisterNewSSHServer(func(logf logger.Logf, lb *ipnlocal.LocalBackend) (ipnlocal.SSHServer, error) {
  69. tsd, err := os.Executable()
  70. if err != nil {
  71. return nil, err
  72. }
  73. srv := &server{
  74. lb: lb,
  75. logf: logf,
  76. tailscaledPath: tsd,
  77. }
  78. return srv, nil
  79. })
  80. }
  81. func (srv *server) trackActiveConn(c *conn, add bool) {
  82. srv.mu.Lock()
  83. defer srv.mu.Unlock()
  84. if add {
  85. mak.Set(&srv.activeConns, c, true)
  86. return
  87. }
  88. delete(srv.activeConns, c)
  89. }
  90. // HandleSSHConn handles a Tailscale SSH connection from c.
  91. // This is the entry point for all SSH connections.
  92. // When this returns, the connection is closed.
  93. func (srv *server) HandleSSHConn(nc net.Conn) error {
  94. metricIncomingConnections.Add(1)
  95. c, err := srv.newConn()
  96. if err != nil {
  97. return err
  98. }
  99. srv.trackActiveConn(c, true) // add
  100. defer srv.trackActiveConn(c, false) // remove
  101. c.HandleConn(nc)
  102. // Return nil to signal to netstack's interception that it doesn't need to
  103. // log. If ss.HandleConn had problems, it can log itself (ideally on an
  104. // sshSession.logf).
  105. return nil
  106. }
  107. // Shutdown terminates all active sessions.
  108. func (srv *server) Shutdown() {
  109. srv.mu.Lock()
  110. srv.shutdownCalled = true
  111. for c := range srv.activeConns {
  112. for _, s := range c.sessions {
  113. s.ctx.CloseWithError(userVisibleError{
  114. fmt.Sprintf("Tailscale SSH is shutting down.\r\n"),
  115. context.Canceled,
  116. })
  117. }
  118. }
  119. srv.mu.Unlock()
  120. srv.sessionWaitGroup.Wait()
  121. }
  122. // OnPolicyChange terminates any active sessions that no longer match
  123. // the SSH access policy.
  124. func (srv *server) OnPolicyChange() {
  125. srv.mu.Lock()
  126. defer srv.mu.Unlock()
  127. for c := range srv.activeConns {
  128. c.mu.Lock()
  129. ci := c.info
  130. c.mu.Unlock()
  131. if ci == nil {
  132. // c.info is nil when the connection hasn't been authenticated yet.
  133. // In that case, the connection will be terminated when it is.
  134. continue
  135. }
  136. go c.checkStillValid()
  137. }
  138. }
  139. // conn represents a single SSH connection and its associated
  140. // ssh.Server.
  141. type conn struct {
  142. *ssh.Server
  143. insecureSkipTailscaleAuth bool // used by tests.
  144. connID string // ID that's shared with control
  145. action0 *tailcfg.SSHAction // first matching action
  146. srv *server
  147. mu sync.Mutex // protects the following
  148. localUser *user.User // set by checkAuth
  149. userGroupIDs []string // set by checkAuth
  150. info *sshConnInfo // set by setInfo
  151. // idH is the RFC4253 sec8 hash H. It is used to identify the connection,
  152. // and is shared among all sessions. It should not be shared outside
  153. // process. It is confusingly referred to as SessionID by the gliderlabs/ssh
  154. // library.
  155. idH string
  156. pubKey gossh.PublicKey // set by authorizeSession
  157. finalAction *tailcfg.SSHAction // set by authorizeSession
  158. finalActionErr error // set by authorizeSession
  159. sessions []*sshSession
  160. }
  161. func (c *conn) logf(format string, args ...any) {
  162. format = fmt.Sprintf("%v: %v", c.connID, format)
  163. c.srv.logf(format, args...)
  164. }
  165. // PublicKeyHandler implements ssh.PublicKeyHandler is called by the the
  166. // ssh.Server when the client presents a public key.
  167. func (c *conn) PublicKeyHandler(ctx ssh.Context, pubKey ssh.PublicKey) error {
  168. c.mu.Lock()
  169. ci := c.info
  170. c.mu.Unlock()
  171. if ci == nil {
  172. return gossh.ErrDenied
  173. }
  174. if err := c.checkAuth(pubKey); err != nil {
  175. // TODO(maisem/bradfitz): surface the error here.
  176. c.logf("rejecting SSH public key %s: %v", bytes.TrimSpace(gossh.MarshalAuthorizedKey(pubKey)), err)
  177. return err
  178. }
  179. c.logf("accepting SSH public key %s", bytes.TrimSpace(gossh.MarshalAuthorizedKey(pubKey)))
  180. return nil
  181. }
  182. // errPubKeyRequired is returned by NoClientAuthCallback to make the client
  183. // resort to public-key auth; not user visible.
  184. var errPubKeyRequired = errors.New("ssh publickey required")
  185. // NoClientAuthCallback implements gossh.NoClientAuthCallback and is called by
  186. // the the ssh.Server when the client first connects with the "none"
  187. // authentication method.
  188. func (c *conn) NoClientAuthCallback(cm gossh.ConnMetadata) (*gossh.Permissions, error) {
  189. if c.insecureSkipTailscaleAuth {
  190. return nil, nil
  191. }
  192. if err := c.setInfo(cm); err != nil {
  193. c.logf("failed to get conninfo: %v", err)
  194. return nil, gossh.ErrDenied
  195. }
  196. return nil, c.checkAuth(nil /* no pub key */)
  197. }
  198. // checkAuth verifies that conn can proceed with the specified (optional)
  199. // pubKey. It returns nil if the matching policy action is Accept or
  200. // HoldAndDelegate. If pubKey is nil, there was no policy match but there is a
  201. // policy that might match a public key it returns errPubKeyRequired. Otherwise,
  202. // it returns gossh.ErrDenied possibly wrapped in gossh.WithBannerError.
  203. func (c *conn) checkAuth(pubKey ssh.PublicKey) error {
  204. a, localUser, err := c.evaluatePolicy(pubKey)
  205. if err != nil {
  206. if pubKey == nil && c.havePubKeyPolicy() {
  207. return errPubKeyRequired
  208. }
  209. return fmt.Errorf("%w: %v", gossh.ErrDenied, err)
  210. }
  211. c.action0 = a
  212. if a.Accept || a.HoldAndDelegate != "" {
  213. lu, err := user.Lookup(localUser)
  214. if err != nil {
  215. c.logf("failed to lookup %v: %v", localUser, err)
  216. return gossh.WithBannerError{
  217. Err: gossh.ErrDenied,
  218. Message: fmt.Sprintf("failed to lookup %v\r\n", localUser),
  219. }
  220. }
  221. gids, err := lu.GroupIds()
  222. if err != nil {
  223. return err
  224. }
  225. c.mu.Lock()
  226. defer c.mu.Unlock()
  227. c.userGroupIDs = gids
  228. c.localUser = lu
  229. return nil
  230. }
  231. if a.Reject {
  232. err := gossh.ErrDenied
  233. if a.Message != "" {
  234. err = gossh.WithBannerError{
  235. Err: err,
  236. Message: a.Message,
  237. }
  238. }
  239. return err
  240. }
  241. // Shouldn't get here, but:
  242. return gossh.ErrDenied
  243. }
  244. // ServerConfig implements ssh.ServerConfigCallback.
  245. func (c *conn) ServerConfig(ctx ssh.Context) *gossh.ServerConfig {
  246. return &gossh.ServerConfig{
  247. // OpenSSH presents this on failure as `Permission denied (tailscale).`
  248. ImplictAuthMethod: "tailscale",
  249. NoClientAuth: true, // required for the NoClientAuthCallback to run
  250. NoClientAuthCallback: c.NoClientAuthCallback,
  251. }
  252. }
  253. func (srv *server) newConn() (*conn, error) {
  254. srv.mu.Lock()
  255. if srv.shutdownCalled {
  256. srv.mu.Unlock()
  257. // Stop accepting new connections.
  258. // Connections in the auth phase are handled in handleConnPostSSHAuth.
  259. // Existing sessions are terminated by Shutdown.
  260. return nil, gossh.ErrDenied
  261. }
  262. srv.mu.Unlock()
  263. c := &conn{srv: srv}
  264. now := srv.now()
  265. c.connID = fmt.Sprintf("ssh-conn-%s-%02x", now.UTC().Format("20060102T150405"), randBytes(5))
  266. c.Server = &ssh.Server{
  267. Version: "Tailscale",
  268. Handler: c.handleSessionPostSSHAuth,
  269. RequestHandlers: map[string]ssh.RequestHandler{},
  270. SubsystemHandlers: map[string]ssh.SubsystemHandler{
  271. "sftp": c.handleSessionPostSSHAuth,
  272. },
  273. // Note: the direct-tcpip channel handler and LocalPortForwardingCallback
  274. // only adds support for forwarding ports from the local machine.
  275. // TODO(maisem/bradfitz): add remote port forwarding support.
  276. ChannelHandlers: map[string]ssh.ChannelHandler{
  277. "direct-tcpip": ssh.DirectTCPIPHandler,
  278. },
  279. LocalPortForwardingCallback: c.mayForwardLocalPortTo,
  280. PublicKeyHandler: c.PublicKeyHandler,
  281. ServerConfigCallback: c.ServerConfig,
  282. }
  283. ss := c.Server
  284. for k, v := range ssh.DefaultRequestHandlers {
  285. ss.RequestHandlers[k] = v
  286. }
  287. for k, v := range ssh.DefaultChannelHandlers {
  288. ss.ChannelHandlers[k] = v
  289. }
  290. for k, v := range ssh.DefaultSubsystemHandlers {
  291. ss.SubsystemHandlers[k] = v
  292. }
  293. keys, err := srv.lb.GetSSH_HostKeys()
  294. if err != nil {
  295. return nil, err
  296. }
  297. for _, signer := range keys {
  298. ss.AddHostKey(signer)
  299. }
  300. return c, nil
  301. }
  302. // mayForwardLocalPortTo reports whether the ctx should be allowed to port forward
  303. // to the specified host and port.
  304. // TODO(bradfitz/maisem): should we have more checks on host/port?
  305. func (c *conn) mayForwardLocalPortTo(ctx ssh.Context, destinationHost string, destinationPort uint32) bool {
  306. if c.finalAction != nil && c.finalAction.AllowLocalPortForwarding {
  307. metricLocalPortForward.Add(1)
  308. return true
  309. }
  310. return false
  311. }
  312. // havePubKeyPolicy reports whether any policy rule may provide access by means
  313. // of a ssh.PublicKey.
  314. func (c *conn) havePubKeyPolicy() bool {
  315. c.mu.Lock()
  316. ci := c.info
  317. c.mu.Unlock()
  318. if ci == nil {
  319. panic("havePubKeyPolicy called before setInfo")
  320. }
  321. // Is there any rule that looks like it'd require a public key for this
  322. // sshUser?
  323. pol, ok := c.sshPolicy()
  324. if !ok {
  325. return false
  326. }
  327. for _, r := range pol.Rules {
  328. if c.ruleExpired(r) {
  329. continue
  330. }
  331. if mapLocalUser(r.SSHUsers, ci.sshUser) == "" {
  332. continue
  333. }
  334. for _, p := range r.Principals {
  335. if len(p.PubKeys) > 0 && c.principalMatchesTailscaleIdentity(p) {
  336. return true
  337. }
  338. }
  339. }
  340. return false
  341. }
  342. // sshPolicy returns the SSHPolicy for current node.
  343. // If there is no SSHPolicy in the netmap, it returns a debugPolicy
  344. // if one is defined.
  345. func (c *conn) sshPolicy() (_ *tailcfg.SSHPolicy, ok bool) {
  346. lb := c.srv.lb
  347. if !lb.ShouldRunSSH() {
  348. return nil, false
  349. }
  350. nm := lb.NetMap()
  351. if nm == nil {
  352. return nil, false
  353. }
  354. if pol := nm.SSHPolicy; pol != nil && !debugIgnoreTailnetSSHPolicy {
  355. return pol, true
  356. }
  357. if debugPolicyFile != "" {
  358. c.logf("reading debug SSH policy file: %v", debugPolicyFile)
  359. f, err := os.ReadFile(debugPolicyFile)
  360. if err != nil {
  361. c.logf("error reading debug SSH policy file: %v", err)
  362. return nil, false
  363. }
  364. p := new(tailcfg.SSHPolicy)
  365. if err := json.Unmarshal(f, p); err != nil {
  366. c.logf("invalid JSON in %v: %v", debugPolicyFile, err)
  367. return nil, false
  368. }
  369. return p, true
  370. }
  371. return nil, false
  372. }
  373. func toIPPort(a net.Addr) (ipp netip.AddrPort) {
  374. ta, ok := a.(*net.TCPAddr)
  375. if !ok {
  376. return
  377. }
  378. tanetaddr, ok := netip.AddrFromSlice(ta.IP)
  379. if !ok {
  380. return
  381. }
  382. return netip.AddrPortFrom(tanetaddr.Unmap(), uint16(ta.Port))
  383. }
  384. // connInfo returns a populated sshConnInfo from the provided arguments,
  385. // validating only that they represent a known Tailscale identity.
  386. func (c *conn) setInfo(cm gossh.ConnMetadata) error {
  387. ci := &sshConnInfo{
  388. sshUser: cm.User(),
  389. src: toIPPort(cm.RemoteAddr()),
  390. dst: toIPPort(cm.LocalAddr()),
  391. }
  392. if !tsaddr.IsTailscaleIP(ci.dst.Addr()) {
  393. return fmt.Errorf("tailssh: rejecting non-Tailscale local address %v", ci.dst)
  394. }
  395. if !tsaddr.IsTailscaleIP(ci.src.Addr()) {
  396. return fmt.Errorf("tailssh: rejecting non-Tailscale remote address %v", ci.src)
  397. }
  398. node, uprof, ok := c.srv.lb.WhoIs(ci.src)
  399. if !ok {
  400. return fmt.Errorf("unknown Tailscale identity from src %v", ci.src)
  401. }
  402. c.mu.Lock()
  403. defer c.mu.Unlock()
  404. ci.node = node
  405. ci.uprof = &uprof
  406. c.info = ci
  407. c.logf("handling conn: %v", ci.String())
  408. return nil
  409. }
  410. // evaluatePolicy returns the SSHAction and localUser after evaluating
  411. // the SSHPolicy for this conn. The pubKey may be nil for "none" auth.
  412. func (c *conn) evaluatePolicy(pubKey gossh.PublicKey) (_ *tailcfg.SSHAction, localUser string, _ error) {
  413. pol, ok := c.sshPolicy()
  414. if !ok {
  415. return nil, "", fmt.Errorf("tailssh: rejecting connection; no SSH policy")
  416. }
  417. a, localUser, ok := c.evalSSHPolicy(pol, pubKey)
  418. if !ok {
  419. return nil, "", fmt.Errorf("tailssh: rejecting connection; no matching policy")
  420. }
  421. return a, localUser, nil
  422. }
  423. // pubKeyCacheEntry is the cache value for an HTTPS URL of public keys (like
  424. // "https://github.com/foo.keys")
  425. type pubKeyCacheEntry struct {
  426. lines []string
  427. etag string // if sent by server
  428. at time.Time
  429. }
  430. const (
  431. pubKeyCacheDuration = time.Minute // how long to cache non-empty public keys
  432. pubKeyCacheEmptyDuration = 15 * time.Second // how long to cache empty responses
  433. )
  434. func (srv *server) fetchPublicKeysURLCached(url string) (ce pubKeyCacheEntry, ok bool) {
  435. srv.mu.Lock()
  436. defer srv.mu.Unlock()
  437. // Mostly don't care about the size of this cache. Clean rarely.
  438. if m := srv.fetchPublicKeysCache; len(m) > 50 {
  439. tooOld := srv.now().Add(pubKeyCacheDuration * 10)
  440. for k, ce := range m {
  441. if ce.at.Before(tooOld) {
  442. delete(m, k)
  443. }
  444. }
  445. }
  446. ce, ok = srv.fetchPublicKeysCache[url]
  447. if !ok {
  448. return ce, false
  449. }
  450. maxAge := pubKeyCacheDuration
  451. if len(ce.lines) == 0 {
  452. maxAge = pubKeyCacheEmptyDuration
  453. }
  454. return ce, srv.now().Sub(ce.at) < maxAge
  455. }
  456. func (srv *server) pubKeyClient() *http.Client {
  457. if srv.pubKeyHTTPClient != nil {
  458. return srv.pubKeyHTTPClient
  459. }
  460. return http.DefaultClient
  461. }
  462. // fetchPublicKeysURL fetches the public keys from a URL. The strings are in the
  463. // the typical public key "type base64-string [comment]" format seen at e.g.
  464. // https://github.com/USER.keys
  465. func (srv *server) fetchPublicKeysURL(url string) ([]string, error) {
  466. if !strings.HasPrefix(url, "https://") {
  467. return nil, errors.New("invalid URL scheme")
  468. }
  469. ce, ok := srv.fetchPublicKeysURLCached(url)
  470. if ok {
  471. return ce.lines, nil
  472. }
  473. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  474. defer cancel()
  475. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  476. if err != nil {
  477. return nil, err
  478. }
  479. if ce.etag != "" {
  480. req.Header.Add("If-None-Match", ce.etag)
  481. }
  482. res, err := srv.pubKeyClient().Do(req)
  483. if err != nil {
  484. return nil, err
  485. }
  486. defer res.Body.Close()
  487. var lines []string
  488. var etag string
  489. switch res.StatusCode {
  490. default:
  491. err = fmt.Errorf("unexpected status %v", res.Status)
  492. srv.logf("fetching public keys from %s: %v", url, err)
  493. case http.StatusNotModified:
  494. lines = ce.lines
  495. etag = ce.etag
  496. case http.StatusOK:
  497. var all []byte
  498. all, err = io.ReadAll(io.LimitReader(res.Body, 4<<10))
  499. if s := strings.TrimSpace(string(all)); s != "" {
  500. lines = strings.Split(s, "\n")
  501. }
  502. etag = res.Header.Get("Etag")
  503. }
  504. srv.mu.Lock()
  505. defer srv.mu.Unlock()
  506. mak.Set(&srv.fetchPublicKeysCache, url, pubKeyCacheEntry{
  507. at: srv.now(),
  508. lines: lines,
  509. etag: etag,
  510. })
  511. return lines, err
  512. }
  513. func (c *conn) authorizeSession(s ssh.Session) (_ *contextReader, ok bool) {
  514. c.mu.Lock()
  515. defer c.mu.Unlock()
  516. idH := s.Context().(ssh.Context).SessionID()
  517. if c.idH == "" {
  518. c.idH = idH
  519. } else if c.idH != idH {
  520. c.logf("ssh: session ID mismatch: %q != %q", c.idH, idH)
  521. s.Exit(1)
  522. return nil, false
  523. }
  524. cr := &contextReader{r: s}
  525. action, err := c.resolveTerminalActionLocked(s, cr)
  526. if err != nil {
  527. c.logf("resolveTerminalAction: %v", err)
  528. io.WriteString(s.Stderr(), "Access Denied: failed during authorization check.\r\n")
  529. s.Exit(1)
  530. return nil, false
  531. }
  532. if action.Reject || !action.Accept {
  533. c.logf("access denied for %v", c.info.uprof.LoginName)
  534. s.Exit(1)
  535. return nil, false
  536. }
  537. return cr, true
  538. }
  539. // handleSessionPostSSHAuth runs an SSH session after the SSH-level authentication,
  540. // but not necessarily before all the Tailscale-level extra verification has
  541. // completed. It also handles SFTP requests.
  542. func (c *conn) handleSessionPostSSHAuth(s ssh.Session) {
  543. // Now that we have passed the SSH-level authentication, we can start the
  544. // Tailscale-level extra verification. This means that we are going to
  545. // evaluate the policy provided by control against the incoming SSH session.
  546. cr, ok := c.authorizeSession(s)
  547. if !ok {
  548. return
  549. }
  550. if cr.HasOutstandingRead() {
  551. // There was some buffered input while we were waiting for the policy
  552. // decision.
  553. s = contextReaderSesssion{s, cr}
  554. }
  555. // Do this check after auth, but before starting the session.
  556. switch s.Subsystem() {
  557. case "sftp", "":
  558. metricSFTP.Add(1)
  559. default:
  560. fmt.Fprintf(s.Stderr(), "Unsupported subsystem %q\r\n", s.Subsystem())
  561. s.Exit(1)
  562. return
  563. }
  564. ss := c.newSSHSession(s)
  565. c.mu.Lock()
  566. ss.logf("handling new SSH connection from %v (%v) to ssh-user %q", c.info.uprof.LoginName, c.info.src.Addr(), c.localUser.Username)
  567. ss.logf("access granted to %v as ssh-user %q", c.info.uprof.LoginName, c.localUser.Username)
  568. c.mu.Unlock()
  569. ss.run()
  570. }
  571. // resolveTerminalActionLocked either returns action0 (if it's Accept or Reject) or
  572. // else loops, fetching new SSHActions from the control plane.
  573. //
  574. // Any action with a Message in the chain will be printed to s.
  575. //
  576. // The returned SSHAction will be either Reject or Accept.
  577. //
  578. // c.mu must be held.
  579. func (c *conn) resolveTerminalActionLocked(s ssh.Session, cr *contextReader) (action *tailcfg.SSHAction, err error) {
  580. if c.finalAction != nil || c.finalActionErr != nil {
  581. return c.finalAction, c.finalActionErr
  582. }
  583. if s.PublicKey() != nil {
  584. metricPublicKeyConnections.Add(1)
  585. }
  586. defer func() {
  587. c.finalAction = action
  588. c.finalActionErr = err
  589. c.pubKey = s.PublicKey()
  590. if c.pubKey != nil && action.Accept {
  591. metricPublicKeyAccepts.Add(1)
  592. }
  593. }()
  594. action = c.action0
  595. var awaitReadOnce sync.Once // to start Reads on cr
  596. var sawInterrupt atomic.Bool
  597. var wg sync.WaitGroup
  598. defer wg.Wait() // wait for awaitIntrOnce's goroutine to exit
  599. ctx, cancel := context.WithCancel(s.Context())
  600. defer cancel()
  601. // Loop processing/fetching Actions until one reaches a
  602. // terminal state (Accept, Reject, or invalid Action), or
  603. // until fetchSSHAction times out due to the context being
  604. // done (client disconnect) or its 30 minute timeout passes.
  605. // (Which is a long time for somebody to see login
  606. // instructions and go to a URL to do something.)
  607. for {
  608. if action.Message != "" {
  609. io.WriteString(s.Stderr(), strings.Replace(action.Message, "\n", "\r\n", -1))
  610. }
  611. if action.Accept || action.Reject {
  612. if action.Reject {
  613. metricTerminalReject.Add(1)
  614. } else {
  615. metricTerminalAccept.Add(1)
  616. }
  617. return action, nil
  618. }
  619. url := action.HoldAndDelegate
  620. if url == "" {
  621. metricTerminalMalformed.Add(1)
  622. return nil, errors.New("reached Action that lacked Accept, Reject, and HoldAndDelegate")
  623. }
  624. metricHolds.Add(1)
  625. awaitReadOnce.Do(func() {
  626. wg.Add(1)
  627. go func() {
  628. defer wg.Done()
  629. buf := make([]byte, 1)
  630. for {
  631. n, err := cr.ReadContext(ctx, buf)
  632. if err != nil {
  633. return
  634. }
  635. if n > 0 && buf[0] == 0x03 { // Ctrl-C
  636. sawInterrupt.Store(true)
  637. s.Stderr().Write([]byte("Canceled.\r\n"))
  638. s.Exit(1)
  639. return
  640. }
  641. }
  642. }()
  643. })
  644. url = c.expandDelegateURLLocked(url)
  645. var err error
  646. action, err = c.fetchSSHAction(ctx, url)
  647. if err != nil {
  648. if sawInterrupt.Load() {
  649. metricTerminalInterrupt.Add(1)
  650. return nil, fmt.Errorf("aborted by user")
  651. } else {
  652. metricTerminalFetchError.Add(1)
  653. }
  654. return nil, fmt.Errorf("fetching SSHAction from %s: %w", url, err)
  655. }
  656. }
  657. }
  658. func (c *conn) expandDelegateURLLocked(actionURL string) string {
  659. nm := c.srv.lb.NetMap()
  660. ci := c.info
  661. lu := c.localUser
  662. var dstNodeID string
  663. if nm != nil {
  664. dstNodeID = fmt.Sprint(int64(nm.SelfNode.ID))
  665. }
  666. return strings.NewReplacer(
  667. "$SRC_NODE_IP", url.QueryEscape(ci.src.Addr().String()),
  668. "$SRC_NODE_ID", fmt.Sprint(int64(ci.node.ID)),
  669. "$DST_NODE_IP", url.QueryEscape(ci.dst.Addr().String()),
  670. "$DST_NODE_ID", dstNodeID,
  671. "$SSH_USER", url.QueryEscape(ci.sshUser),
  672. "$LOCAL_USER", url.QueryEscape(lu.Username),
  673. ).Replace(actionURL)
  674. }
  675. func (c *conn) expandPublicKeyURL(pubKeyURL string) string {
  676. if !strings.Contains(pubKeyURL, "$") {
  677. return pubKeyURL
  678. }
  679. var localPart string
  680. var loginName string
  681. c.mu.Lock()
  682. if c.info.uprof != nil {
  683. loginName = c.info.uprof.LoginName
  684. localPart, _, _ = strings.Cut(loginName, "@")
  685. }
  686. c.mu.Unlock()
  687. return strings.NewReplacer(
  688. "$LOGINNAME_EMAIL", loginName,
  689. "$LOGINNAME_LOCALPART", localPart,
  690. ).Replace(pubKeyURL)
  691. }
  692. // sshSession is an accepted Tailscale SSH session.
  693. type sshSession struct {
  694. ssh.Session
  695. sharedID string // ID that's shared with control
  696. logf logger.Logf
  697. ctx *sshContext // implements context.Context
  698. conn *conn
  699. agentListener net.Listener // non-nil if agent-forwarding requested+allowed
  700. // initialized by launchProcess:
  701. cmd *exec.Cmd
  702. stdin io.WriteCloser
  703. stdout io.ReadCloser
  704. stderr io.Reader // nil for pty sessions
  705. ptyReq *ssh.Pty // non-nil for pty sessions
  706. // We use this sync.Once to ensure that we only terminate the process once,
  707. // either it exits itself or is terminated
  708. exitOnce sync.Once
  709. }
  710. func (ss *sshSession) vlogf(format string, args ...interface{}) {
  711. if sshVerboseLogging {
  712. ss.logf(format, args...)
  713. }
  714. }
  715. func (c *conn) newSSHSession(s ssh.Session) *sshSession {
  716. sharedID := fmt.Sprintf("sess-%s-%02x", c.srv.now().UTC().Format("20060102T150405"), randBytes(5))
  717. c.logf("starting session: %v", sharedID)
  718. return &sshSession{
  719. Session: s,
  720. sharedID: sharedID,
  721. ctx: newSSHContext(),
  722. conn: c,
  723. logf: logger.WithPrefix(c.srv.logf, "ssh-session("+sharedID+"): "),
  724. }
  725. }
  726. // isStillValid reports whether the conn is still valid.
  727. func (c *conn) isStillValid() bool {
  728. a, localUser, err := c.evaluatePolicy(c.pubKey)
  729. if err != nil {
  730. return false
  731. }
  732. if !a.Accept && a.HoldAndDelegate == "" {
  733. return false
  734. }
  735. c.mu.Lock()
  736. defer c.mu.Unlock()
  737. return c.localUser.Username == localUser
  738. }
  739. // checkStillValid checks that the conn is still valid per the latest SSHPolicy.
  740. // If not, it terminates all sessions associated with the conn.
  741. func (c *conn) checkStillValid() {
  742. if c.isStillValid() {
  743. return
  744. }
  745. metricPolicyChangeKick.Add(1)
  746. c.logf("session no longer valid per new SSH policy; closing")
  747. for _, s := range c.sessions {
  748. s.ctx.CloseWithError(userVisibleError{
  749. fmt.Sprintf("Access revoked.\r\n"),
  750. context.Canceled,
  751. })
  752. }
  753. }
  754. func (c *conn) fetchSSHAction(ctx context.Context, url string) (*tailcfg.SSHAction, error) {
  755. ctx, cancel := context.WithTimeout(ctx, 30*time.Minute)
  756. defer cancel()
  757. bo := backoff.NewBackoff("fetch-ssh-action", c.logf, 10*time.Second)
  758. for {
  759. if err := ctx.Err(); err != nil {
  760. return nil, err
  761. }
  762. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  763. if err != nil {
  764. return nil, err
  765. }
  766. res, err := c.srv.lb.DoNoiseRequest(req)
  767. if err != nil {
  768. bo.BackOff(ctx, err)
  769. continue
  770. }
  771. if res.StatusCode != 200 {
  772. body, _ := io.ReadAll(res.Body)
  773. res.Body.Close()
  774. if len(body) > 1<<10 {
  775. body = body[:1<<10]
  776. }
  777. c.logf("fetch of %v: %s, %s", url, res.Status, body)
  778. bo.BackOff(ctx, fmt.Errorf("unexpected status: %v", res.Status))
  779. continue
  780. }
  781. a := new(tailcfg.SSHAction)
  782. err = json.NewDecoder(res.Body).Decode(a)
  783. res.Body.Close()
  784. if err != nil {
  785. c.logf("invalid next SSHAction JSON from %v: %v", url, err)
  786. bo.BackOff(ctx, err)
  787. continue
  788. }
  789. return a, nil
  790. }
  791. }
  792. // killProcessOnContextDone waits for ss.ctx to be done and kills the process,
  793. // unless the process has already exited.
  794. func (ss *sshSession) killProcessOnContextDone() {
  795. <-ss.ctx.Done()
  796. // Either the process has already exited, in which case this does nothing.
  797. // Or, the process is still running in which case this will kill it.
  798. ss.exitOnce.Do(func() {
  799. err := ss.ctx.Err()
  800. if serr, ok := err.(SSHTerminationError); ok {
  801. msg := serr.SSHTerminationMessage()
  802. if msg != "" {
  803. io.WriteString(ss.Stderr(), "\r\n\r\n"+msg+"\r\n\r\n")
  804. }
  805. }
  806. ss.logf("terminating SSH session from %v: %v", ss.conn.info.src.Addr(), err)
  807. // We don't need to Process.Wait here, sshSession.run() does
  808. // the waiting regardless of termination reason.
  809. // TODO(maisem): should this be a SIGTERM followed by a SIGKILL?
  810. ss.cmd.Process.Kill()
  811. })
  812. }
  813. // startSessionLocked registers ss as an active session.
  814. // It must be called with srv.mu held.
  815. func (c *conn) startSessionLocked(ss *sshSession) {
  816. c.srv.sessionWaitGroup.Add(1)
  817. if ss.sharedID == "" {
  818. panic("empty sharedID")
  819. }
  820. c.sessions = append(c.sessions, ss)
  821. }
  822. // endSession unregisters s from the list of active sessions.
  823. func (c *conn) endSession(ss *sshSession) {
  824. defer c.srv.sessionWaitGroup.Done()
  825. c.srv.mu.Lock()
  826. defer c.srv.mu.Unlock()
  827. for i, s := range c.sessions {
  828. if s == ss {
  829. c.sessions = append(c.sessions[:i], c.sessions[i+1:]...)
  830. break
  831. }
  832. }
  833. }
  834. var errSessionDone = errors.New("session is done")
  835. // handleSSHAgentForwarding starts a Unix socket listener and in the background
  836. // forwards agent connections between the listener and the ssh.Session.
  837. // On success, it assigns ss.agentListener.
  838. func (ss *sshSession) handleSSHAgentForwarding(s ssh.Session, lu *user.User) error {
  839. if !ssh.AgentRequested(ss) || !ss.conn.finalAction.AllowAgentForwarding {
  840. return nil
  841. }
  842. ss.logf("ssh: agent forwarding requested")
  843. ln, err := ssh.NewAgentListener()
  844. if err != nil {
  845. return err
  846. }
  847. defer func() {
  848. if err != nil && ln != nil {
  849. ln.Close()
  850. }
  851. }()
  852. uid, err := strconv.ParseUint(lu.Uid, 10, 32)
  853. if err != nil {
  854. return err
  855. }
  856. gid, err := strconv.ParseUint(lu.Gid, 10, 32)
  857. if err != nil {
  858. return err
  859. }
  860. socket := ln.Addr().String()
  861. dir := filepath.Dir(socket)
  862. // Make sure the socket is accessible only by the user.
  863. if err := os.Chmod(socket, 0600); err != nil {
  864. return err
  865. }
  866. if err := os.Chown(socket, int(uid), int(gid)); err != nil {
  867. return err
  868. }
  869. // Make sure the dir is also accessible.
  870. if err := os.Chmod(dir, 0755); err != nil {
  871. return err
  872. }
  873. go ssh.ForwardAgentConnections(ln, s)
  874. ss.agentListener = ln
  875. return nil
  876. }
  877. // recordSSH is a temporary dev knob to test the SSH recording
  878. // functionality and support off-node streaming.
  879. //
  880. // TODO(bradfitz,maisem): move this to SSHPolicy.
  881. var recordSSH = envknob.Bool("TS_DEBUG_LOG_SSH")
  882. // run is the entrypoint for a newly accepted SSH session.
  883. //
  884. // It handles ss once it's been accepted and determined
  885. // that it should run.
  886. func (ss *sshSession) run() {
  887. metricActiveSessions.Add(1)
  888. defer metricActiveSessions.Add(-1)
  889. defer ss.ctx.CloseWithError(errSessionDone)
  890. srv := ss.conn.srv
  891. srv.mu.Lock()
  892. if srv.shutdownCalled {
  893. srv.mu.Unlock()
  894. // Do not start any new sessions.
  895. fmt.Fprintf(ss, "Tailscale SSH is shutting down\r\n")
  896. ss.Exit(1)
  897. return
  898. }
  899. ss.conn.startSessionLocked(ss)
  900. lu := ss.conn.localUser
  901. localUser := lu.Username
  902. srv.mu.Unlock()
  903. defer ss.conn.endSession(ss)
  904. if ss.conn.finalAction.SessionDuration != 0 {
  905. t := time.AfterFunc(ss.conn.finalAction.SessionDuration, func() {
  906. ss.ctx.CloseWithError(userVisibleError{
  907. fmt.Sprintf("Session timeout of %v elapsed.", ss.conn.finalAction.SessionDuration),
  908. context.DeadlineExceeded,
  909. })
  910. })
  911. defer t.Stop()
  912. }
  913. logf := ss.logf
  914. if euid := os.Geteuid(); euid != 0 {
  915. if lu.Uid != fmt.Sprint(euid) {
  916. ss.logf("can't switch to user %q from process euid %v", localUser, euid)
  917. fmt.Fprintf(ss, "can't switch user\r\n")
  918. ss.Exit(1)
  919. return
  920. }
  921. }
  922. // Take control of the PTY so that we can configure it below.
  923. // See https://github.com/tailscale/tailscale/issues/4146
  924. ss.DisablePTYEmulation()
  925. var rec *recording // or nil if disabled
  926. if ss.Subsystem() != "sftp" {
  927. if err := ss.handleSSHAgentForwarding(ss, lu); err != nil {
  928. ss.logf("agent forwarding failed: %v", err)
  929. } else if ss.agentListener != nil {
  930. // TODO(maisem/bradfitz): add a way to close all session resources
  931. defer ss.agentListener.Close()
  932. }
  933. if ss.shouldRecord() {
  934. var err error
  935. rec, err = ss.startNewRecording()
  936. if err != nil {
  937. fmt.Fprintf(ss, "can't start new recording\r\n")
  938. ss.logf("startNewRecording: %v", err)
  939. ss.Exit(1)
  940. return
  941. }
  942. defer rec.Close()
  943. }
  944. }
  945. err := ss.launchProcess()
  946. if err != nil {
  947. logf("start failed: %v", err.Error())
  948. ss.Exit(1)
  949. return
  950. }
  951. go ss.killProcessOnContextDone()
  952. go func() {
  953. defer ss.stdin.Close()
  954. if _, err := io.Copy(rec.writer("i", ss.stdin), ss); err != nil {
  955. logf("stdin copy: %v", err)
  956. ss.ctx.CloseWithError(err)
  957. } else if ss.ptyReq != nil {
  958. const EOT = 4 // https://en.wikipedia.org/wiki/End-of-Transmission_character
  959. ss.stdin.Write([]byte{EOT})
  960. }
  961. }()
  962. go func() {
  963. defer ss.stdout.Close()
  964. _, err := io.Copy(rec.writer("o", ss), ss.stdout)
  965. if err != nil && !errors.Is(err, io.EOF) {
  966. logf("stdout copy: %v", err)
  967. ss.ctx.CloseWithError(err)
  968. } else {
  969. ss.CloseWrite()
  970. }
  971. }()
  972. // stderr is nil for ptys.
  973. if ss.stderr != nil {
  974. go func() {
  975. _, err := io.Copy(ss.Stderr(), ss.stderr)
  976. if err != nil {
  977. logf("stderr copy: %v", err)
  978. }
  979. }()
  980. }
  981. err = ss.cmd.Wait()
  982. // This will either make the SSH Termination goroutine be a no-op,
  983. // or itself will be a no-op because the process was killed by the
  984. // aforementioned goroutine.
  985. ss.exitOnce.Do(func() {})
  986. if err == nil {
  987. ss.logf("Session complete")
  988. ss.Exit(0)
  989. return
  990. }
  991. if ee, ok := err.(*exec.ExitError); ok {
  992. code := ee.ProcessState.ExitCode()
  993. ss.logf("Wait: code=%v", code)
  994. ss.Exit(code)
  995. return
  996. }
  997. ss.logf("Wait: %v", err)
  998. ss.Exit(1)
  999. return
  1000. }
  1001. func (ss *sshSession) shouldRecord() bool {
  1002. // for now only record pty sessions
  1003. // TODO(bradfitz,maisem): make configurable on SSHPolicy and
  1004. // support recording non-pty stuff too.
  1005. _, _, isPtyReq := ss.Pty()
  1006. return recordSSH && isPtyReq
  1007. }
  1008. type sshConnInfo struct {
  1009. // sshUser is the requested local SSH username ("root", "alice", etc).
  1010. sshUser string
  1011. // src is the Tailscale IP and port that the connection came from.
  1012. src netip.AddrPort
  1013. // dst is the Tailscale IP and port that the connection came for.
  1014. dst netip.AddrPort
  1015. // node is srcIP's node.
  1016. node *tailcfg.Node
  1017. // uprof is node's UserProfile.
  1018. uprof *tailcfg.UserProfile
  1019. }
  1020. func (ci *sshConnInfo) String() string {
  1021. return fmt.Sprintf("%v->%v@%v", ci.src, ci.sshUser, ci.dst)
  1022. }
  1023. func (c *conn) ruleExpired(r *tailcfg.SSHRule) bool {
  1024. if r.RuleExpires == nil {
  1025. return false
  1026. }
  1027. return r.RuleExpires.Before(c.srv.now())
  1028. }
  1029. func (c *conn) evalSSHPolicy(pol *tailcfg.SSHPolicy, pubKey gossh.PublicKey) (a *tailcfg.SSHAction, localUser string, ok bool) {
  1030. for _, r := range pol.Rules {
  1031. if a, localUser, err := c.matchRule(r, pubKey); err == nil {
  1032. return a, localUser, true
  1033. }
  1034. }
  1035. return nil, "", false
  1036. }
  1037. // internal errors for testing; they don't escape to callers or logs.
  1038. var (
  1039. errNilRule = errors.New("nil rule")
  1040. errNilAction = errors.New("nil action")
  1041. errRuleExpired = errors.New("rule expired")
  1042. errPrincipalMatch = errors.New("principal didn't match")
  1043. errUserMatch = errors.New("user didn't match")
  1044. errInvalidConn = errors.New("invalid connection state")
  1045. )
  1046. func (c *conn) matchRule(r *tailcfg.SSHRule, pubKey gossh.PublicKey) (a *tailcfg.SSHAction, localUser string, err error) {
  1047. if c == nil {
  1048. return nil, "", errInvalidConn
  1049. }
  1050. c.mu.Lock()
  1051. ci := c.info
  1052. c.mu.Unlock()
  1053. if ci == nil {
  1054. c.logf("invalid connection state")
  1055. return nil, "", errInvalidConn
  1056. }
  1057. if r == nil {
  1058. return nil, "", errNilRule
  1059. }
  1060. if r.Action == nil {
  1061. return nil, "", errNilAction
  1062. }
  1063. if c.ruleExpired(r) {
  1064. return nil, "", errRuleExpired
  1065. }
  1066. if !r.Action.Reject {
  1067. // For all but Reject rules, SSHUsers is required.
  1068. // If SSHUsers is nil or empty, mapLocalUser will return an
  1069. // empty string anyway.
  1070. localUser = mapLocalUser(r.SSHUsers, ci.sshUser)
  1071. if localUser == "" {
  1072. return nil, "", errUserMatch
  1073. }
  1074. }
  1075. if ok, err := c.anyPrincipalMatches(r.Principals, pubKey); err != nil {
  1076. return nil, "", err
  1077. } else if !ok {
  1078. return nil, "", errPrincipalMatch
  1079. }
  1080. return r.Action, localUser, nil
  1081. }
  1082. func mapLocalUser(ruleSSHUsers map[string]string, reqSSHUser string) (localUser string) {
  1083. v, ok := ruleSSHUsers[reqSSHUser]
  1084. if !ok {
  1085. v = ruleSSHUsers["*"]
  1086. }
  1087. if v == "=" {
  1088. return reqSSHUser
  1089. }
  1090. return v
  1091. }
  1092. func (c *conn) anyPrincipalMatches(ps []*tailcfg.SSHPrincipal, pubKey gossh.PublicKey) (bool, error) {
  1093. for _, p := range ps {
  1094. if p == nil {
  1095. continue
  1096. }
  1097. if ok, err := c.principalMatches(p, pubKey); err != nil {
  1098. return false, err
  1099. } else if ok {
  1100. return true, nil
  1101. }
  1102. }
  1103. return false, nil
  1104. }
  1105. func (c *conn) principalMatches(p *tailcfg.SSHPrincipal, pubKey gossh.PublicKey) (bool, error) {
  1106. if !c.principalMatchesTailscaleIdentity(p) {
  1107. return false, nil
  1108. }
  1109. return c.principalMatchesPubKey(p, pubKey)
  1110. }
  1111. // principalMatchesTailscaleIdentity reports whether one of p's four fields
  1112. // that match the Tailscale identity match (Node, NodeIP, UserLogin, Any).
  1113. // This function does not consider PubKeys.
  1114. func (c *conn) principalMatchesTailscaleIdentity(p *tailcfg.SSHPrincipal) bool {
  1115. c.mu.Lock()
  1116. ci := c.info
  1117. c.mu.Unlock()
  1118. if p.Any {
  1119. return true
  1120. }
  1121. if !p.Node.IsZero() && ci.node != nil && p.Node == ci.node.StableID {
  1122. return true
  1123. }
  1124. if p.NodeIP != "" {
  1125. if ip, _ := netip.ParseAddr(p.NodeIP); ip == ci.src.Addr() {
  1126. return true
  1127. }
  1128. }
  1129. if p.UserLogin != "" && ci.uprof != nil && ci.uprof.LoginName == p.UserLogin {
  1130. return true
  1131. }
  1132. return false
  1133. }
  1134. func (c *conn) principalMatchesPubKey(p *tailcfg.SSHPrincipal, clientPubKey gossh.PublicKey) (bool, error) {
  1135. if len(p.PubKeys) == 0 {
  1136. return true, nil
  1137. }
  1138. if clientPubKey == nil {
  1139. return false, nil
  1140. }
  1141. knownKeys := p.PubKeys
  1142. if len(knownKeys) == 1 && strings.HasPrefix(knownKeys[0], "https://") {
  1143. var err error
  1144. knownKeys, err = c.srv.fetchPublicKeysURL(c.expandPublicKeyURL(knownKeys[0]))
  1145. if err != nil {
  1146. return false, err
  1147. }
  1148. }
  1149. for _, knownKey := range knownKeys {
  1150. if pubKeyMatchesAuthorizedKey(clientPubKey, knownKey) {
  1151. return true, nil
  1152. }
  1153. }
  1154. return false, nil
  1155. }
  1156. func pubKeyMatchesAuthorizedKey(pubKey ssh.PublicKey, wantKey string) bool {
  1157. wantKeyType, rest, ok := strings.Cut(wantKey, " ")
  1158. if !ok {
  1159. return false
  1160. }
  1161. if pubKey.Type() != wantKeyType {
  1162. return false
  1163. }
  1164. wantKeyB64, _, _ := strings.Cut(rest, " ")
  1165. wantKeyData, _ := base64.StdEncoding.DecodeString(wantKeyB64)
  1166. return len(wantKeyData) > 0 && bytes.Equal(pubKey.Marshal(), wantKeyData)
  1167. }
  1168. func randBytes(n int) []byte {
  1169. b := make([]byte, n)
  1170. if _, err := rand.Read(b); err != nil {
  1171. panic(err)
  1172. }
  1173. return b
  1174. }
  1175. // startNewRecording starts a new SSH session recording.
  1176. //
  1177. // It writes an asciinema file to
  1178. // $TAILSCALE_VAR_ROOT/ssh-sessions/ssh-session-<unixtime>-*.cast.
  1179. func (ss *sshSession) startNewRecording() (*recording, error) {
  1180. var w ssh.Window
  1181. if ptyReq, _, isPtyReq := ss.Pty(); isPtyReq {
  1182. w = ptyReq.Window
  1183. }
  1184. term := envValFromList(ss.Environ(), "TERM")
  1185. if term == "" {
  1186. term = "xterm-256color" // something non-empty
  1187. }
  1188. now := time.Now()
  1189. rec := &recording{
  1190. ss: ss,
  1191. start: now,
  1192. }
  1193. varRoot := ss.conn.srv.lb.TailscaleVarRoot()
  1194. if varRoot == "" {
  1195. return nil, errors.New("no var root for recording storage")
  1196. }
  1197. dir := filepath.Join(varRoot, "ssh-sessions")
  1198. if err := os.MkdirAll(dir, 0700); err != nil {
  1199. return nil, err
  1200. }
  1201. f, err := ioutil.TempFile(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
  1202. if err != nil {
  1203. return nil, err
  1204. }
  1205. rec.out = f
  1206. // {"version": 2, "width": 221, "height": 84, "timestamp": 1647146075, "env": {"SHELL": "/bin/bash", "TERM": "screen"}}
  1207. type CastHeader struct {
  1208. Version int `json:"version"`
  1209. Width int `json:"width"`
  1210. Height int `json:"height"`
  1211. Timestamp int64 `json:"timestamp"`
  1212. Env map[string]string `json:"env"`
  1213. }
  1214. j, err := json.Marshal(CastHeader{
  1215. Version: 2,
  1216. Width: w.Width,
  1217. Height: w.Height,
  1218. Timestamp: now.Unix(),
  1219. Env: map[string]string{
  1220. "TERM": term,
  1221. // TODO(bradiftz): anything else important?
  1222. // including all seems noisey, but maybe we should
  1223. // for auditing. But first need to break
  1224. // launchProcess's startWithStdPipes and
  1225. // startWithPTY up so that they first return the cmd
  1226. // without starting it, and then a step that starts
  1227. // it. Then we can (1) make the cmd, (2) start the
  1228. // recording, (3) start the process.
  1229. },
  1230. })
  1231. if err != nil {
  1232. f.Close()
  1233. return nil, err
  1234. }
  1235. ss.logf("starting asciinema recording to %s", f.Name())
  1236. j = append(j, '\n')
  1237. if _, err := f.Write(j); err != nil {
  1238. f.Close()
  1239. return nil, err
  1240. }
  1241. return rec, nil
  1242. }
  1243. // recording is the state for an SSH session recording.
  1244. type recording struct {
  1245. ss *sshSession
  1246. start time.Time
  1247. mu sync.Mutex // guards writes to, close of out
  1248. out *os.File // nil if closed
  1249. }
  1250. func (r *recording) Close() error {
  1251. r.mu.Lock()
  1252. defer r.mu.Unlock()
  1253. if r.out == nil {
  1254. return nil
  1255. }
  1256. err := r.out.Close()
  1257. r.out = nil
  1258. return err
  1259. }
  1260. // writer returns an io.Writer around w that first records the write.
  1261. //
  1262. // The dir should be "i" for input or "o" for output.
  1263. //
  1264. // If r is nil, it returns w unchanged.
  1265. func (r *recording) writer(dir string, w io.Writer) io.Writer {
  1266. if r == nil {
  1267. return w
  1268. }
  1269. return &loggingWriter{r, dir, w}
  1270. }
  1271. // loggingWriter is an io.Writer wrapper that writes first an
  1272. // asciinema JSON cast format recording line, and then writes to w.
  1273. type loggingWriter struct {
  1274. r *recording
  1275. dir string // "i" or "o" (input or output)
  1276. w io.Writer // underlying Writer, after writing to r.out
  1277. }
  1278. func (w loggingWriter) Write(p []byte) (n int, err error) {
  1279. j, err := json.Marshal([]interface{}{
  1280. time.Since(w.r.start).Seconds(),
  1281. w.dir,
  1282. string(p),
  1283. })
  1284. if err != nil {
  1285. return 0, err
  1286. }
  1287. j = append(j, '\n')
  1288. if err := w.writeCastLine(j); err != nil {
  1289. return 0, err
  1290. }
  1291. return w.w.Write(p)
  1292. }
  1293. func (w loggingWriter) writeCastLine(j []byte) error {
  1294. w.r.mu.Lock()
  1295. defer w.r.mu.Unlock()
  1296. if w.r.out == nil {
  1297. return errors.New("logger closed")
  1298. }
  1299. _, err := w.r.out.Write(j)
  1300. if err != nil {
  1301. return fmt.Errorf("logger Write: %w", err)
  1302. }
  1303. return nil
  1304. }
  1305. func envValFromList(env []string, wantKey string) (v string) {
  1306. for _, kv := range env {
  1307. if thisKey, v, ok := strings.Cut(kv, "="); ok && envEq(thisKey, wantKey) {
  1308. return v
  1309. }
  1310. }
  1311. return ""
  1312. }
  1313. // envEq reports whether environment variable a == b for the current
  1314. // operating system.
  1315. func envEq(a, b string) bool {
  1316. if runtime.GOOS == "windows" {
  1317. return strings.EqualFold(a, b)
  1318. }
  1319. return a == b
  1320. }
  1321. var (
  1322. metricActiveSessions = clientmetric.NewGauge("ssh_active_sessions")
  1323. metricIncomingConnections = clientmetric.NewCounter("ssh_incoming_connections")
  1324. metricPublicKeyConnections = clientmetric.NewCounter("ssh_publickey_connections") // total
  1325. metricPublicKeyAccepts = clientmetric.NewCounter("ssh_publickey_accepts") // accepted subset of ssh_publickey_connections
  1326. metricTerminalAccept = clientmetric.NewCounter("ssh_terminalaction_accept")
  1327. metricTerminalReject = clientmetric.NewCounter("ssh_terminalaction_reject")
  1328. metricTerminalInterrupt = clientmetric.NewCounter("ssh_terminalaction_interrupt")
  1329. metricTerminalMalformed = clientmetric.NewCounter("ssh_terminalaction_malformed")
  1330. metricTerminalFetchError = clientmetric.NewCounter("ssh_terminalaction_fetch_error")
  1331. metricHolds = clientmetric.NewCounter("ssh_holds")
  1332. metricPolicyChangeKick = clientmetric.NewCounter("ssh_policy_change_kick")
  1333. metricSFTP = clientmetric.NewCounter("ssh_sftp_requests")
  1334. metricLocalPortForward = clientmetric.NewCounter("ssh_local_port_forward_requests")
  1335. )