ssh/tailssh: handle Control-C during hold-and-delegate prompt

Fixes #4549

Change-Id: Iafc61af5e08cd03564d39cf667e940b2417714cc
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2022-04-27 13:23:13 -07:00
committed by Brad Fitzpatrick
parent f9e86e64b7
commit c1445155ef
2 changed files with 152 additions and 3 deletions

View File

@@ -37,6 +37,7 @@ import (
"tailscale.com/ipn/ipnlocal"
"tailscale.com/logtail/backoff"
"tailscale.com/net/tsaddr"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tempfork/gliderlabs/ssh"
"tailscale.com/types/logger"
@@ -488,7 +489,8 @@ func (srv *server) fetchPublicKeysURL(url string) ([]string, error) {
// completed. It also handles SFTP requests.
func (c *conn) handleConnPostSSHAuth(s ssh.Session) {
sshUser := s.User()
action, err := c.resolveTerminalAction(s)
cr := &contextReader{r: s}
action, err := c.resolveTerminalAction(s, cr)
if err != nil {
c.logf("resolveTerminalAction: %v", err)
io.WriteString(s.Stderr(), "Access Denied: failed during authorization check.\r\n")
@@ -501,6 +503,10 @@ func (c *conn) handleConnPostSSHAuth(s ssh.Session) {
return
}
if cr.HasOutstandingRead() {
s = contextReaderSesssion{s, cr}
}
// Do this check after auth, but before starting the session.
switch s.Subsystem() {
case "sftp", "":
@@ -522,8 +528,17 @@ func (c *conn) handleConnPostSSHAuth(s ssh.Session) {
// Any action with a Message in the chain will be printed to s.
//
// The returned SSHAction will be either Reject or Accept.
func (c *conn) resolveTerminalAction(s ssh.Session) (*tailcfg.SSHAction, error) {
func (c *conn) resolveTerminalAction(s ssh.Session, cr *contextReader) (*tailcfg.SSHAction, error) {
action := c.action0
var awaitReadOnce sync.Once // to start Reads on cr
var sawInterrupt syncs.AtomicBool
var wg sync.WaitGroup
defer wg.Wait() // wait for awaitIntrOnce's goroutine to exit
ctx, cancel := context.WithCancel(s.Context())
defer cancel()
// Loop processing/fetching Actions until one reaches a
// terminal state (Accept, Reject, or invalid Action), or
// until fetchSSHAction times out due to the context being
@@ -541,10 +556,32 @@ func (c *conn) resolveTerminalAction(s ssh.Session) (*tailcfg.SSHAction, error)
if url == "" {
return nil, errors.New("reached Action that lacked Accept, Reject, and HoldAndDelegate")
}
awaitReadOnce.Do(func() {
wg.Add(1)
go func() {
defer wg.Done()
buf := make([]byte, 1)
for {
n, err := cr.ReadContext(ctx, buf)
if err != nil {
return
}
if n > 0 && buf[0] == 0x03 { // Ctrl-C
sawInterrupt.Set(true)
s.Stderr().Write([]byte("Canceled.\r\n"))
s.Exit(1)
return
}
}
}()
})
url = c.expandDelegateURL(url)
var err error
action, err = c.fetchSSHAction(s.Context(), url)
action, err = c.fetchSSHAction(ctx, url)
if err != nil {
if sawInterrupt.Get() {
return nil, fmt.Errorf("aborted by user")
}
return nil, fmt.Errorf("fetching SSHAction from %s: %w", url, err)
}
}