tailssh.go 39 KB

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