tailssh.go 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build (linux && !android) || (darwin && !ios) || freebsd || openbsd || plan9
  4. // Package tailssh is an SSH server integrated into Tailscale.
  5. package tailssh
  6. import (
  7. "bytes"
  8. "context"
  9. "crypto/rand"
  10. "encoding/json"
  11. "errors"
  12. "fmt"
  13. "io"
  14. "net"
  15. "net/http"
  16. "net/netip"
  17. "net/url"
  18. "os"
  19. "os/exec"
  20. "path/filepath"
  21. "runtime"
  22. "strconv"
  23. "strings"
  24. "sync"
  25. "sync/atomic"
  26. "syscall"
  27. "time"
  28. gossh "golang.org/x/crypto/ssh"
  29. "tailscale.com/envknob"
  30. "tailscale.com/feature"
  31. "tailscale.com/ipn/ipnlocal"
  32. "tailscale.com/net/tsaddr"
  33. "tailscale.com/net/tsdial"
  34. "tailscale.com/sessionrecording"
  35. "tailscale.com/tailcfg"
  36. "tailscale.com/tempfork/gliderlabs/ssh"
  37. "tailscale.com/types/key"
  38. "tailscale.com/types/logger"
  39. "tailscale.com/types/netmap"
  40. "tailscale.com/util/backoff"
  41. "tailscale.com/util/clientmetric"
  42. "tailscale.com/util/httpm"
  43. "tailscale.com/util/mak"
  44. )
  45. var (
  46. sshVerboseLogging = envknob.RegisterBool("TS_DEBUG_SSH_VLOG")
  47. sshDisableSFTP = envknob.RegisterBool("TS_SSH_DISABLE_SFTP")
  48. sshDisableForwarding = envknob.RegisterBool("TS_SSH_DISABLE_FORWARDING")
  49. sshDisablePTY = envknob.RegisterBool("TS_SSH_DISABLE_PTY")
  50. // errTerminal is an empty gossh.PartialSuccessError (with no 'Next'
  51. // authentication methods that may proceed), which results in the SSH
  52. // server immediately disconnecting the client.
  53. errTerminal = &gossh.PartialSuccessError{}
  54. // hookSSHLoginSuccess is called after successful SSH authentication.
  55. // It is set by platform-specific code (e.g., auditd_linux.go).
  56. hookSSHLoginSuccess feature.Hook[func(logf logger.Logf, c *conn)]
  57. )
  58. const (
  59. // forcePasswordSuffix is the suffix at the end of a username that forces
  60. // Tailscale SSH into password authentication mode to work around buggy SSH
  61. // clients that get confused by successful replies to auth type "none".
  62. forcePasswordSuffix = "+password"
  63. )
  64. // ipnLocalBackend is the subset of ipnlocal.LocalBackend that we use.
  65. // It is used for testing.
  66. type ipnLocalBackend interface {
  67. GetSSH_HostKeys() ([]gossh.Signer, error)
  68. ShouldRunSSH() bool
  69. NetMap() *netmap.NetworkMap
  70. WhoIs(proto string, ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool)
  71. DoNoiseRequest(req *http.Request) (*http.Response, error)
  72. Dialer() *tsdial.Dialer
  73. TailscaleVarRoot() string
  74. NodeKey() key.NodePublic
  75. }
  76. type server struct {
  77. lb ipnLocalBackend
  78. logf logger.Logf
  79. tailscaledPath string
  80. timeNow func() time.Time // or nil for time.Now
  81. sessionWaitGroup sync.WaitGroup
  82. // mu protects the following
  83. mu sync.Mutex
  84. activeConns map[*conn]bool // set; value is always true
  85. shutdownCalled bool
  86. }
  87. func (srv *server) now() time.Time {
  88. if srv != nil && srv.timeNow != nil {
  89. return srv.timeNow()
  90. }
  91. return time.Now()
  92. }
  93. func init() {
  94. ipnlocal.RegisterNewSSHServer(func(logf logger.Logf, lb *ipnlocal.LocalBackend) (ipnlocal.SSHServer, error) {
  95. tsd, err := os.Executable()
  96. if err != nil {
  97. return nil, err
  98. }
  99. srv := &server{
  100. lb: lb,
  101. logf: logf,
  102. tailscaledPath: tsd,
  103. timeNow: func() time.Time {
  104. return lb.ControlNow(time.Now())
  105. },
  106. }
  107. return srv, nil
  108. })
  109. }
  110. // attachSessionToConnIfNotShutdown ensures that srv is not shutdown before
  111. // attaching the session to the conn. This ensures that once Shutdown is called,
  112. // new sessions are not allowed and existing ones are cleaned up.
  113. // It reports whether ss was attached to the conn.
  114. func (srv *server) attachSessionToConnIfNotShutdown(ss *sshSession) bool {
  115. srv.mu.Lock()
  116. defer srv.mu.Unlock()
  117. if srv.shutdownCalled {
  118. // Do not start any new sessions.
  119. return false
  120. }
  121. ss.conn.attachSession(ss)
  122. return true
  123. }
  124. func (srv *server) trackActiveConn(c *conn, add bool) {
  125. srv.mu.Lock()
  126. defer srv.mu.Unlock()
  127. if add {
  128. mak.Set(&srv.activeConns, c, true)
  129. return
  130. }
  131. delete(srv.activeConns, c)
  132. }
  133. // NumActiveConns returns the number of active SSH connections.
  134. func (srv *server) NumActiveConns() int {
  135. srv.mu.Lock()
  136. defer srv.mu.Unlock()
  137. return len(srv.activeConns)
  138. }
  139. // HandleSSHConn handles a Tailscale SSH connection from c.
  140. // This is the entry point for all SSH connections.
  141. // When this returns, the connection is closed.
  142. func (srv *server) HandleSSHConn(nc net.Conn) error {
  143. metricIncomingConnections.Add(1)
  144. c, err := srv.newConn()
  145. if err != nil {
  146. return err
  147. }
  148. srv.trackActiveConn(c, true) // add
  149. defer srv.trackActiveConn(c, false) // remove
  150. c.HandleConn(nc)
  151. // Return nil to signal to netstack's interception that it doesn't need to
  152. // log. If ss.HandleConn had problems, it can log itself (ideally on an
  153. // sshSession.logf).
  154. return nil
  155. }
  156. // Shutdown terminates all active sessions.
  157. func (srv *server) Shutdown() {
  158. srv.mu.Lock()
  159. srv.shutdownCalled = true
  160. for c := range srv.activeConns {
  161. c.Close()
  162. }
  163. srv.mu.Unlock()
  164. srv.sessionWaitGroup.Wait()
  165. }
  166. // OnPolicyChange terminates any active sessions that no longer match
  167. // the SSH access policy.
  168. func (srv *server) OnPolicyChange() {
  169. srv.mu.Lock()
  170. defer srv.mu.Unlock()
  171. for c := range srv.activeConns {
  172. if c.info == nil {
  173. // c.info is nil when the connection hasn't been authenticated yet.
  174. // In that case, the connection will be terminated when it is.
  175. continue
  176. }
  177. go c.checkStillValid()
  178. }
  179. }
  180. // conn represents a single SSH connection and its associated
  181. // ssh.Server.
  182. //
  183. // During the lifecycle of a connection, the following are called in order:
  184. // Setup and discover server info
  185. // - ServerConfigCallback
  186. //
  187. // Get access to a ServerPreAuthConn (useful for sending banners)
  188. //
  189. // Do the user auth with a NoClientAuthCallback. If user specified
  190. // a username ending in "+password", follow this with password auth
  191. // (to work around buggy SSH clients that don't work with noauth).
  192. //
  193. // Once auth is done, the conn can be multiplexed with multiple sessions and
  194. // channels concurrently. At which point any of the following can be called
  195. // in any order.
  196. // - c.handleSessionPostSSHAuth
  197. // - c.mayForwardLocalPortTo followed by ssh.DirectTCPIPHandler
  198. type conn struct {
  199. *ssh.Server
  200. srv *server
  201. insecureSkipTailscaleAuth bool // used by tests.
  202. // idH is the RFC4253 sec8 hash H. It is used to identify the connection,
  203. // and is shared among all sessions. It should not be shared outside
  204. // process. It is confusingly referred to as SessionID by the gliderlabs/ssh
  205. // library.
  206. idH string
  207. connID string // ID that's shared with control
  208. // spac is a [gossh.ServerPreAuthConn] used for sending auth banners.
  209. // Banners cannot be sent after auth completes.
  210. spac gossh.ServerPreAuthConn
  211. action0 *tailcfg.SSHAction // set by clientAuth
  212. finalAction *tailcfg.SSHAction // set by clientAuth
  213. info *sshConnInfo // set by setInfo
  214. localUser *userMeta // set by clientAuth
  215. userGroupIDs []string // set by clientAuth
  216. acceptEnv []string
  217. // mu protects the following fields.
  218. //
  219. // srv.mu should be acquired prior to mu.
  220. // It is safe to just acquire mu, but unsafe to
  221. // acquire mu and then srv.mu.
  222. mu sync.Mutex // protects the following
  223. sessions []*sshSession
  224. }
  225. func (c *conn) logf(format string, args ...any) {
  226. format = fmt.Sprintf("%v: %v", c.connID, format)
  227. c.srv.logf(format, args...)
  228. }
  229. func (c *conn) vlogf(format string, args ...any) {
  230. if sshVerboseLogging() {
  231. c.logf(format, args...)
  232. }
  233. }
  234. // errDenied is returned by auth callbacks when a connection is denied by the
  235. // policy. It writes the message to an auth banner and then returns an empty
  236. // gossh.PartialSuccessError in order to stop processing authentication
  237. // attempts and immediately disconnect the client.
  238. func (c *conn) errDenied(message string) error {
  239. if message == "" {
  240. message = "tailscale: access denied"
  241. }
  242. if err := c.spac.SendAuthBanner(message); err != nil {
  243. c.logf("failed to send auth banner: %s", err)
  244. }
  245. return errTerminal
  246. }
  247. // errBanner writes the given message to an auth banner and then returns an
  248. // empty gossh.PartialSuccessError in order to stop processing authentication
  249. // attempts and immediately disconnect the client. The contents of err is not
  250. // leaked in the auth banner, but it is logged to the server's log.
  251. func (c *conn) errBanner(message string, err error) error {
  252. if err != nil {
  253. c.logf("%s: %s", message, err)
  254. }
  255. if err := c.spac.SendAuthBanner("tailscale: " + message + "\n"); err != nil {
  256. c.logf("failed to send auth banner: %s", err)
  257. }
  258. return errTerminal
  259. }
  260. // errUnexpected is returned by auth callbacks that encounter an unexpected
  261. // error, such as being unable to send an auth banner. It sends an empty
  262. // gossh.PartialSuccessError to tell gossh.Server to stop processing
  263. // authentication attempts and instead disconnect immediately.
  264. func (c *conn) errUnexpected(err error) error {
  265. c.logf("terminal error: %s", err)
  266. return errTerminal
  267. }
  268. // clientAuth is responsible for performing client authentication.
  269. //
  270. // If policy evaluation fails, it returns an error.
  271. // If access is denied, it returns an error. This must always be an empty
  272. // gossh.PartialSuccessError to prevent further authentication methods from
  273. // being tried.
  274. func (c *conn) clientAuth(cm gossh.ConnMetadata) (perms *gossh.Permissions, retErr error) {
  275. defer func() {
  276. if pse, ok := retErr.(*gossh.PartialSuccessError); ok {
  277. if pse.Next.GSSAPIWithMICConfig != nil ||
  278. pse.Next.KeyboardInteractiveCallback != nil ||
  279. pse.Next.PasswordCallback != nil ||
  280. pse.Next.PublicKeyCallback != nil {
  281. panic("clientAuth attempted to return a non-empty PartialSuccessError")
  282. }
  283. } else if retErr != nil {
  284. panic(fmt.Sprintf("clientAuth attempted to return a non-PartialSuccessError error of type: %t", retErr))
  285. }
  286. }()
  287. if c.insecureSkipTailscaleAuth {
  288. return &gossh.Permissions{}, nil
  289. }
  290. if err := c.setInfo(cm); err != nil {
  291. return nil, c.errBanner("failed to get connection info", err)
  292. }
  293. action, localUser, acceptEnv, result := c.evaluatePolicy()
  294. switch result {
  295. case accepted:
  296. // do nothing
  297. case rejectedUser:
  298. return nil, c.errBanner(fmt.Sprintf("tailnet policy does not permit you to SSH as user %q", c.info.sshUser), nil)
  299. case rejected, noPolicy:
  300. return nil, c.errBanner("tailnet policy does not permit you to SSH to this node", fmt.Errorf("failed to evaluate policy, result: %s", result))
  301. default:
  302. return nil, c.errBanner("failed to evaluate tailnet policy", fmt.Errorf("failed to evaluate policy, result: %s", result))
  303. }
  304. c.action0 = action
  305. if action.Accept || action.HoldAndDelegate != "" {
  306. // Immediately look up user information for purposes of generating
  307. // hold and delegate URL (if necessary).
  308. lu, err := userLookup(localUser)
  309. if err != nil {
  310. return nil, c.errBanner(fmt.Sprintf("failed to look up local user %q ", localUser), err)
  311. }
  312. gids, err := lu.GroupIds()
  313. if err != nil {
  314. return nil, c.errBanner("failed to look up local user's group IDs", err)
  315. }
  316. c.userGroupIDs = gids
  317. c.localUser = lu
  318. c.acceptEnv = acceptEnv
  319. }
  320. for {
  321. switch {
  322. case action.Accept:
  323. metricTerminalAccept.Add(1)
  324. if action.Message != "" {
  325. if err := c.spac.SendAuthBanner(action.Message); err != nil {
  326. return nil, c.errUnexpected(fmt.Errorf("error sending auth welcome message: %w", err))
  327. }
  328. }
  329. c.finalAction = action
  330. return &gossh.Permissions{}, nil
  331. case action.Reject:
  332. metricTerminalReject.Add(1)
  333. c.finalAction = action
  334. return nil, c.errDenied(action.Message)
  335. case action.HoldAndDelegate != "":
  336. if action.Message != "" {
  337. if err := c.spac.SendAuthBanner(action.Message); err != nil {
  338. return nil, c.errUnexpected(fmt.Errorf("error sending hold and delegate message: %w", err))
  339. }
  340. }
  341. url := action.HoldAndDelegate
  342. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
  343. defer cancel()
  344. metricHolds.Add(1)
  345. url = c.expandDelegateURLLocked(url)
  346. var err error
  347. action, err = c.fetchSSHAction(ctx, url)
  348. if err != nil {
  349. metricTerminalFetchError.Add(1)
  350. return nil, c.errBanner("failed to fetch next SSH action", fmt.Errorf("fetch failed from %s: %w", url, err))
  351. }
  352. default:
  353. metricTerminalMalformed.Add(1)
  354. return nil, c.errBanner("reached Action that had neither Accept, Reject, nor HoldAndDelegate", nil)
  355. }
  356. }
  357. }
  358. // ServerConfig implements ssh.ServerConfigCallback.
  359. func (c *conn) ServerConfig(ctx ssh.Context) *gossh.ServerConfig {
  360. return &gossh.ServerConfig{
  361. PreAuthConnCallback: func(spac gossh.ServerPreAuthConn) {
  362. c.spac = spac
  363. },
  364. NoClientAuth: true, // required for the NoClientAuthCallback to run
  365. NoClientAuthCallback: func(cm gossh.ConnMetadata) (*gossh.Permissions, error) {
  366. // First perform client authentication, which can potentially
  367. // involve multiple steps (for example prompting user to log in to
  368. // Tailscale admin panel to confirm identity).
  369. perms, err := c.clientAuth(cm)
  370. if err != nil {
  371. return nil, err
  372. }
  373. // Authentication succeeded. Buggy SSH clients get confused by
  374. // success from the "none" auth method. As a workaround, let users
  375. // specify a username ending in "+password" to force password auth.
  376. // The actual value of the password doesn't matter.
  377. if strings.HasSuffix(cm.User(), forcePasswordSuffix) {
  378. return nil, &gossh.PartialSuccessError{
  379. Next: gossh.ServerAuthCallbacks{
  380. PasswordCallback: func(_ gossh.ConnMetadata, password []byte) (*gossh.Permissions, error) {
  381. return &gossh.Permissions{}, nil
  382. },
  383. },
  384. }
  385. }
  386. return perms, nil
  387. },
  388. PasswordCallback: func(cm gossh.ConnMetadata, pword []byte) (*gossh.Permissions, error) {
  389. // Some clients don't request 'none' authentication. Instead, they
  390. // immediately supply a password. We humor them by accepting the
  391. // password, but authenticate as usual, ignoring the actual value of
  392. // the password.
  393. return c.clientAuth(cm)
  394. },
  395. PublicKeyCallback: func(cm gossh.ConnMetadata, key gossh.PublicKey) (*gossh.Permissions, error) {
  396. // Some clients don't request 'none' authentication. Instead, they
  397. // immediately supply a public key. We humor them by accepting the
  398. // key, but authenticate as usual, ignoring the actual content of
  399. // the key.
  400. return c.clientAuth(cm)
  401. },
  402. }
  403. }
  404. func (srv *server) newConn() (*conn, error) {
  405. srv.mu.Lock()
  406. if srv.shutdownCalled {
  407. srv.mu.Unlock()
  408. // Stop accepting new connections.
  409. // Connections in the auth phase are handled in handleConnPostSSHAuth.
  410. // Existing sessions are terminated by Shutdown.
  411. return nil, errors.New("server is shutting down")
  412. }
  413. srv.mu.Unlock()
  414. c := &conn{srv: srv}
  415. now := srv.now()
  416. c.connID = fmt.Sprintf("ssh-conn-%s-%02x", now.UTC().Format("20060102T150405"), randBytes(5))
  417. fwdHandler := &ssh.ForwardedTCPHandler{}
  418. c.Server = &ssh.Server{
  419. Version: "Tailscale",
  420. ServerConfigCallback: c.ServerConfig,
  421. Handler: c.handleSessionPostSSHAuth,
  422. LocalPortForwardingCallback: c.mayForwardLocalPortTo,
  423. ReversePortForwardingCallback: c.mayReversePortForwardTo,
  424. SubsystemHandlers: map[string]ssh.SubsystemHandler{
  425. "sftp": c.handleSessionPostSSHAuth,
  426. },
  427. // Note: the direct-tcpip channel handler and LocalPortForwardingCallback
  428. // only adds support for forwarding ports from the local machine.
  429. // TODO(maisem/bradfitz): add remote port forwarding support.
  430. ChannelHandlers: map[string]ssh.ChannelHandler{
  431. "direct-tcpip": ssh.DirectTCPIPHandler,
  432. },
  433. RequestHandlers: map[string]ssh.RequestHandler{
  434. "tcpip-forward": fwdHandler.HandleSSHRequest,
  435. "cancel-tcpip-forward": fwdHandler.HandleSSHRequest,
  436. },
  437. }
  438. ss := c.Server
  439. for k, v := range ssh.DefaultRequestHandlers {
  440. ss.RequestHandlers[k] = v
  441. }
  442. for k, v := range ssh.DefaultChannelHandlers {
  443. ss.ChannelHandlers[k] = v
  444. }
  445. for k, v := range ssh.DefaultSubsystemHandlers {
  446. ss.SubsystemHandlers[k] = v
  447. }
  448. keys, err := srv.lb.GetSSH_HostKeys()
  449. if err != nil {
  450. return nil, err
  451. }
  452. for _, signer := range keys {
  453. ss.AddHostKey(signer)
  454. }
  455. return c, nil
  456. }
  457. // mayReversePortPortForwardTo reports whether the ctx should be allowed to port forward
  458. // to the specified host and port.
  459. // TODO(bradfitz/maisem): should we have more checks on host/port?
  460. func (c *conn) mayReversePortForwardTo(ctx ssh.Context, destinationHost string, destinationPort uint32) bool {
  461. if sshDisableForwarding() {
  462. return false
  463. }
  464. if c.finalAction != nil && c.finalAction.AllowRemotePortForwarding {
  465. metricRemotePortForward.Add(1)
  466. return true
  467. }
  468. return false
  469. }
  470. // mayForwardLocalPortTo reports whether the ctx should be allowed to port forward
  471. // to the specified host and port.
  472. // TODO(bradfitz/maisem): should we have more checks on host/port?
  473. func (c *conn) mayForwardLocalPortTo(ctx ssh.Context, destinationHost string, destinationPort uint32) bool {
  474. if sshDisableForwarding() {
  475. return false
  476. }
  477. if c.finalAction != nil && c.finalAction.AllowLocalPortForwarding {
  478. metricLocalPortForward.Add(1)
  479. return true
  480. }
  481. return false
  482. }
  483. // sshPolicy returns the SSHPolicy for current node.
  484. // If there is no SSHPolicy in the netmap, it returns a debugPolicy
  485. // if one is defined.
  486. func (c *conn) sshPolicy() (_ *tailcfg.SSHPolicy, ok bool) {
  487. lb := c.srv.lb
  488. if !lb.ShouldRunSSH() {
  489. return nil, false
  490. }
  491. nm := lb.NetMap()
  492. if nm == nil {
  493. return nil, false
  494. }
  495. if pol := nm.SSHPolicy; pol != nil && !envknob.SSHIgnoreTailnetPolicy() {
  496. return pol, true
  497. }
  498. debugPolicyFile := envknob.SSHPolicyFile()
  499. if debugPolicyFile != "" {
  500. c.logf("reading debug SSH policy file: %v", debugPolicyFile)
  501. f, err := os.ReadFile(debugPolicyFile)
  502. if err != nil {
  503. c.logf("error reading debug SSH policy file: %v", err)
  504. return nil, false
  505. }
  506. p := new(tailcfg.SSHPolicy)
  507. if err := json.Unmarshal(f, p); err != nil {
  508. c.logf("invalid JSON in %v: %v", debugPolicyFile, err)
  509. return nil, false
  510. }
  511. return p, true
  512. }
  513. return nil, false
  514. }
  515. func toIPPort(a net.Addr) (ipp netip.AddrPort) {
  516. ta, ok := a.(*net.TCPAddr)
  517. if !ok {
  518. return
  519. }
  520. tanetaddr, ok := netip.AddrFromSlice(ta.IP)
  521. if !ok {
  522. return
  523. }
  524. return netip.AddrPortFrom(tanetaddr.Unmap(), uint16(ta.Port))
  525. }
  526. // connInfo populates the sshConnInfo from the provided arguments,
  527. // validating only that they represent a known Tailscale identity.
  528. func (c *conn) setInfo(cm gossh.ConnMetadata) error {
  529. if c.info != nil {
  530. return nil
  531. }
  532. ci := &sshConnInfo{
  533. sshUser: strings.TrimSuffix(cm.User(), forcePasswordSuffix),
  534. src: toIPPort(cm.RemoteAddr()),
  535. dst: toIPPort(cm.LocalAddr()),
  536. }
  537. if !tsaddr.IsTailscaleIP(ci.dst.Addr()) {
  538. return fmt.Errorf("tailssh: rejecting non-Tailscale local address %v", ci.dst)
  539. }
  540. if !tsaddr.IsTailscaleIP(ci.src.Addr()) {
  541. return fmt.Errorf("tailssh: rejecting non-Tailscale remote address %v", ci.src)
  542. }
  543. node, uprof, ok := c.srv.lb.WhoIs("tcp", ci.src)
  544. if !ok {
  545. return fmt.Errorf("unknown Tailscale identity from src %v", ci.src)
  546. }
  547. ci.node = node
  548. ci.uprof = uprof
  549. c.idH = string(cm.SessionID())
  550. c.info = ci
  551. c.logf("handling conn: %v", ci.String())
  552. return nil
  553. }
  554. type evalResult string
  555. const (
  556. noPolicy evalResult = "no policy"
  557. rejected evalResult = "rejected"
  558. rejectedUser evalResult = "rejected user"
  559. accepted evalResult = "accept"
  560. )
  561. // evaluatePolicy returns the SSHAction and localUser after evaluating
  562. // the SSHPolicy for this conn.
  563. func (c *conn) evaluatePolicy() (_ *tailcfg.SSHAction, localUser string, acceptEnv []string, result evalResult) {
  564. pol, ok := c.sshPolicy()
  565. if !ok {
  566. return nil, "", nil, noPolicy
  567. }
  568. return c.evalSSHPolicy(pol)
  569. }
  570. // handleSessionPostSSHAuth runs an SSH session after the SSH-level authentication,
  571. // but not necessarily before all the Tailscale-level extra verification has
  572. // completed. It also handles SFTP requests.
  573. func (c *conn) handleSessionPostSSHAuth(s ssh.Session) {
  574. // Do this check after auth, but before starting the session.
  575. switch s.Subsystem() {
  576. case "sftp":
  577. if sshDisableSFTP() {
  578. fmt.Fprintf(s.Stderr(), "sftp disabled\r\n")
  579. s.Exit(1)
  580. return
  581. }
  582. metricSFTP.Add(1)
  583. case "":
  584. // Regular SSH session.
  585. default:
  586. fmt.Fprintf(s.Stderr(), "Unsupported subsystem %q\r\n", s.Subsystem())
  587. s.Exit(1)
  588. return
  589. }
  590. ss := c.newSSHSession(s)
  591. ss.logf("handling new SSH connection from %v (%v) to ssh-user %q", c.info.uprof.LoginName, c.info.src.Addr(), c.localUser.Username)
  592. ss.logf("access granted to %v as ssh-user %q", c.info.uprof.LoginName, c.localUser.Username)
  593. if f, ok := hookSSHLoginSuccess.GetOk(); ok {
  594. f(c.srv.logf, c)
  595. }
  596. ss.run()
  597. }
  598. func (c *conn) expandDelegateURLLocked(actionURL string) string {
  599. nm := c.srv.lb.NetMap()
  600. ci := c.info
  601. lu := c.localUser
  602. var dstNodeID string
  603. if nm != nil {
  604. dstNodeID = fmt.Sprint(int64(nm.SelfNode.ID()))
  605. }
  606. return strings.NewReplacer(
  607. "$SRC_NODE_IP", url.QueryEscape(ci.src.Addr().String()),
  608. "$SRC_NODE_ID", fmt.Sprint(int64(ci.node.ID())),
  609. "$DST_NODE_IP", url.QueryEscape(ci.dst.Addr().String()),
  610. "$DST_NODE_ID", dstNodeID,
  611. "$SSH_USER", url.QueryEscape(ci.sshUser),
  612. "$LOCAL_USER", url.QueryEscape(lu.Username),
  613. ).Replace(actionURL)
  614. }
  615. // sshSession is an accepted Tailscale SSH session.
  616. type sshSession struct {
  617. ssh.Session
  618. sharedID string // ID that's shared with control
  619. logf logger.Logf
  620. ctx context.Context
  621. cancelCtx context.CancelCauseFunc
  622. conn *conn
  623. agentListener net.Listener // non-nil if agent-forwarding requested+allowed
  624. // initialized by launchProcess:
  625. cmd *exec.Cmd
  626. wrStdin io.WriteCloser
  627. rdStdout io.ReadCloser
  628. rdStderr io.ReadCloser // rdStderr is nil for pty sessions
  629. ptyReq *ssh.Pty // non-nil for pty sessions
  630. // childPipes is a list of pipes that need to be closed when the process exits.
  631. // For pty sessions, this is the tty fd.
  632. // For non-pty sessions, this is the stdin, stdout, stderr fds.
  633. childPipes []io.Closer
  634. // We use this sync.Once to ensure that we only terminate the process once,
  635. // either it exits itself or is terminated
  636. exitOnce sync.Once
  637. }
  638. func (ss *sshSession) vlogf(format string, args ...any) {
  639. if sshVerboseLogging() {
  640. ss.logf(format, args...)
  641. }
  642. }
  643. func (c *conn) newSSHSession(s ssh.Session) *sshSession {
  644. sharedID := fmt.Sprintf("sess-%s-%02x", c.srv.now().UTC().Format("20060102T150405"), randBytes(5))
  645. c.logf("starting session: %v", sharedID)
  646. ctx, cancel := context.WithCancelCause(s.Context())
  647. return &sshSession{
  648. Session: s,
  649. sharedID: sharedID,
  650. ctx: ctx,
  651. cancelCtx: cancel,
  652. conn: c,
  653. logf: logger.WithPrefix(c.srv.logf, "ssh-session("+sharedID+"): "),
  654. }
  655. }
  656. // isStillValid reports whether the conn is still valid.
  657. func (c *conn) isStillValid() bool {
  658. a, localUser, _, result := c.evaluatePolicy()
  659. c.vlogf("stillValid: %+v %v %v", a, localUser, result)
  660. if result != accepted {
  661. return false
  662. }
  663. if !a.Accept && a.HoldAndDelegate == "" {
  664. return false
  665. }
  666. return c.localUser.Username == localUser
  667. }
  668. // checkStillValid checks that the conn is still valid per the latest SSHPolicy.
  669. // If not, it terminates all sessions associated with the conn.
  670. func (c *conn) checkStillValid() {
  671. if c.isStillValid() {
  672. return
  673. }
  674. metricPolicyChangeKick.Add(1)
  675. c.logf("session no longer valid per new SSH policy; closing")
  676. c.mu.Lock()
  677. defer c.mu.Unlock()
  678. for _, s := range c.sessions {
  679. s.cancelCtx(userVisibleError{
  680. fmt.Sprintf("Access revoked.\r\n"),
  681. context.Canceled,
  682. })
  683. }
  684. }
  685. func (c *conn) fetchSSHAction(ctx context.Context, url string) (*tailcfg.SSHAction, error) {
  686. ctx, cancel := context.WithTimeout(ctx, 30*time.Minute)
  687. defer cancel()
  688. bo := backoff.NewBackoff("fetch-ssh-action", c.logf, 10*time.Second)
  689. for {
  690. if err := ctx.Err(); err != nil {
  691. return nil, err
  692. }
  693. req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
  694. if err != nil {
  695. return nil, err
  696. }
  697. res, err := c.srv.lb.DoNoiseRequest(req)
  698. if err != nil {
  699. bo.BackOff(ctx, err)
  700. continue
  701. }
  702. if res.StatusCode != 200 {
  703. body, _ := io.ReadAll(res.Body)
  704. res.Body.Close()
  705. if len(body) > 1<<10 {
  706. body = body[:1<<10]
  707. }
  708. c.logf("fetch of %v: %s, %s", url, res.Status, body)
  709. bo.BackOff(ctx, fmt.Errorf("unexpected status: %v", res.Status))
  710. continue
  711. }
  712. a := new(tailcfg.SSHAction)
  713. err = json.NewDecoder(res.Body).Decode(a)
  714. res.Body.Close()
  715. if err != nil {
  716. c.logf("invalid next SSHAction JSON from %v: %v", url, err)
  717. bo.BackOff(ctx, err)
  718. continue
  719. }
  720. return a, nil
  721. }
  722. }
  723. // killProcessOnContextDone waits for ss.ctx to be done and kills the process,
  724. // unless the process has already exited.
  725. func (ss *sshSession) killProcessOnContextDone() {
  726. <-ss.ctx.Done()
  727. // Either the process has already exited, in which case this does nothing.
  728. // Or, the process is still running in which case this will kill it.
  729. ss.exitOnce.Do(func() {
  730. err := context.Cause(ss.ctx)
  731. if serr, ok := err.(SSHTerminationError); ok {
  732. msg := serr.SSHTerminationMessage()
  733. if msg != "" {
  734. io.WriteString(ss.Stderr(), "\r\n\r\n"+msg+"\r\n\r\n")
  735. }
  736. }
  737. ss.logf("terminating SSH session from %v: %v", ss.conn.info.src.Addr(), err)
  738. // We don't need to Process.Wait here, sshSession.run() does
  739. // the waiting regardless of termination reason.
  740. // TODO(maisem): should this be a SIGTERM followed by a SIGKILL?
  741. ss.cmd.Process.Kill()
  742. })
  743. }
  744. // attachSession registers ss as an active session.
  745. func (c *conn) attachSession(ss *sshSession) {
  746. c.srv.sessionWaitGroup.Add(1)
  747. if ss.sharedID == "" {
  748. panic("empty sharedID")
  749. }
  750. c.mu.Lock()
  751. defer c.mu.Unlock()
  752. c.sessions = append(c.sessions, ss)
  753. }
  754. // detachSession unregisters s from the list of active sessions.
  755. func (c *conn) detachSession(ss *sshSession) {
  756. defer c.srv.sessionWaitGroup.Done()
  757. c.mu.Lock()
  758. defer c.mu.Unlock()
  759. for i, s := range c.sessions {
  760. if s == ss {
  761. c.sessions = append(c.sessions[:i], c.sessions[i+1:]...)
  762. break
  763. }
  764. }
  765. }
  766. var errSessionDone = errors.New("session is done")
  767. // handleSSHAgentForwarding starts a Unix socket listener and in the background
  768. // forwards agent connections between the listener and the ssh.Session.
  769. // On success, it assigns ss.agentListener.
  770. func (ss *sshSession) handleSSHAgentForwarding(s ssh.Session, lu *userMeta) error {
  771. if !ssh.AgentRequested(ss) || !ss.conn.finalAction.AllowAgentForwarding {
  772. return nil
  773. }
  774. if sshDisableForwarding() {
  775. // TODO(bradfitz): or do we want to return an error here instead so the user
  776. // gets an error if they ran with ssh -A? But for now we just silently
  777. // don't work, like the condition above.
  778. return nil
  779. }
  780. ss.logf("ssh: agent forwarding requested")
  781. ln, err := ssh.NewAgentListener()
  782. if err != nil {
  783. return err
  784. }
  785. defer func() {
  786. if err != nil && ln != nil {
  787. ln.Close()
  788. }
  789. }()
  790. uid, err := strconv.ParseUint(lu.Uid, 10, 32)
  791. if err != nil {
  792. return err
  793. }
  794. gid, err := strconv.ParseUint(lu.Gid, 10, 32)
  795. if err != nil {
  796. return err
  797. }
  798. socket := ln.Addr().String()
  799. dir := filepath.Dir(socket)
  800. // Make sure the socket is accessible only by the user.
  801. if err := os.Chmod(socket, 0600); err != nil {
  802. return err
  803. }
  804. if err := os.Chown(socket, int(uid), int(gid)); err != nil {
  805. return err
  806. }
  807. // Make sure the dir is also accessible.
  808. if err := os.Chmod(dir, 0755); err != nil {
  809. return err
  810. }
  811. go ssh.ForwardAgentConnections(ln, s)
  812. ss.agentListener = ln
  813. return nil
  814. }
  815. // run is the entrypoint for a newly accepted SSH session.
  816. //
  817. // It handles ss once it's been accepted and determined
  818. // that it should run.
  819. func (ss *sshSession) run() {
  820. metricActiveSessions.Add(1)
  821. defer metricActiveSessions.Add(-1)
  822. defer ss.cancelCtx(errSessionDone)
  823. if attached := ss.conn.srv.attachSessionToConnIfNotShutdown(ss); !attached {
  824. fmt.Fprintf(ss, "Tailscale SSH is shutting down\r\n")
  825. ss.Exit(1)
  826. return
  827. }
  828. defer ss.conn.detachSession(ss)
  829. lu := ss.conn.localUser
  830. logf := ss.logf
  831. if ss.conn.finalAction.SessionDuration != 0 {
  832. t := time.AfterFunc(ss.conn.finalAction.SessionDuration, func() {
  833. ss.cancelCtx(userVisibleError{
  834. fmt.Sprintf("Session timeout of %v elapsed.", ss.conn.finalAction.SessionDuration),
  835. context.DeadlineExceeded,
  836. })
  837. })
  838. defer t.Stop()
  839. }
  840. if euid := os.Geteuid(); euid != 0 && runtime.GOOS != "plan9" {
  841. if lu.Uid != fmt.Sprint(euid) {
  842. ss.logf("can't switch to user %q from process euid %v", lu.Username, euid)
  843. fmt.Fprintf(ss, "can't switch user\r\n")
  844. ss.Exit(1)
  845. return
  846. }
  847. }
  848. // Take control of the PTY so that we can configure it below.
  849. // See https://github.com/tailscale/tailscale/issues/4146
  850. ss.DisablePTYEmulation()
  851. var rec *recording // or nil if disabled
  852. if ss.Subsystem() != "sftp" {
  853. if err := ss.handleSSHAgentForwarding(ss, lu); err != nil {
  854. ss.logf("agent forwarding failed: %v", err)
  855. } else if ss.agentListener != nil {
  856. // TODO(maisem/bradfitz): add a way to close all session resources
  857. defer ss.agentListener.Close()
  858. }
  859. if ss.shouldRecord() {
  860. var err error
  861. rec, err = ss.startNewRecording()
  862. if err != nil {
  863. var uve userVisibleError
  864. if errors.As(err, &uve) {
  865. fmt.Fprintf(ss, "%s\r\n", uve.SSHTerminationMessage())
  866. } else {
  867. fmt.Fprintf(ss, "can't start new recording\r\n")
  868. }
  869. ss.logf("startNewRecording: %v", err)
  870. ss.Exit(1)
  871. return
  872. }
  873. ss.logf("startNewRecording: <nil>")
  874. if rec != nil {
  875. defer rec.Close()
  876. }
  877. }
  878. }
  879. err := ss.launchProcess()
  880. if err != nil {
  881. logf("start failed: %v", err.Error())
  882. if errors.Is(err, context.Canceled) {
  883. err := context.Cause(ss.ctx)
  884. var uve userVisibleError
  885. if errors.As(err, &uve) {
  886. fmt.Fprintf(ss, "%s\r\n", uve)
  887. }
  888. }
  889. ss.Exit(1)
  890. return
  891. }
  892. go ss.killProcessOnContextDone()
  893. var processDone atomic.Bool
  894. go func() {
  895. defer ss.wrStdin.Close()
  896. if _, err := io.Copy(rec.writer("i", ss.wrStdin), ss); err != nil {
  897. logf("stdin copy: %v", err)
  898. ss.cancelCtx(err)
  899. }
  900. }()
  901. outputDone := make(chan struct{})
  902. var openOutputStreams atomic.Int32
  903. if ss.rdStderr != nil {
  904. openOutputStreams.Store(2)
  905. } else {
  906. openOutputStreams.Store(1)
  907. }
  908. go func() {
  909. defer ss.rdStdout.Close()
  910. _, err := io.Copy(rec.writer("o", ss), ss.rdStdout)
  911. if err != nil && !errors.Is(err, io.EOF) {
  912. isErrBecauseProcessExited := processDone.Load() && errors.Is(err, syscall.EIO)
  913. if !isErrBecauseProcessExited {
  914. logf("stdout copy: %v", err)
  915. ss.cancelCtx(err)
  916. }
  917. }
  918. if openOutputStreams.Add(-1) == 0 {
  919. ss.CloseWrite()
  920. close(outputDone)
  921. }
  922. }()
  923. // rdStderr is nil for ptys.
  924. if ss.rdStderr != nil {
  925. go func() {
  926. defer ss.rdStderr.Close()
  927. _, err := io.Copy(ss.Stderr(), ss.rdStderr)
  928. if err != nil {
  929. logf("stderr copy: %v", err)
  930. }
  931. if openOutputStreams.Add(-1) == 0 {
  932. ss.CloseWrite()
  933. close(outputDone)
  934. }
  935. }()
  936. }
  937. err = ss.cmd.Wait()
  938. processDone.Store(true)
  939. // This will either make the SSH Termination goroutine be a no-op,
  940. // or itself will be a no-op because the process was killed by the
  941. // aforementioned goroutine.
  942. ss.exitOnce.Do(func() {})
  943. // Close the process-side of all pipes to signal the asynchronous
  944. // io.Copy routines reading/writing from the pipes to terminate.
  945. // Block for the io.Copy to finish before calling ss.Exit below.
  946. closeAll(ss.childPipes...)
  947. select {
  948. case <-outputDone:
  949. case <-ss.ctx.Done():
  950. }
  951. if err == nil {
  952. ss.logf("Session complete")
  953. ss.Exit(0)
  954. return
  955. }
  956. if ee, ok := err.(*exec.ExitError); ok {
  957. code := ee.ProcessState.ExitCode()
  958. ss.logf("Wait: code=%v", code)
  959. ss.Exit(code)
  960. return
  961. }
  962. ss.logf("Wait: %v", err)
  963. ss.Exit(1)
  964. return
  965. }
  966. // recordSSHToLocalDisk is a deprecated dev knob to allow recording SSH sessions
  967. // to local storage. It is only used if there is no recording configured by the
  968. // coordination server. This will be removed in the future.
  969. var recordSSHToLocalDisk = envknob.RegisterBool("TS_DEBUG_LOG_SSH")
  970. // recorders returns the list of recorders to use for this session.
  971. // If the final action has a non-empty list of recorders, that list is
  972. // returned. Otherwise, the list of recorders from the initial action
  973. // is returned.
  974. func (ss *sshSession) recorders() ([]netip.AddrPort, *tailcfg.SSHRecorderFailureAction) {
  975. if len(ss.conn.finalAction.Recorders) > 0 {
  976. return ss.conn.finalAction.Recorders, ss.conn.finalAction.OnRecordingFailure
  977. }
  978. return ss.conn.action0.Recorders, ss.conn.action0.OnRecordingFailure
  979. }
  980. func (ss *sshSession) shouldRecord() bool {
  981. recs, _ := ss.recorders()
  982. return len(recs) > 0 || recordSSHToLocalDisk()
  983. }
  984. type sshConnInfo struct {
  985. // sshUser is the requested local SSH username ("root", "alice", etc).
  986. sshUser string
  987. // src is the Tailscale IP and port that the connection came from.
  988. src netip.AddrPort
  989. // dst is the Tailscale IP and port that the connection came for.
  990. dst netip.AddrPort
  991. // node is srcIP's node.
  992. node tailcfg.NodeView
  993. // uprof is node's UserProfile.
  994. uprof tailcfg.UserProfile
  995. }
  996. func (ci *sshConnInfo) String() string {
  997. return fmt.Sprintf("%v->%v@%v", ci.src, ci.sshUser, ci.dst)
  998. }
  999. func (c *conn) ruleExpired(r *tailcfg.SSHRule) bool {
  1000. if r.RuleExpires == nil {
  1001. return false
  1002. }
  1003. return r.RuleExpires.Before(c.srv.now())
  1004. }
  1005. func (c *conn) evalSSHPolicy(pol *tailcfg.SSHPolicy) (a *tailcfg.SSHAction, localUser string, acceptEnv []string, result evalResult) {
  1006. failedOnUser := false
  1007. for _, r := range pol.Rules {
  1008. if a, localUser, acceptEnv, err := c.matchRule(r); err == nil {
  1009. return a, localUser, acceptEnv, accepted
  1010. } else if errors.Is(err, errUserMatch) {
  1011. failedOnUser = true
  1012. }
  1013. }
  1014. result = rejected
  1015. if failedOnUser {
  1016. result = rejectedUser
  1017. }
  1018. return nil, "", nil, result
  1019. }
  1020. // internal errors for testing; they don't escape to callers or logs.
  1021. var (
  1022. errNilRule = errors.New("nil rule")
  1023. errNilAction = errors.New("nil action")
  1024. errRuleExpired = errors.New("rule expired")
  1025. errPrincipalMatch = errors.New("principal didn't match")
  1026. errUserMatch = errors.New("user didn't match")
  1027. errInvalidConn = errors.New("invalid connection state")
  1028. )
  1029. func (c *conn) matchRule(r *tailcfg.SSHRule) (a *tailcfg.SSHAction, localUser string, acceptEnv []string, err error) {
  1030. defer func() {
  1031. c.vlogf("matchRule(%+v): %v", r, err)
  1032. }()
  1033. if c == nil {
  1034. return nil, "", nil, errInvalidConn
  1035. }
  1036. if c.info == nil {
  1037. c.logf("invalid connection state")
  1038. return nil, "", nil, errInvalidConn
  1039. }
  1040. if r == nil {
  1041. return nil, "", nil, errNilRule
  1042. }
  1043. if r.Action == nil {
  1044. return nil, "", nil, errNilAction
  1045. }
  1046. if c.ruleExpired(r) {
  1047. return nil, "", nil, errRuleExpired
  1048. }
  1049. if !c.anyPrincipalMatches(r.Principals) {
  1050. return nil, "", nil, errPrincipalMatch
  1051. }
  1052. if !r.Action.Reject {
  1053. // For all but Reject rules, SSHUsers is required.
  1054. // If SSHUsers is nil or empty, mapLocalUser will return an
  1055. // empty string anyway.
  1056. localUser = mapLocalUser(r.SSHUsers, c.info.sshUser)
  1057. if localUser == "" {
  1058. return nil, "", nil, errUserMatch
  1059. }
  1060. }
  1061. return r.Action, localUser, r.AcceptEnv, nil
  1062. }
  1063. func mapLocalUser(ruleSSHUsers map[string]string, reqSSHUser string) (localUser string) {
  1064. v, ok := ruleSSHUsers[reqSSHUser]
  1065. if !ok {
  1066. v = ruleSSHUsers["*"]
  1067. }
  1068. if v == "=" {
  1069. return reqSSHUser
  1070. }
  1071. return v
  1072. }
  1073. func (c *conn) anyPrincipalMatches(ps []*tailcfg.SSHPrincipal) bool {
  1074. for _, p := range ps {
  1075. if p == nil {
  1076. continue
  1077. }
  1078. if c.principalMatchesTailscaleIdentity(p) {
  1079. return true
  1080. }
  1081. }
  1082. return false
  1083. }
  1084. // principalMatchesTailscaleIdentity reports whether one of p's four fields
  1085. // that match the Tailscale identity match (Node, NodeIP, UserLogin, Any).
  1086. func (c *conn) principalMatchesTailscaleIdentity(p *tailcfg.SSHPrincipal) bool {
  1087. ci := c.info
  1088. if p.Any {
  1089. return true
  1090. }
  1091. if !p.Node.IsZero() && ci.node.Valid() && p.Node == ci.node.StableID() {
  1092. return true
  1093. }
  1094. if p.NodeIP != "" {
  1095. if ip, _ := netip.ParseAddr(p.NodeIP); ip == ci.src.Addr() {
  1096. return true
  1097. }
  1098. }
  1099. if p.UserLogin != "" && ci.uprof.LoginName == p.UserLogin {
  1100. return true
  1101. }
  1102. return false
  1103. }
  1104. func randBytes(n int) []byte {
  1105. b := make([]byte, n)
  1106. if _, err := rand.Read(b); err != nil {
  1107. panic(err)
  1108. }
  1109. return b
  1110. }
  1111. func (ss *sshSession) openFileForRecording(now time.Time) (_ io.WriteCloser, err error) {
  1112. varRoot := ss.conn.srv.lb.TailscaleVarRoot()
  1113. if varRoot == "" {
  1114. return nil, errors.New("no var root for recording storage")
  1115. }
  1116. dir := filepath.Join(varRoot, "ssh-sessions")
  1117. if err := os.MkdirAll(dir, 0700); err != nil {
  1118. return nil, err
  1119. }
  1120. f, err := os.CreateTemp(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
  1121. if err != nil {
  1122. return nil, err
  1123. }
  1124. return f, nil
  1125. }
  1126. // startNewRecording starts a new SSH session recording.
  1127. // It may return a nil recording if recording is not available.
  1128. func (ss *sshSession) startNewRecording() (_ *recording, err error) {
  1129. // We store the node key as soon as possible when creating
  1130. // a new recording incase of FUS.
  1131. nodeKey := ss.conn.srv.lb.NodeKey()
  1132. if nodeKey.IsZero() {
  1133. return nil, errors.New("ssh server is unavailable: no node key")
  1134. }
  1135. recorders, onFailure := ss.recorders()
  1136. var localRecording bool
  1137. if len(recorders) == 0 {
  1138. if recordSSHToLocalDisk() {
  1139. localRecording = true
  1140. } else {
  1141. return nil, errors.New("no recorders configured")
  1142. }
  1143. }
  1144. var w ssh.Window
  1145. if ptyReq, _, isPtyReq := ss.Pty(); isPtyReq {
  1146. w = ptyReq.Window
  1147. }
  1148. term := envValFromList(ss.Environ(), "TERM")
  1149. if term == "" {
  1150. term = "xterm-256color" // something non-empty
  1151. }
  1152. now := time.Now()
  1153. rec := &recording{
  1154. ss: ss,
  1155. start: now,
  1156. failOpen: onFailure == nil || onFailure.TerminateSessionWithMessage == "",
  1157. }
  1158. // We want to use a background context for uploading and not ss.ctx.
  1159. // ss.ctx is closed when the session closes, but we don't want to break the upload at that time.
  1160. // Instead we want to wait for the session to close the writer when it finishes.
  1161. ctx := context.Background()
  1162. if localRecording {
  1163. rec.out, err = ss.openFileForRecording(now)
  1164. if err != nil {
  1165. return nil, err
  1166. }
  1167. } else {
  1168. var errChan <-chan error
  1169. var attempts []*tailcfg.SSHRecordingAttempt
  1170. rec.out, attempts, errChan, err = sessionrecording.ConnectToRecorder(ctx, recorders, ss.conn.srv.lb.Dialer().UserDial)
  1171. if err != nil {
  1172. if onFailure != nil && onFailure.NotifyURL != "" && len(attempts) > 0 {
  1173. eventType := tailcfg.SSHSessionRecordingFailed
  1174. if onFailure.RejectSessionWithMessage != "" {
  1175. eventType = tailcfg.SSHSessionRecordingRejected
  1176. }
  1177. ss.notifyControl(ctx, nodeKey, eventType, attempts, onFailure.NotifyURL)
  1178. }
  1179. if onFailure != nil && onFailure.RejectSessionWithMessage != "" {
  1180. ss.logf("recording: error starting recording (rejecting session): %v", err)
  1181. return nil, userVisibleError{
  1182. error: err,
  1183. msg: onFailure.RejectSessionWithMessage,
  1184. }
  1185. }
  1186. ss.logf("recording: error starting recording (failing open): %v", err)
  1187. return nil, nil
  1188. }
  1189. go func() {
  1190. err := <-errChan
  1191. if err == nil {
  1192. select {
  1193. case <-ss.ctx.Done():
  1194. // Success.
  1195. ss.logf("recording: finished uploading recording")
  1196. return
  1197. default:
  1198. err = errors.New("recording upload ended before the SSH session")
  1199. }
  1200. }
  1201. if onFailure != nil && onFailure.NotifyURL != "" && len(attempts) > 0 {
  1202. lastAttempt := attempts[len(attempts)-1]
  1203. lastAttempt.FailureMessage = err.Error()
  1204. eventType := tailcfg.SSHSessionRecordingFailed
  1205. if onFailure.TerminateSessionWithMessage != "" {
  1206. eventType = tailcfg.SSHSessionRecordingTerminated
  1207. }
  1208. ss.notifyControl(ctx, nodeKey, eventType, attempts, onFailure.NotifyURL)
  1209. }
  1210. if onFailure != nil && onFailure.TerminateSessionWithMessage != "" {
  1211. ss.logf("recording: error uploading recording (closing session): %v", err)
  1212. ss.cancelCtx(userVisibleError{
  1213. error: err,
  1214. msg: onFailure.TerminateSessionWithMessage,
  1215. })
  1216. return
  1217. }
  1218. ss.logf("recording: error uploading recording (failing open): %v", err)
  1219. }()
  1220. }
  1221. ch := sessionrecording.CastHeader{
  1222. Version: 2,
  1223. Width: w.Width,
  1224. Height: w.Height,
  1225. Timestamp: now.Unix(),
  1226. Command: strings.Join(ss.Command(), " "),
  1227. Env: map[string]string{
  1228. "TERM": term,
  1229. // TODO(bradfitz): anything else important?
  1230. // including all seems noisey, but maybe we should
  1231. // for auditing. But first need to break
  1232. // launchProcess's startWithStdPipes and
  1233. // startWithPTY up so that they first return the cmd
  1234. // without starting it, and then a step that starts
  1235. // it. Then we can (1) make the cmd, (2) start the
  1236. // recording, (3) start the process.
  1237. },
  1238. SSHUser: ss.conn.info.sshUser,
  1239. LocalUser: ss.conn.localUser.Username,
  1240. SrcNode: strings.TrimSuffix(ss.conn.info.node.Name(), "."),
  1241. SrcNodeID: ss.conn.info.node.StableID(),
  1242. ConnectionID: ss.conn.connID,
  1243. }
  1244. if !ss.conn.info.node.IsTagged() {
  1245. ch.SrcNodeUser = ss.conn.info.uprof.LoginName
  1246. ch.SrcNodeUserID = ss.conn.info.node.User()
  1247. } else {
  1248. ch.SrcNodeTags = ss.conn.info.node.Tags().AsSlice()
  1249. }
  1250. j, err := json.Marshal(ch)
  1251. if err != nil {
  1252. return nil, err
  1253. }
  1254. j = append(j, '\n')
  1255. if _, err := rec.out.Write(j); err != nil {
  1256. if errors.Is(err, io.ErrClosedPipe) && ss.ctx.Err() != nil {
  1257. // If we got an io.ErrClosedPipe, it's likely because
  1258. // the recording server closed the connection on us. Return
  1259. // the original context error instead.
  1260. return nil, context.Cause(ss.ctx)
  1261. }
  1262. return nil, err
  1263. }
  1264. return rec, nil
  1265. }
  1266. // notifyControl sends a SSHEventNotifyRequest to control over noise.
  1267. // A SSHEventNotifyRequest is sent when an action or state reached during
  1268. // an SSH session is a defined EventType.
  1269. func (ss *sshSession) notifyControl(ctx context.Context, nodeKey key.NodePublic, notifyType tailcfg.SSHEventType, attempts []*tailcfg.SSHRecordingAttempt, url string) {
  1270. re := tailcfg.SSHEventNotifyRequest{
  1271. EventType: notifyType,
  1272. ConnectionID: ss.conn.connID,
  1273. CapVersion: tailcfg.CurrentCapabilityVersion,
  1274. NodeKey: nodeKey,
  1275. SrcNode: ss.conn.info.node.ID(),
  1276. SSHUser: ss.conn.info.sshUser,
  1277. LocalUser: ss.conn.localUser.Username,
  1278. RecordingAttempts: attempts,
  1279. }
  1280. body, err := json.Marshal(re)
  1281. if err != nil {
  1282. ss.logf("notifyControl: unable to marshal SSHNotifyRequest:", err)
  1283. return
  1284. }
  1285. req, err := http.NewRequestWithContext(ctx, httpm.POST, url, bytes.NewReader(body))
  1286. if err != nil {
  1287. ss.logf("notifyControl: unable to create request:", err)
  1288. return
  1289. }
  1290. resp, err := ss.conn.srv.lb.DoNoiseRequest(req)
  1291. if err != nil {
  1292. ss.logf("notifyControl: unable to send noise request:", err)
  1293. return
  1294. }
  1295. if resp.StatusCode != http.StatusCreated {
  1296. ss.logf("notifyControl: noise request returned status code %v", resp.StatusCode)
  1297. return
  1298. }
  1299. }
  1300. // recording is the state for an SSH session recording.
  1301. type recording struct {
  1302. ss *sshSession
  1303. start time.Time
  1304. // failOpen specifies whether the session should be allowed to
  1305. // continue if writing to the recording fails.
  1306. failOpen bool
  1307. mu sync.Mutex // guards writes to, close of out
  1308. out io.WriteCloser
  1309. }
  1310. func (r *recording) Close() error {
  1311. r.mu.Lock()
  1312. defer r.mu.Unlock()
  1313. if r.out == nil {
  1314. return nil
  1315. }
  1316. err := r.out.Close()
  1317. r.out = nil
  1318. return err
  1319. }
  1320. // writer returns an io.Writer around w that first records the write.
  1321. //
  1322. // The dir should be "i" for input or "o" for output.
  1323. //
  1324. // If r is nil, it returns w unchanged.
  1325. //
  1326. // Currently (2023-03-21) we only record output, not input.
  1327. func (r *recording) writer(dir string, w io.Writer) io.Writer {
  1328. if r == nil {
  1329. return w
  1330. }
  1331. if dir == "i" {
  1332. // TODO: record input? Maybe not, since it might contain
  1333. // passwords.
  1334. return w
  1335. }
  1336. return &loggingWriter{r: r, dir: dir, w: w}
  1337. }
  1338. // loggingWriter is an io.Writer wrapper that writes first an
  1339. // asciinema JSON cast format recording line, and then writes to w.
  1340. type loggingWriter struct {
  1341. r *recording
  1342. dir string // "i" or "o" (input or output)
  1343. w io.Writer // underlying Writer, after writing to r.out
  1344. // recordingFailedOpen specifies whether we've failed to write to
  1345. // r.out and should stop trying. It is set to true if we fail to write
  1346. // to r.out and r.failOpen is set.
  1347. recordingFailedOpen bool
  1348. }
  1349. func (w *loggingWriter) Write(p []byte) (n int, err error) {
  1350. if !w.recordingFailedOpen {
  1351. j, err := json.Marshal([]any{
  1352. time.Since(w.r.start).Seconds(),
  1353. w.dir,
  1354. string(p),
  1355. })
  1356. if err != nil {
  1357. return 0, err
  1358. }
  1359. j = append(j, '\n')
  1360. if err := w.writeCastLine(j); err != nil {
  1361. if !w.r.failOpen {
  1362. return 0, err
  1363. }
  1364. w.recordingFailedOpen = true
  1365. }
  1366. }
  1367. return w.w.Write(p)
  1368. }
  1369. func (w loggingWriter) writeCastLine(j []byte) error {
  1370. w.r.mu.Lock()
  1371. defer w.r.mu.Unlock()
  1372. if w.r.out == nil {
  1373. return errors.New("logger closed")
  1374. }
  1375. _, err := w.r.out.Write(j)
  1376. if err != nil {
  1377. return fmt.Errorf("logger Write: %w", err)
  1378. }
  1379. return nil
  1380. }
  1381. func envValFromList(env []string, wantKey string) (v string) {
  1382. for _, kv := range env {
  1383. if thisKey, v, ok := strings.Cut(kv, "="); ok && envEq(thisKey, wantKey) {
  1384. return v
  1385. }
  1386. }
  1387. return ""
  1388. }
  1389. // envEq reports whether environment variable a == b for the current
  1390. // operating system.
  1391. func envEq(a, b string) bool {
  1392. //lint:ignore SA4032 in case this func moves elsewhere, permit the GOOS check
  1393. if runtime.GOOS == "windows" {
  1394. return strings.EqualFold(a, b)
  1395. }
  1396. return a == b
  1397. }
  1398. var (
  1399. metricActiveSessions = clientmetric.NewGauge("ssh_active_sessions")
  1400. metricIncomingConnections = clientmetric.NewCounter("ssh_incoming_connections")
  1401. metricTerminalAccept = clientmetric.NewCounter("ssh_terminalaction_accept")
  1402. metricTerminalReject = clientmetric.NewCounter("ssh_terminalaction_reject")
  1403. metricTerminalMalformed = clientmetric.NewCounter("ssh_terminalaction_malformed")
  1404. metricTerminalFetchError = clientmetric.NewCounter("ssh_terminalaction_fetch_error")
  1405. metricHolds = clientmetric.NewCounter("ssh_holds")
  1406. metricPolicyChangeKick = clientmetric.NewCounter("ssh_policy_change_kick")
  1407. metricSFTP = clientmetric.NewCounter("ssh_sftp_sessions")
  1408. metricLocalPortForward = clientmetric.NewCounter("ssh_local_port_forward_requests")
  1409. metricRemotePortForward = clientmetric.NewCounter("ssh_remote_port_forward_requests")
  1410. )
  1411. // userVisibleError is a wrapper around an error that implements
  1412. // SSHTerminationError, so msg is written to their session.
  1413. type userVisibleError struct {
  1414. msg string
  1415. error
  1416. }
  1417. func (ue userVisibleError) SSHTerminationMessage() string { return ue.msg }
  1418. // SSHTerminationError is implemented by errors that terminate an SSH
  1419. // session and should be written to user's sessions.
  1420. type SSHTerminationError interface {
  1421. error
  1422. SSHTerminationMessage() string
  1423. }
  1424. func closeAll(cs ...io.Closer) {
  1425. for _, c := range cs {
  1426. if c != nil {
  1427. c.Close()
  1428. }
  1429. }
  1430. }