Skip to content

Commit e44f7ad

Browse files
authored
feat: Set SSH env vars: SSH_CLIENT, SSH_CONNECTION and SSH_TTY (#3622)
Fixes #2339
1 parent 9c0cd52 commit e44f7ad

File tree

4 files changed

+45
-1
lines changed

4 files changed

+45
-1
lines changed

agent/agent.go

+11
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,15 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
404404
unixExecutablePath := strings.ReplaceAll(executablePath, "\\", "/")
405405
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, unixExecutablePath))
406406

407+
// Set SSH connection environment variables (these are also set by OpenSSH
408+
// and thus expected to be present by SSH clients). Since the agent does
409+
// networking in-memory, trying to provide accurate values here would be
410+
// nonsensical. For now, we hard code these values so that they're present.
411+
srcAddr, srcPort := "0.0.0.0", "0"
412+
dstAddr, dstPort := "0.0.0.0", "0"
413+
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CLIENT=%s %s %s", srcAddr, srcPort, dstPort))
414+
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CONNECTION=%s %s %s %s", srcAddr, srcPort, dstAddr, dstPort))
415+
407416
// Load environment variables passed via the agent.
408417
// These should override all variables we manually specify.
409418
for envKey, value := range metadata.EnvironmentVariables {
@@ -441,6 +450,8 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
441450
sshPty, windowSize, isPty := session.Pty()
442451
if isPty {
443452
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", sshPty.Term))
453+
454+
// The pty package sets `SSH_TTY` on supported platforms.
444455
ptty, process, err := pty.Start(cmd)
445456
if err != nil {
446457
return xerrors.Errorf("start command: %w", err)

agent/agent_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,29 @@ func TestAgent(t *testing.T) {
252252
}
253253
})
254254

255+
t.Run("SSH connection env vars", func(t *testing.T) {
256+
t.Parallel()
257+
258+
// Note: the SSH_TTY environment variable should only be set for TTYs.
259+
// For some reason this test produces a TTY locally and a non-TTY in CI
260+
// so we don't test for the absence of SSH_TTY.
261+
for _, key := range []string{"SSH_CONNECTION", "SSH_CLIENT"} {
262+
key := key
263+
t.Run(key, func(t *testing.T) {
264+
t.Parallel()
265+
266+
session := setupSSHSession(t, agent.Metadata{})
267+
command := "sh -c 'echo $" + key + "'"
268+
if runtime.GOOS == "windows" {
269+
command = "cmd.exe /c echo %" + key + "%"
270+
}
271+
output, err := session.Output(command)
272+
require.NoError(t, err)
273+
require.NotEmpty(t, strings.TrimSpace(string(output)))
274+
})
275+
}
276+
})
277+
255278
t.Run("StartupScript", func(t *testing.T) {
256279
t.Parallel()
257280
tempPath := filepath.Join(t.TempDir(), "content.txt")

pty/start_other.go

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package pty
55

66
import (
7+
"fmt"
78
"os/exec"
89
"runtime"
910
"strings"
@@ -18,6 +19,8 @@ func startPty(cmd *exec.Cmd) (PTY, Process, error) {
1819
if err != nil {
1920
return nil, nil, xerrors.Errorf("open: %w", err)
2021
}
22+
23+
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_PTY=%s", tty.Name()))
2124
cmd.SysProcAttr = &syscall.SysProcAttr{
2225
Setsid: true,
2326
Setctty: true,

pty/start_other_test.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//go:build !windows
2-
// +build !windows
32

43
package pty_test
54

@@ -40,4 +39,12 @@ func TestStart(t *testing.T) {
4039
require.True(t, xerrors.As(err, &exitErr))
4140
assert.NotEqual(t, 0, exitErr.ExitCode())
4241
})
42+
43+
t.Run("SSH_PTY", func(t *testing.T) {
44+
t.Parallel()
45+
pty, ps := ptytest.Start(t, exec.Command("env"))
46+
pty.ExpectMatch("SSH_PTY=/dev/")
47+
err := ps.Wait()
48+
require.NoError(t, err)
49+
})
4350
}

0 commit comments

Comments
 (0)