| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -10,7 +10,6 @@ import (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					"bytes"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					"context"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					"crypto/rand"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					"encoding/base64"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					"encoding/json"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					"errors"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					"fmt"
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -80,7 +79,6 @@ type server struct {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					logf           logger.Logf
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					tailscaledPath string
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					pubKeyHTTPClient *http.Client     // or nil for http.DefaultClient
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					timeNow func() time.Time // or nil for time.Now
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					sessionWaitGroup sync.WaitGroup
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -88,7 +86,6 @@ type server struct {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// mu protects the following
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mu             sync.Mutex
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					activeConns    map[*conn]bool // set; value is always true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					fetchPublicKeysCache map[string]pubKeyCacheEntry // by https URL
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					shutdownCalled bool
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -204,7 +201,6 @@ func (srv *server) OnPolicyChange() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// Do the user auth
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//   - NoClientAuthHandler
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//   - PublicKeyHandler (only if NoClientAuthHandler returns errPubKeyRequired)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// Once auth is done, the conn can be multiplexed with multiple sessions and
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// channels concurrently. At which point any of the following can be called
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -237,7 +233,6 @@ type conn struct {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					info         *sshConnInfo // set by setInfo
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					localUser    *userMeta    // set by doPolicyAuth
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					userGroupIDs []string     // set by doPolicyAuth
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					pubKey       gossh.PublicKey // set by doPolicyAuth
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// mu protects the following fields.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					//
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -267,9 +262,6 @@ func (c *conn) isAuthorized(ctx ssh.Context) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					action := c.currentAction
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if action.Accept {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							if c.pubKey != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								metricPublicKeyAccepts.Add(1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if action.Reject || action.HoldAndDelegate == "" {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -292,10 +284,6 @@ func (c *conn) isAuthorized(ctx ssh.Context) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// policy.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var errDenied = errors.New("ssh: access denied")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// errPubKeyRequired is returned by NoClientAuthCallback to make the client
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// resort to public-key auth; not user visible.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var errPubKeyRequired = errors.New("ssh publickey required")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// NoClientAuthCallback implements gossh.NoClientAuthCallback and is called by
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// the ssh.Server when the client first connects with the "none"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// authentication method.
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -304,13 +292,13 @@ var errPubKeyRequired = errors.New("ssh publickey required")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// starting it afresh). It returns an error if the policy evaluation fails, or
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// if the decision is "reject"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				//
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// It either returns nil (accept) or errPubKeyRequired or errDenied
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// (reject). The errors may be wrapped.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// It either returns nil (accept) or errDenied (reject). The errors may be
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// wrapped.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) NoClientAuthCallback(ctx ssh.Context) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if c.insecureSkipTailscaleAuth {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err := c.doPolicyAuth(ctx, nil /* no pub key */); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err := c.doPolicyAuth(ctx); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err := c.isAuthorized(ctx); err != nil {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -328,11 +316,8 @@ func (c *conn) NoClientAuthCallback(ctx ssh.Context) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) nextAuthMethodCallback(cm gossh.ConnMetadata, prevErrors []error) (nextMethod []string) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					switch {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					case c.anyPasswordIsOkay:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if c.anyPasswordIsOkay {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						nextMethod = append(nextMethod, "password")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					case len(prevErrors) > 0 && prevErrors[len(prevErrors)-1] == errPubKeyRequired:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						nextMethod = append(nextMethod, "publickey")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// The fake "tailscale" method is always appended to next so OpenSSH renders
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -352,41 +337,19 @@ func (c *conn) fakePasswordHandler(ctx ssh.Context, password string) bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return c.anyPasswordIsOkay
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// PublicKeyHandler implements ssh.PublicKeyHandler is called by the
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// ssh.Server when the client presents a public key.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) PublicKeyHandler(ctx ssh.Context, pubKey ssh.PublicKey) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err := c.doPolicyAuth(ctx, pubKey); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						// TODO(maisem/bradfitz): surface the error here.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						c.logf("rejecting SSH public key %s: %v", bytes.TrimSpace(gossh.MarshalAuthorizedKey(pubKey)), err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err := c.isAuthorized(ctx); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					c.logf("accepting SSH public key %s", bytes.TrimSpace(gossh.MarshalAuthorizedKey(pubKey)))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// doPolicyAuth verifies that conn can proceed with the specified (optional)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// pubKey. It returns nil if the matching policy action is Accept or
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// HoldAndDelegate. If pubKey is nil, there was no policy match but there is a
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// policy that might match a public key it returns errPubKeyRequired. Otherwise,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// it returns errDenied.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) doPolicyAuth(ctx ssh.Context, pubKey ssh.PublicKey) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// doPolicyAuth verifies that conn can proceed. It returns nil if the matching
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// policy action is Accept or HoldAndDelegate. Otherwise, it returns errDenied.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) doPolicyAuth(ctx ssh.Context) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err := c.setInfo(ctx); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						c.logf("failed to get conninfo: %v", err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return errDenied
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					a, localUser, err := c.evaluatePolicy(pubKey)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					a, localUser, err := c.evaluatePolicy()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if pubKey == nil && c.havePubKeyPolicy() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return errPubKeyRequired
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return fmt.Errorf("%w: %v", errDenied, err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					c.action0 = a
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					c.currentAction = a
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					c.pubKey = pubKey
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if a.Message != "" {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if err := ctx.SendAuthBanner(a.Message); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return fmt.Errorf("SendBanner: %w", err)
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -446,7 +409,6 @@ func (srv *server) newConn() (*conn, error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						ServerConfigCallback: c.ServerConfig,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						NoClientAuthHandler: c.NoClientAuthCallback,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						PublicKeyHandler:    c.PublicKeyHandler,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						PasswordHandler:     c.fakePasswordHandler,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						Handler:                       c.handleSessionPostSSHAuth,
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -514,34 +476,6 @@ func (c *conn) mayForwardLocalPortTo(ctx ssh.Context, destinationHost string, de
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// havePubKeyPolicy reports whether any policy rule may provide access by means
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// of a ssh.PublicKey.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) havePubKeyPolicy() bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if c.info == nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						panic("havePubKeyPolicy called before setInfo")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Is there any rule that looks like it'd require a public key for this
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// sshUser?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					pol, ok := c.sshPolicy()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for _, r := range pol.Rules {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if c.ruleExpired(r) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							continue
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if mapLocalUser(r.SSHUsers, c.info.sshUser) == "" {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							continue
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						for _, p := range r.Principals {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							if len(p.PubKeys) > 0 && c.principalMatchesTailscaleIdentity(p) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								return true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// sshPolicy returns the SSHPolicy for current node.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// If there is no SSHPolicy in the netmap, it returns a debugPolicy
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// if one is defined.
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -618,117 +552,19 @@ func (c *conn) setInfo(ctx ssh.Context) error {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// evaluatePolicy returns the SSHAction and localUser after evaluating
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// the SSHPolicy for this conn. The pubKey may be nil for "none" auth.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) evaluatePolicy(pubKey gossh.PublicKey) (_ *tailcfg.SSHAction, localUser string, _ error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// the SSHPolicy for this conn.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) evaluatePolicy() (_ *tailcfg.SSHAction, localUser string, _ error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					pol, ok := c.sshPolicy()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil, "", fmt.Errorf("tailssh: rejecting connection; no SSH policy")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					a, localUser, ok := c.evalSSHPolicy(pol, pubKey)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					a, localUser, ok := c.evalSSHPolicy(pol)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil, "", fmt.Errorf("tailssh: rejecting connection; no matching policy")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return a, localUser, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// pubKeyCacheEntry is the cache value for an HTTPS URL of public keys (like
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// "https://github.com/foo.keys")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				type pubKeyCacheEntry struct {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					lines []string
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					etag  string // if sent by server
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					at    time.Time
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					pubKeyCacheDuration      = time.Minute      // how long to cache non-empty public keys
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					pubKeyCacheEmptyDuration = 15 * time.Second // how long to cache empty responses
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (srv *server) fetchPublicKeysURLCached(url string) (ce pubKeyCacheEntry, ok bool) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					srv.mu.Lock()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					defer srv.mu.Unlock()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					// Mostly don't care about the size of this cache. Clean rarely.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if m := srv.fetchPublicKeysCache; len(m) > 50 {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						tooOld := srv.now().Add(pubKeyCacheDuration * 10)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						for k, ce := range m {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							if ce.at.Before(tooOld) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								delete(m, k)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					ce, ok = srv.fetchPublicKeysCache[url]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return ce, false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					maxAge := pubKeyCacheDuration
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if len(ce.lines) == 0 {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						maxAge = pubKeyCacheEmptyDuration
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return ce, srv.now().Sub(ce.at) < maxAge
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (srv *server) pubKeyClient() *http.Client {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if srv.pubKeyHTTPClient != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return srv.pubKeyHTTPClient
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return http.DefaultClient
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// fetchPublicKeysURL fetches the public keys from a URL. The strings are in the
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// the typical public key "type base64-string [comment]" format seen at e.g.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// https://github.com/USER.keys
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (srv *server) fetchPublicKeysURL(url string) ([]string, error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !strings.HasPrefix(url, "https://") {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil, errors.New("invalid URL scheme")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					ce, ok := srv.fetchPublicKeysURLCached(url)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return ce.lines, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					defer cancel()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil, err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if ce.etag != "" {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						req.Header.Add("If-None-Match", ce.etag)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					res, err := srv.pubKeyClient().Do(req)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil, err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					defer res.Body.Close()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var lines []string
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var etag string
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					switch res.StatusCode {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					default:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						err = fmt.Errorf("unexpected status %v", res.Status)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						srv.logf("fetching public keys from %s: %v", url, err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					case http.StatusNotModified:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						lines = ce.lines
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						etag = ce.etag
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					case http.StatusOK:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						var all []byte
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						all, err = io.ReadAll(io.LimitReader(res.Body, 4<<10))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if s := strings.TrimSpace(string(all)); s != "" {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							lines = strings.Split(s, "\n")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						etag = res.Header.Get("Etag")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					srv.mu.Lock()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					defer srv.mu.Unlock()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					mak.Set(&srv.fetchPublicKeysCache, url, pubKeyCacheEntry{
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						at:    srv.now(),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						lines: lines,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						etag:  etag,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					})
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return lines, err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// handleSessionPostSSHAuth runs an SSH session after the SSH-level authentication,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// but not necessarily before all the Tailscale-level extra verification has
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// completed. It also handles SFTP requests.
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -830,18 +666,6 @@ func (c *conn) expandDelegateURLLocked(actionURL string) string {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					).Replace(actionURL)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) expandPublicKeyURL(pubKeyURL string) string {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !strings.Contains(pubKeyURL, "$") {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return pubKeyURL
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					loginName := c.info.uprof.LoginName
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					localPart, _, _ := strings.Cut(loginName, "@")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return strings.NewReplacer(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						"$LOGINNAME_EMAIL", loginName,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						"$LOGINNAME_LOCALPART", localPart,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					).Replace(pubKeyURL)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// sshSession is an accepted Tailscale SSH session.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				type sshSession struct {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					ssh.Session
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -892,7 +716,7 @@ func (c *conn) newSSHSession(s ssh.Session) *sshSession {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// isStillValid reports whether the conn is still valid.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) isStillValid() bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					a, localUser, err := c.evaluatePolicy(c.pubKey)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					a, localUser, err := c.evaluatePolicy()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					c.vlogf("stillValid: %+v %v %v", a, localUser, err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return false
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1275,9 +1099,9 @@ func (c *conn) ruleExpired(r *tailcfg.SSHRule) bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return r.RuleExpires.Before(c.srv.now())
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) evalSSHPolicy(pol *tailcfg.SSHPolicy, pubKey gossh.PublicKey) (a *tailcfg.SSHAction, localUser string, ok bool) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) evalSSHPolicy(pol *tailcfg.SSHPolicy) (a *tailcfg.SSHAction, localUser string, ok bool) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for _, r := range pol.Rules {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if a, localUser, err := c.matchRule(r, pubKey); err == nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if a, localUser, err := c.matchRule(r); err == nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return a, localUser, true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1294,7 +1118,7 @@ var (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					errInvalidConn    = errors.New("invalid connection state")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) matchRule(r *tailcfg.SSHRule, pubKey gossh.PublicKey) (a *tailcfg.SSHAction, localUser string, err error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) matchRule(r *tailcfg.SSHRule) (a *tailcfg.SSHAction, localUser string, err error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					defer func() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						c.vlogf("matchRule(%+v): %v", r, err)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}()
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1324,7 +1148,7 @@ func (c *conn) matchRule(r *tailcfg.SSHRule, pubKey gossh.PublicKey) (a *tailcfg
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return nil, "", errUserMatch
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if ok, err := c.anyPrincipalMatches(r.Principals, pubKey); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if ok, err := c.anyPrincipalMatches(r.Principals); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil, "", err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					} else if !ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return nil, "", errPrincipalMatch
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1343,30 +1167,20 @@ func mapLocalUser(ruleSSHUsers map[string]string, reqSSHUser string) (localUser
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return v
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) anyPrincipalMatches(ps []*tailcfg.SSHPrincipal, pubKey gossh.PublicKey) (bool, error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) anyPrincipalMatches(ps []*tailcfg.SSHPrincipal) (bool, error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for _, p := range ps {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if p == nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							continue
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if ok, err := c.principalMatches(p, pubKey); err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return false, err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						} else if ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if c.principalMatchesTailscaleIdentity(p) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return true, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return false, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) principalMatches(p *tailcfg.SSHPrincipal, pubKey gossh.PublicKey) (bool, error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !c.principalMatchesTailscaleIdentity(p) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return false, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return c.principalMatchesPubKey(p, pubKey)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// principalMatchesTailscaleIdentity reports whether one of p's four fields
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// that match the Tailscale identity match (Node, NodeIP, UserLogin, Any).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				// This function does not consider PubKeys.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) principalMatchesTailscaleIdentity(p *tailcfg.SSHPrincipal) bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					ci := c.info
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if p.Any {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1386,42 +1200,6 @@ func (c *conn) principalMatchesTailscaleIdentity(p *tailcfg.SSHPrincipal) bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func (c *conn) principalMatchesPubKey(p *tailcfg.SSHPrincipal, clientPubKey gossh.PublicKey) (bool, error) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if len(p.PubKeys) == 0 {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return true, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if clientPubKey == nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return false, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					knownKeys := p.PubKeys
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if len(knownKeys) == 1 && strings.HasPrefix(knownKeys[0], "https://") {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						var err error
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						knownKeys, err = c.srv.fetchPublicKeysURL(c.expandPublicKeyURL(knownKeys[0]))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if err != nil {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return false, err
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for _, knownKey := range knownKeys {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if pubKeyMatchesAuthorizedKey(clientPubKey, knownKey) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							return true, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return false, nil
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func pubKeyMatchesAuthorizedKey(pubKey ssh.PublicKey, wantKey string) bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					wantKeyType, rest, ok := strings.Cut(wantKey, " ")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !ok {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if pubKey.Type() != wantKeyType {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					wantKeyB64, _, _ := strings.Cut(rest, " ")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					wantKeyData, _ := base64.StdEncoding.DecodeString(wantKeyB64)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return len(wantKeyData) > 0 && bytes.Equal(pubKey.Marshal(), wantKeyData)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func randBytes(n int) []byte {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					b := make([]byte, n)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if _, err := rand.Read(b); err != nil {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1918,7 +1696,6 @@ func envEq(a, b string) bool {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					metricActiveSessions      = clientmetric.NewGauge("ssh_active_sessions")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					metricIncomingConnections = clientmetric.NewCounter("ssh_incoming_connections")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					metricPublicKeyAccepts    = clientmetric.NewCounter("ssh_publickey_accepts") // accepted subset of ssh_publickey_connections
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					metricTerminalAccept      = clientmetric.NewCounter("ssh_terminalaction_accept")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					metricTerminalReject      = clientmetric.NewCounter("ssh_terminalaction_reject")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					metricTerminalMalformed   = clientmetric.NewCounter("ssh_terminalaction_malformed")
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |