Skip to content

Commit fcdf9ba

Browse files
committed
Refactor Windows SSH agent configuration from config file
1 parent 505a5d4 commit fcdf9ba

2 files changed

Lines changed: 112 additions & 23 deletions

File tree

internal/sshutil/agent_windows.go

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package sshutil
33
import (
44
"bufio"
55
"context"
6+
"fmt"
67
"net"
78
"os"
9+
"path/filepath"
810
"regexp"
11+
"strings"
912

1013
"github.com/Microsoft/go-winio"
1114
"github.com/pkg/errors"
@@ -28,42 +31,60 @@ func dialAgent() (*Agent, error) {
2831
// Connect to Windows pipe at the supplied address
2932
conn, err := winio.DialPipeContext(context.Background(), socket)
3033
if err != nil {
31-
return nil, errors.Wrap(err, "error connecting with ssh-agent at pipe specified by environment variable SSH_AUTH_SOCK")
34+
return nil, errors.Wrap(err, fmt.Sprintf("failed to connect to SSH agent at SSH_AUTH_SOCK=%s", socket))
3235
}
36+
3337
return &Agent{
3438
ExtendedAgent: agent.NewClient(conn),
3539
Conn: conn,
3640
}, nil
3741
}
3842

39-
homepath := os.Getenv("HOMEPATH")
40-
sshagentfile := string(homepath) + "\\.ssh\\config"
43+
pipeName := determineWindowsPipeName()
44+
conn, err := winio.DialPipeContext(context.Background(), pipeName)
45+
if err != nil {
46+
return nil, errors.Wrap(err, "error connecting with ssh-agent")
47+
}
48+
49+
return &Agent{
50+
ExtendedAgent: agent.NewClient(conn),
51+
Conn: conn,
52+
}, nil
53+
}
54+
55+
const (
56+
// defaultPipeName is the default Windows OpenSSH agent pipe
57+
defaultPipeName = `\\.\\pipe\\openssh-ssh-agent`
58+
)
59+
60+
func determineWindowsPipeName() string {
61+
homePath := os.Getenv("HOMEPATH") // TODO(hs): add default if not set?
62+
sshAgentConfigFile := filepath.Join(homePath, ".ssh", "config")
63+
64+
if pipeName := readWindowsPipeNameFrom(sshAgentConfigFile); pipeName != "" {
65+
return pipeName
66+
}
67+
68+
return defaultPipeName
69+
}
4170

42-
// DEFAULT: Windows OpenSSH agent
43-
pipename := "\\\\.\\pipe\\ssh-agent"
71+
var (
72+
re = regexp.MustCompile(`/`)
73+
re2 = regexp.MustCompile(`[\s\"]*`)
74+
)
4475

45-
file, err := os.Open(sshagentfile)
76+
func readWindowsPipeNameFrom(configFile string) (pipeName string) {
77+
file, err := os.Open(configFile)
4678
if err == nil {
4779
sc := bufio.NewScanner(file)
4880
for sc.Scan() {
49-
var line = sc.Text()
50-
if len(line) > 15 {
51-
compare := line[0:13]
52-
if compare == "IdentityAgent" {
53-
temp := line[14:len(line)]
54-
re := regexp.MustCompile(`/`)
55-
re2 := regexp.MustCompile(`[\s\"]*`)
56-
pipename = re2.ReplaceAllString(re.ReplaceAllString(temp, "\\"), "")
57-
}
81+
line := sc.Text()
82+
if len(line) > 15 && strings.HasPrefix(line, "IdentityAgent") {
83+
pipeName = re2.ReplaceAllString(re.ReplaceAllString(line[14:], "\\"), "")
84+
break
5885
}
5986
}
6087
}
61-
if conn, err := winio.DialPipeContext(context.Background(), pipename); err == nil {
62-
return &Agent{
63-
ExtendedAgent: agent.NewClient(conn),
64-
Conn: conn,
65-
}, nil
66-
} else {
67-
return nil, errors.Wrap(err, "error connecting with ssh-agent")
68-
}
88+
89+
return
6990
}
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 Test_determineWindowsPipeName(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)