tailssh.go 45 KB

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