Skip to content

Commit dbff97a

Browse files
authored
Merge pull request #1428 from smallstep/herman/ssh-agent-config-file
Enable alternate SSH agents for `step ssh` on Windows
2 parents 7e33b4e + 2ae10f6 commit dbff97a

File tree

3 files changed

+120
-3
lines changed

3 files changed

+120
-3
lines changed

internal/sshutil/agent_windows.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package sshutil
22

33
import (
44
"context"
5+
"fmt"
56
"net"
67
"os"
78

@@ -26,19 +27,21 @@ func dialAgent() (*Agent, error) {
2627
// Connect to Windows pipe at the supplied address
2728
conn, err := winio.DialPipeContext(context.Background(), socket)
2829
if err != nil {
29-
return nil, errors.Wrap(err, "error connecting with ssh-agent at pipe specified by environment variable SSH_AUTH_SOCK")
30+
return nil, errors.Wrap(err, fmt.Sprintf("failed to connect to SSH agent at SSH_AUTH_SOCK=%s", socket))
3031
}
32+
3133
return &Agent{
3234
ExtendedAgent: agent.NewClient(conn),
3335
Conn: conn,
3436
}, nil
3537
}
3638

37-
// DEFAULT: Windows OpenSSH agent
38-
conn, err := winio.DialPipeContext(context.Background(), `\\.\\pipe\\openssh-ssh-agent`)
39+
pipeName := determineWindowsPipeName()
40+
conn, err := winio.DialPipeContext(context.Background(), pipeName)
3941
if err != nil {
4042
return nil, errors.Wrap(err, "error connecting with ssh-agent")
4143
}
44+
4245
return &Agent{
4346
ExtendedAgent: agent.NewClient(conn),
4447
Conn: conn,

internal/sshutil/pipe.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package sshutil
2+
3+
import (
4+
"bufio"
5+
"os"
6+
"path/filepath"
7+
"regexp"
8+
"strings"
9+
)
10+
11+
const (
12+
// defaultPipeName is the default Windows OpenSSH agent pipe
13+
defaultPipeName = `\\.\\pipe\\openssh-ssh-agent`
14+
)
15+
16+
func determineWindowsPipeName() string {
17+
homePath := os.Getenv("HOMEPATH") // TODO(hs): add default if not set?
18+
sshAgentConfigFile := filepath.Join(homePath, ".ssh", "config")
19+
20+
if pipeName := readWindowsPipeNameFrom(sshAgentConfigFile); pipeName != "" {
21+
return pipeName
22+
}
23+
24+
return defaultPipeName
25+
}
26+
27+
var (
28+
re = regexp.MustCompile(`/`)
29+
re2 = regexp.MustCompile(`[\s\"]*`)
30+
)
31+
32+
func readWindowsPipeNameFrom(configFile string) (pipeName string) {
33+
file, err := os.Open(configFile)
34+
if err == nil {
35+
sc := bufio.NewScanner(file)
36+
for sc.Scan() {
37+
line := sc.Text()
38+
if len(line) > 15 && strings.HasPrefix(line, "IdentityAgent") {
39+
pipeName = re2.ReplaceAllString(re.ReplaceAllString(line[14:], "\\"), "")
40+
break
41+
}
42+
}
43+
}
44+
45+
return
46+
}

internal/sshutil/pipe_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package sshutil
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestDeterminesWindowsPipeName(t *testing.T) {
13+
t.Run("default", func(t *testing.T) {
14+
assert.Equal(t, `\\.\\pipe\\openssh-ssh-agent`, determineWindowsPipeName())
15+
})
16+
17+
t.Run("valid-config-file", func(t *testing.T) {
18+
dir := t.TempDir()
19+
file := filepath.Join(dir, ".ssh", "config")
20+
21+
t.Setenv("HOMEPATH", dir)
22+
err := os.Mkdir(filepath.Join(dir, ".ssh"), 0777)
23+
require.NoError(t, err)
24+
err = os.WriteFile(file, []byte(`IdentityAgent \\.\\pipe\\pageant.user.abcd`), 0600)
25+
require.NoError(t, err)
26+
27+
assert.Equal(t, `\\.\\pipe\\pageant.user.abcd`, determineWindowsPipeName())
28+
})
29+
30+
t.Run("invalid-config-file", func(t *testing.T) {
31+
dir := t.TempDir()
32+
file := filepath.Join(dir, ".ssh", "config")
33+
34+
t.Setenv("HOMEPATH", dir)
35+
err := os.Mkdir(filepath.Join(dir, ".ssh"), 0777)
36+
require.NoError(t, err)
37+
err = os.WriteFile(file, []byte(`NoIdentityAgent \\.\\pipe\\pageant.user.abcd`), 0600)
38+
require.NoError(t, err)
39+
40+
assert.Equal(t, `\\.\\pipe\\openssh-ssh-agent`, determineWindowsPipeName())
41+
})
42+
}
43+
44+
func TestReadsWindowsPipeNameFromFile(t *testing.T) {
45+
t.Run("empty-path", func(t *testing.T) {
46+
assert.Equal(t, ``, readWindowsPipeNameFrom(""))
47+
})
48+
49+
t.Run("valid-config-file", func(t *testing.T) {
50+
dir := t.TempDir()
51+
file := filepath.Join(dir, "config")
52+
53+
err := os.WriteFile(file, []byte(`IdentityAgent \\.\\pipe\\pageant.user.abcd`), 0600)
54+
require.NoError(t, err)
55+
56+
assert.Equal(t, `\\.\\pipe\\pageant.user.abcd`, readWindowsPipeNameFrom(file))
57+
})
58+
59+
t.Run("invalid-config-file", func(t *testing.T) {
60+
dir := t.TempDir()
61+
file := filepath.Join(dir, "config")
62+
63+
err := os.WriteFile(file, []byte(`NoIdentityAgent \\.\\pipe\\pageant.user.abcd`), 0600)
64+
require.NoError(t, err)
65+
66+
assert.Equal(t, ``, readWindowsPipeNameFrom(file))
67+
})
68+
}

0 commit comments

Comments
 (0)