diff --git a/balena/main.go b/balena/main.go index e401e4e..2fa7c14 100644 --- a/balena/main.go +++ b/balena/main.go @@ -27,6 +27,7 @@ import ( "os" "path" "runtime" + "strings" "syscall" "github.com/balena-io/sshproxy" @@ -49,7 +50,7 @@ func init() { pflag.CommandLine.Int64P("shell-gid", "g", -1, "Group to run shell as (default: current gid)") pflag.CommandLine.StringP("auth-failed-banner", "b", "", "Path to template displayed after failed authentication") pflag.CommandLine.IntP("max-auth-tries", "m", 0, "Maximum number of authentication attempts per connection (default 0; unlimited)") - pflag.CommandLine.BoolP("allow-env", "E", false, "Pass environment from client to shell (default: false) (warning: security implications)") + pflag.CommandLine.StringP("allow-env", "E", "", "List of environment variables to pass from client to shell (default: None)") pflag.CommandLine.StringP("sentry-dsn", "S", "", "Sentry DSN for error reporting") pflag.CommandLine.IntP("verbosity", "v", 1, "Set verbosity level (0 = quiet, 1 = normal, 2 = verbose, 3 = debug, default: 1)") pflag.CommandLine.BoolP("version", "", false, "Display version and exit") @@ -179,7 +180,7 @@ func main() { server, err := sshproxy.New( viper.GetString("dir"), viper.GetString("shell"), - viper.GetBool("allow-env"), + strings.Split(viper.GetString("allow-env"), ","), shellCreds, viper.GetInt("verbosity"), sshConfig, diff --git a/sshproxy.go b/sshproxy.go index e92b761..2058da7 100644 --- a/sshproxy.go +++ b/sshproxy.go @@ -45,7 +45,7 @@ type Server struct { config *ssh.ServerConfig shell string shellCreds *syscall.Credential - passEnv bool + envWhitelist map[string]bool errorHandler ErrorHandler verbosity int } @@ -57,13 +57,13 @@ type ErrorHandler func(error, map[string]string) // and an ssh.ServerConfig. If no ServerConfig is provided, then // ServerConfig.NoClientAuth is set to true. ed25519, rsa, ecdsa and dsa // keys are loaded, and generated if they do not exist. Returns a new Server. -func New(keyDir, shell string, passEnv bool, shellCreds *syscall.Credential, verbosity int, sshConfig *ssh.ServerConfig, errorHandler ErrorHandler) (*Server, error) { +func New(keyDir, shell string, envWhitelist []string, shellCreds *syscall.Credential, verbosity int, sshConfig *ssh.ServerConfig, errorHandler ErrorHandler) (*Server, error) { s := &Server{ keyDir: keyDir, config: sshConfig, shell: shell, shellCreds: shellCreds, - passEnv: passEnv, + envWhitelist: make(map[string]bool), errorHandler: errorHandler, verbosity: verbosity, } @@ -72,6 +72,9 @@ func New(keyDir, shell string, passEnv bool, shellCreds *syscall.Credential, ver NoClientAuth: true, } } + for _, envKey := range envWhitelist { + s.envWhitelist[envKey] = true + } for _, keyType := range []string{"ed25519", "rsa", "ecdsa", "dsa"} { if err := s.addHostKey(keyType); err != nil { return nil, err @@ -211,15 +214,16 @@ func (s *Server) handleRequests(reqs <-chan *ssh.Request, channel ssh.Channel, c for req := range reqs { switch req.Type { case "env": - if s.passEnv { - // append client env to the command environment - keyLen := binary.BigEndian.Uint32(req.Payload[:4]) - valLen := binary.BigEndian.Uint32(req.Payload[keyLen+4 : keyLen+8]) - key := string(req.Payload[4 : keyLen+4]) + // append client env to the command environment + keyLen := binary.BigEndian.Uint32(req.Payload[:4]) + valLen := binary.BigEndian.Uint32(req.Payload[keyLen+4 : keyLen+8]) + key := string(req.Payload[4 : keyLen+4]) + _, ok := s.envWhitelist[key] + if ok { val := string(req.Payload[keyLen+8 : keyLen+valLen+8]) env = append(env, fmt.Sprintf("%s=%s", key, val)) } - if err := req.Reply(s.passEnv, nil); err != nil { + if err := req.Reply(ok, nil); err != nil { return err } case "pty-req":