Browse Source

ssh/tailssh: add support for agent forwarding.

Updates #3802

Signed-off-by: Maisem Ali <[email protected]>
Maisem Ali 4 years ago
parent
commit
98b45ef12c
3 changed files with 63 additions and 5 deletions
  1. 4 0
      ssh/tailssh/incubator.go
  2. 55 5
      ssh/tailssh/tailssh.go
  3. 4 0
      tailcfg/tailcfg.go

+ 4 - 0
ssh/tailssh/incubator.go

@@ -186,6 +186,10 @@ func (ss *sshSession) launchProcess(ctx context.Context) error {
 
 
 	ss.cmd = cmd
 	ss.cmd = cmd
 
 
+	if ss.agentListener != nil {
+		cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_AUTH_SOCK=%s", ss.agentListener.Addr()))
+	}
+
 	ptyReq, winCh, isPty := ss.Pty()
 	ptyReq, winCh, isPty := ss.Pty()
 	if !isPty {
 	if !isPty {
 		ss.logf("starting non-pty command: %+v", cmd.Args)
 		ss.logf("starting non-pty command: %+v", cmd.Args)

+ 55 - 5
ssh/tailssh/tailssh.go

@@ -20,6 +20,8 @@ import (
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"os/user"
 	"os/user"
+	"path/filepath"
+	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
@@ -257,11 +259,12 @@ type sshSession struct {
 	sharedID string // ID that's shared with control
 	sharedID string // ID that's shared with control
 	logf     logger.Logf
 	logf     logger.Logf
 
 
-	ctx       *sshContext // implements context.Context
-	srv       *server
-	connInfo  *sshConnInfo
-	action    *tailcfg.SSHAction
-	localUser *user.User
+	ctx           *sshContext // implements context.Context
+	srv           *server
+	connInfo      *sshConnInfo
+	action        *tailcfg.SSHAction
+	localUser     *user.User
+	agentListener net.Listener // non-nil if agent-forwarding requested+allowed
 
 
 	// initialized by launchProcess:
 	// initialized by launchProcess:
 	cmd    *exec.Cmd
 	cmd    *exec.Cmd
@@ -385,6 +388,47 @@ func (srv *server) endSession(ss *sshSession) {
 
 
 var errSessionDone = errors.New("session is done")
 var errSessionDone = errors.New("session is done")
 
 
+// handleSSHAgentForwarding starts a Unix socket listener and in the background
+// forwards agent connections between the listenr and the ssh.Session.
+// On success, it assigns ss.agentListener.
+func (ss *sshSession) handleSSHAgentForwarding(s ssh.Session, lu *user.User) error {
+	if !ssh.AgentRequested(ss) || !ss.action.AllowAgentForwarding {
+		return nil
+	}
+	ss.logf("ssh: agent forwarding requested")
+	ln, err := ssh.NewAgentListener()
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if err != nil && ln != nil {
+			ln.Close()
+		}
+	}()
+
+	uid, err := strconv.ParseUint(lu.Uid, 10, 32)
+	if err != nil {
+		return err
+	}
+	gid, err := strconv.ParseUint(lu.Gid, 10, 32)
+	if err != nil {
+		return err
+	}
+	socket := ln.Addr().String()
+	dir := filepath.Dir(socket)
+	// Make sure the socket is accessible by the user.
+	if err := os.Chown(socket, int(uid), int(gid)); err != nil {
+		return err
+	}
+	if err := os.Chmod(dir, 0755); err != nil {
+		return err
+	}
+
+	go ssh.ForwardAgentConnections(ln, s)
+	ss.agentListener = ln
+	return nil
+}
+
 // run is the entrypoint for a newly accepted SSH session.
 // run is the entrypoint for a newly accepted SSH session.
 //
 //
 // When ctx is done, the session is forcefully terminated. If its Err
 // When ctx is done, the session is forcefully terminated. If its Err
@@ -424,6 +468,12 @@ func (ss *sshSession) run() {
 	// See https://github.com/tailscale/tailscale/issues/4146
 	// See https://github.com/tailscale/tailscale/issues/4146
 	ss.DisablePTYEmulation()
 	ss.DisablePTYEmulation()
 
 
+	if err := ss.handleSSHAgentForwarding(ss, lu); err != nil {
+		logf("ssh: agent forwarding failed: %v", err)
+	} else if ss.agentListener != nil {
+		// TODO(maisem/bradfitz): add a way to close all session resources
+		defer ss.agentListener.Close()
+	}
 	err := ss.launchProcess(ss.ctx)
 	err := ss.launchProcess(ss.ctx)
 	if err != nil {
 	if err != nil {
 		logf("start failed: %v", err.Error())
 		logf("start failed: %v", err.Error())

+ 4 - 0
tailcfg/tailcfg.go

@@ -1615,6 +1615,10 @@ type SSHAction struct {
 	// before being forcefully terminated.
 	// before being forcefully terminated.
 	SesssionDuration time.Duration `json:"sessionDuration,omitempty"`
 	SesssionDuration time.Duration `json:"sessionDuration,omitempty"`
 
 
+	// AllowAgentForwarding, if true, allows accepted connections to forward
+	// the ssh agent if requested.
+	AllowAgentForwarding bool `json:"allowAgentForwarding,omitempty"`
+
 	// HoldAndDelegate, if non-empty, is a URL that serves an
 	// HoldAndDelegate, if non-empty, is a URL that serves an
 	// outcome verdict.  The connection will be accepted and will
 	// outcome verdict.  The connection will be accepted and will
 	// block until the provided long-polling URL serves a new
 	// block until the provided long-polling URL serves a new