From 0bcdd845cc53aa711972b3be0af1398e4de337a7 Mon Sep 17 00:00:00 2001 From: Vladimir DOMBROVSKI Date: Tue, 18 Jul 2023 08:10:51 +0200 Subject: [PATCH 1/2] Add ssh-agent forwarding support Fixes #206 --- pkg/bastion/session.go | 71 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/pkg/bastion/session.go b/pkg/bastion/session.go index 0d1fd243..85edc29d 100644 --- a/pkg/bastion/session.go +++ b/pkg/bastion/session.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "sync" "time" "github.com/gliderlabs/ssh" @@ -15,6 +16,13 @@ import ( gossh "golang.org/x/crypto/ssh" ) +const ( + authAgentReqOpenSSH = "auth-agent-req@openssh.com" + authAgentChannelOpenSSH = "auth-agent@openssh.com" + authAgentReqRFC = "auth-agent-req" + authAgentChannelRFC = "auth-agent" +) + type sessionConfig struct { Addr string LogsLocation string @@ -61,11 +69,12 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s if err != nil { return err } + user := conn.User() actx := ctx.Value(authContextKey).(*authContext) username := actx.user.Name // pipe everything - return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan) + return pipe(conn, lastClient, lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan, actx.aesKey) case "direct-tcpip": lch, lreqs, err := newChan.Accept() // TODO: defer clean closer @@ -110,7 +119,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s actx := ctx.Value(authContextKey).(*authContext) username := actx.user.Name // pipe everything - return pipe(lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan) + return pipe(conn, lastClient, lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan, actx.aesKey) default: if err := newChan.Reject(gossh.UnknownChannelType, "unsupported channel type"); err != nil { log.Printf("failed to reject chan: %v", err) @@ -119,7 +128,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s } } -func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig sessionConfig, user string, username string, sessionID uint, newChan gossh.NewChannel) error { +func pipe(serverConn *gossh.ServerConn, client *gossh.Client, lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig sessionConfig, user string, username string, sessionID uint, newChan gossh.NewChannel, aesKey string) error { defer func() { _ = lch.Close() _ = rch.Close() @@ -195,6 +204,16 @@ func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig log.Printf("failed to write log: %v", err) } } + if req.Type == authAgentReqOpenSSH { + if err := ForwardToRemote(authAgentChannelOpenSSH, serverConn, client); err != nil { + log.Println("Failed to forward openssh agent", err) + } + } + if req.Type == authAgentReqRFC { + if err := ForwardToRemote(authAgentChannelRFC, serverConn, client); err != nil { + log.Println("Failed to forward RFC agent", err) + } + } if err != nil { errch <- err @@ -253,6 +272,52 @@ func pipe(lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig } } +func ForwardToRemote(chanName string, conn *gossh.ServerConn, client *gossh.Client) error { + channels := client.HandleChannelOpen(chanName) + if channels == nil { + return errors.New("agent: already have handler for " + chanName) + } + + go func() { + for ch := range channels { + lch, reqs, err := ch.Accept() + if err != nil { + log.Println("On auth-agent channel accept", err) + continue + } + defer lch.Close() + go gossh.DiscardRequests(reqs) + + rch, rreqs, err := conn.OpenChannel(chanName, nil) + if err != nil { + log.Println("On auth-agent channel open", err) + continue + } + defer rch.Close() + go gossh.DiscardRequests(rreqs) + + forwardChannel(rch, lch) + } + }() + return nil +} + +func forwardChannel(rch, lch gossh.Channel) { + var wg sync.WaitGroup + wg.Add(2) + go func() { + io.Copy(lch, rch) + lch.CloseWrite() + wg.Done() + }() + go func() { + io.Copy(rch, lch) + rch.CloseWrite() + wg.Done() + }() + wg.Wait() +} + func newDiscardWriteCloser() io.WriteCloser { return &discardWriteCloser{ioutil.Discard} } type discardWriteCloser struct { From ebe667850a39770c9f4f67ab4afa47aa2b275ee8 Mon Sep 17 00:00:00 2001 From: Vladimir DOMBROVSKI Date: Tue, 18 Jul 2023 13:00:17 +0200 Subject: [PATCH 2/2] Remove aes context from pipe This was part of another modification --- pkg/bastion/session.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/bastion/session.go b/pkg/bastion/session.go index 85edc29d..8dd84ca1 100644 --- a/pkg/bastion/session.go +++ b/pkg/bastion/session.go @@ -74,7 +74,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s actx := ctx.Value(authContextKey).(*authContext) username := actx.user.Name // pipe everything - return pipe(conn, lastClient, lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan, actx.aesKey) + return pipe(conn, lastClient, lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan) case "direct-tcpip": lch, lreqs, err := newChan.Accept() // TODO: defer clean closer @@ -119,7 +119,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s actx := ctx.Value(authContextKey).(*authContext) username := actx.user.Name // pipe everything - return pipe(conn, lastClient, lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan, actx.aesKey) + return pipe(conn, lastClient, lreqs, rreqs, lch, rch, configs[len(configs)-1], user, username, sessionID, newChan) default: if err := newChan.Reject(gossh.UnknownChannelType, "unsupported channel type"); err != nil { log.Printf("failed to reject chan: %v", err) @@ -128,7 +128,7 @@ func multiChannelHandler(conn *gossh.ServerConn, newChan gossh.NewChannel, ctx s } } -func pipe(serverConn *gossh.ServerConn, client *gossh.Client, lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig sessionConfig, user string, username string, sessionID uint, newChan gossh.NewChannel, aesKey string) error { +func pipe(serverConn *gossh.ServerConn, client *gossh.Client, lreqs, rreqs <-chan *gossh.Request, lch, rch gossh.Channel, sessConfig sessionConfig, user string, username string, sessionID uint, newChan gossh.NewChannel) error { defer func() { _ = lch.Close() _ = rch.Close()