2
0

tailssh.go 46 KB

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