diff --git a/lib/client/api.go b/lib/client/api.go index da2382162d683..af5dd6051800a 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -327,9 +327,9 @@ type Config struct { // SessionID is a session ID to use when opening a new session. SessionID string - // extraEnvs contains additional environment variables that will be added + // ExtraEnvs contains additional environment variables that will be added // to SSH session. - extraEnvs map[string]string + ExtraEnvs map[string]string // InteractiveCommand tells tsh to launch a remote exec command in interactive mode, // i.e. attaching the terminal to it. @@ -3039,7 +3039,7 @@ func (tc *TeleportClient) newSessionEnv() map[string]string { env[sshutils.SessionEnvVar] = tc.SessionID } - for key, val := range tc.extraEnvs { + for key, val := range tc.ExtraEnvs { env[key] = val } return env diff --git a/lib/client/client.go b/lib/client/client.go index 3c88cb88b71ed..aa6a06cf015bd 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -620,10 +620,10 @@ func (c *NodeClient) RunCommand(ctx context.Context, command []string, opts ...R // AddEnv add environment variable to SSH session. This method needs to be called // before the session is created. func (c *NodeClient) AddEnv(key, value string) { - if c.TC.extraEnvs == nil { - c.TC.extraEnvs = make(map[string]string) + if c.TC.ExtraEnvs == nil { + c.TC.ExtraEnvs = make(map[string]string) } - c.TC.extraEnvs[key] = value + c.TC.ExtraEnvs[key] = value } func (c *NodeClient) handleGlobalRequests(ctx context.Context, requestCh <-chan *ssh.Request) { diff --git a/tool/tsh/common/options.go b/tool/tsh/common/options.go index 6ce01d7d2fbd3..3fd748e95af8b 100644 --- a/tool/tsh/common/options.go +++ b/tool/tsh/common/options.go @@ -102,7 +102,7 @@ var supportedOptions = map[string]setOption{ "RequestTTY": setRequestTTYOption, "RhostsRSAAuthentication": nil, "RSAAuthentication": nil, - "SendEnv": nil, + "SendEnv": setSendEnvOption, "ServerAliveInterval": nil, "ServerAliveCountMax": nil, "StreamLocalBindMask": nil, @@ -157,6 +157,9 @@ type Options struct { // ForwardX11Timeout specifies a timeout in seconds after which X11 forwarding // attempts will be rejected when in untrusted forwarding mode. ForwardX11Timeout time.Duration + + // SendEnvVariables is a list of local environment variables to send to remote host. + SendEnvVariables []string } type setOption func(*Options, string) error @@ -227,6 +230,11 @@ func setRequestTTYOption(o *Options, val string) error { return nil } +func setSendEnvOption(o *Options, val string) error { + o.SendEnvVariables = append(o.SendEnvVariables, val) + return nil +} + func setStrictHostKeyCheckingOption(o *Options, val string) error { parsedValue, err := parseBoolOption(val) if err != nil { diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index a3171c6bbdf56..84f17512a45b2 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -4498,6 +4498,9 @@ func loadClientConfigFromCLIConf(cf *CLIConf, proxy string) (*client.Config, err logger.InfoContext(ctx, "X11 forwarding is not properly configured, continuing without it", "error", err) } + // send variables from user env + setEnvVariables(c, options) + // If the caller does not want to check host keys, pass in a insecure host // key checker. if !options.StrictHostKeyChecking { @@ -4574,6 +4577,20 @@ func loadClientConfigFromCLIConf(cf *CLIConf, proxy string) (*client.Config, err return c, nil } +// setEnvVariables configures extra env variables to send in client config based on the requested options. +// We match OpenSSH behavior: if the requested env var is not set (os.LookupEnv return false), we won't send it. +func setEnvVariables(c *client.Config, options Options) { + if c.ExtraEnvs == nil { + c.ExtraEnvs = map[string]string{} + } + for _, variable := range options.SendEnvVariables { + value, found := os.LookupEnv(variable) + if found { + c.ExtraEnvs[variable] = value + } + } +} + func initClientStore(cf *CLIConf, proxy string) (*client.Store, error) { switch { case cf.IdentityFileIn != "": diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index 2db166e61f5d9..189cdabf61b3b 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -7174,3 +7174,46 @@ func TestSCP(t *testing.T) { }) } } + +func TestSetEnvVariables(t *testing.T) { + testCases := []struct { + name string + envVars map[string]string + sendEnvVariables []string + expectedExtraEnvs map[string]string + }{ + { + name: "Skip unset var", + envVars: map[string]string{ + "TEST_VAR1": "value1", + "TEST_VAR2": "value2", + }, + sendEnvVariables: []string{"TEST_VAR1", "TEST_VAR2", "UNSET_VAR"}, + expectedExtraEnvs: map[string]string{ + "TEST_VAR1": "value1", + "TEST_VAR2": "value2", + }, + }, + { + name: "Sending empty var", + envVars: map[string]string{"EMPTY_VAR": ""}, + sendEnvVariables: []string{"EMPTY_VAR"}, + expectedExtraEnvs: map[string]string{"EMPTY_VAR": ""}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for k, v := range tc.envVars { + t.Setenv(k, v) + } + + c := &client.Config{} + options := Options{SendEnvVariables: tc.sendEnvVariables} + + setEnvVariables(c, options) + + require.Equal(t, tc.expectedExtraEnvs, c.ExtraEnvs) + }) + } +}