diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index 2a14d84a2..2ffd78462 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -41,6 +41,7 @@ "tailscale.com/tailcfg" "tailscale.com/tempfork/gliderlabs/ssh" "tailscale.com/types/logger" + "tailscale.com/util/clientmetric" "tailscale.com/util/mak" ) @@ -92,6 +93,7 @@ func init() { // HandleSSHConn handles a Tailscale SSH connection from c. func (srv *server) HandleSSHConn(c net.Conn) error { + metricIncomingConnections.Add(1) ss, err := srv.newConn() if err != nil { return err @@ -301,7 +303,11 @@ func (srv *server) mayForwardLocalPortTo(ctx ssh.Context, destinationHost string if !ok { return false } - return ss.action.AllowLocalPortForwarding + if !ss.action.AllowLocalPortForwarding { + return false + } + metricLocalPortForward.Add(1) + return true } // havePubKeyPolicy reports whether any policy rule may provide access by means @@ -514,6 +520,9 @@ func (srv *server) fetchPublicKeysURL(url string) ([]string, error) { // but not necessarily before all the Tailscale-level extra verification has // completed. It also handles SFTP requests. func (c *conn) handleConnPostSSHAuth(s ssh.Session) { + if s.PublicKey() != nil { + metricPublicKeyConnections.Add(1) + } sshUser := s.User() cr := &contextReader{r: s} action, err := c.resolveTerminalAction(s, cr) @@ -528,6 +537,9 @@ func (c *conn) handleConnPostSSHAuth(s ssh.Session) { s.Exit(1) return } + if s.PublicKey() != nil { + metricPublicKeyAccepts.Add(1) + } if cr.HasOutstandingRead() { s = contextReaderSesssion{s, cr} @@ -536,8 +548,9 @@ func (c *conn) handleConnPostSSHAuth(s ssh.Session) { // Do this check after auth, but before starting the session. switch s.Subsystem() { case "sftp", "": + metricSFTP.Add(1) default: - fmt.Fprintf(s.Stderr(), "Unsupported subsystem %q \r\n", s.Subsystem()) + fmt.Fprintf(s.Stderr(), "Unsupported subsystem %q\r\n", s.Subsystem()) s.Exit(1) return } @@ -576,12 +589,19 @@ func (c *conn) resolveTerminalAction(s ssh.Session, cr *contextReader) (*tailcfg io.WriteString(s.Stderr(), strings.Replace(action.Message, "\n", "\r\n", -1)) } if action.Accept || action.Reject { + if action.Reject { + metricTerminalReject.Add(1) + } else { + metricTerminalAccept.Add(1) + } return action, nil } url := action.HoldAndDelegate if url == "" { + metricTerminalMalformed.Add(1) return nil, errors.New("reached Action that lacked Accept, Reject, and HoldAndDelegate") } + metricHolds.Add(1) awaitReadOnce.Do(func() { wg.Add(1) go func() { @@ -606,7 +626,10 @@ func (c *conn) resolveTerminalAction(s ssh.Session, cr *contextReader) (*tailcfg action, err = c.fetchSSHAction(ctx, url) if err != nil { if sawInterrupt.Get() { + metricTerminalInterrupt.Add(1) return nil, fmt.Errorf("aborted by user") + } else { + metricTerminalFetchError.Add(1) } return nil, fmt.Errorf("fetching SSHAction from %s: %w", url, err) } @@ -707,6 +730,7 @@ func (ss *sshSession) checkStillValid() { if ss.conn.isStillValid(ss.PublicKey()) { return } + metricPolicyChangeKick.Add(1) ss.logf("session no longer valid per new SSH policy; closing") ss.ctx.CloseWithError(userVisibleError{ fmt.Sprintf("Access revoked.\r\n"), @@ -869,6 +893,8 @@ func (ss *sshSession) handleSSHAgentForwarding(s ssh.Session, lu *user.User) err // It handles ss once it's been accepted and determined // that it should run. func (ss *sshSession) run() { + metricActiveSessions.Add(1) + defer metricActiveSessions.Add(-1) defer ss.ctx.CloseWithError(errSessionDone) srv := ss.conn.srv @@ -1327,3 +1353,19 @@ func envEq(a, b string) bool { } return a == b } + +var ( + metricActiveSessions = clientmetric.NewGauge("ssh_active_sessions") + metricIncomingConnections = clientmetric.NewCounter("ssh_incoming_connections") + metricPublicKeyConnections = clientmetric.NewCounter("ssh_publickey_connections") // total + metricPublicKeyAccepts = clientmetric.NewCounter("ssh_publickey_accepts") // accepted subset of ssh_publickey_connections + metricTerminalAccept = clientmetric.NewCounter("ssh_terminalaction_accept") + metricTerminalReject = clientmetric.NewCounter("ssh_terminalaction_reject") + metricTerminalInterrupt = clientmetric.NewCounter("ssh_terminalaction_interrupt") + metricTerminalMalformed = clientmetric.NewCounter("ssh_terminalaction_malformed") + metricTerminalFetchError = clientmetric.NewCounter("ssh_terminalaction_fetch_error") + metricHolds = clientmetric.NewCounter("ssh_holds") + metricPolicyChangeKick = clientmetric.NewCounter("ssh_policy_change_kick") + metricSFTP = clientmetric.NewCounter("ssh_sftp_requests") + metricLocalPortForward = clientmetric.NewCounter("ssh_local_port_forward_requests") +)