From 0498a83cc214f02bcdbc9fde4fec521ea57fe36e Mon Sep 17 00:00:00 2001 From: Scott Leggett Date: Wed, 20 Jul 2022 14:26:12 +0800 Subject: [PATCH] feat: add SFTP support This requires use of an sftp-server binary which must be installed in the target container and be in $PATH. On alpine, the package for this is openssh-sftp-server. --- internal/sshserver/serve.go | 5 ++++- internal/sshserver/sessionhandler.go | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/internal/sshserver/serve.go b/internal/sshserver/serve.go index 88d6c1e4..6e7fce3f 100644 --- a/internal/sshserver/serve.go +++ b/internal/sshserver/serve.go @@ -17,7 +17,10 @@ import ( func Serve(ctx context.Context, log *zap.Logger, nc *nats.EncodedConn, l net.Listener, c *k8s.Client, hostKeys [][]byte) error { srv := ssh.Server{ - Handler: sessionHandler(log, c), + Handler: sessionHandler(log, c, false), + SubsystemHandlers: map[string]ssh.SubsystemHandler{ + "sftp": ssh.SubsystemHandler(sessionHandler(log, c, true)), + }, PublicKeyHandler: pubKeyAuth(log, nc, c), } for _, hk := range hostKeys { diff --git a/internal/sshserver/sessionhandler.go b/internal/sshserver/sessionhandler.go index fc05afb8..416bead6 100644 --- a/internal/sshserver/sessionhandler.go +++ b/internal/sshserver/sessionhandler.go @@ -13,11 +13,19 @@ import ( var ( sessionTotal = promauto.NewCounter(prometheus.CounterOpts{ Name: "session_total", - Help: "The total number of ssh sessions", + Help: "The total number of ssh sessions started", }) ) -func sessionHandler(log *zap.Logger, c *k8s.Client) ssh.Handler { +// sessionHandler returns a ssh.Handler which connects the ssh session to the +// requested container. +// +// If sftp is true, the returned ssh.Handler can be type converted to a sftp +// ssh.SubsystemHandler. The only practical difference in the returned session +// handler is that the command is set to sftp-server. This implies that the +// target container must have a sftp-server binary installed for sftp to work. +// There is no support for a built-in sftp server. +func sessionHandler(log *zap.Logger, c *k8s.Client, sftp bool) ssh.Handler { return func(s ssh.Session) { sessionTotal.Inc() sid, ok := s.Context().Value(ssh.ContextKeySessionID).(string) @@ -25,15 +33,17 @@ func sessionHandler(log *zap.Logger, c *k8s.Client) ssh.Handler { log.Warn("couldn't get session ID") return } - // check if a pty was requested - _, _, pty := s.Pty() // start the command log.Debug("starting command exec", zap.String("session-id", sid), zap.Strings("raw command", s.Command()), + zap.String("subsystem", s.Subsystem()), ) // parse the command line arguments to extract any service or container args service, container, cmd := parseConnectionParams(s.Command()) + if sftp { + cmd = []string{"sftp-server"} + } // validate the service and container if err := k8s.ValidateLabelValue(service); err != nil { log.Debug("invalid service name", @@ -79,6 +89,8 @@ func sessionHandler(log *zap.Logger, c *k8s.Client) ssh.Handler { } return } + // check if a pty was requested + _, _, pty := s.Pty() log.Info("executing command", zap.String("namespace", s.User()), zap.String("deployment", deployment),