Skip to content

Commit 93803a1

Browse files
shanman190pascalbreuninger
authored andcommitted
Enable SSH agent support on Windows
1 parent f1bc41f commit 93803a1

File tree

15 files changed

+1290
-9
lines changed

15 files changed

+1290
-9
lines changed

cmd/machine/ssh.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import (
1111
"github.com/loft-sh/devpod/pkg/client"
1212
"github.com/loft-sh/devpod/pkg/config"
1313
devssh "github.com/loft-sh/devpod/pkg/ssh"
14+
devsshagent "github.com/loft-sh/devpod/pkg/ssh/agent"
1415
"github.com/loft-sh/devpod/pkg/workspace"
1516
"github.com/loft-sh/log"
1617
"github.com/mattn/go-isatty"
1718
"github.com/pkg/errors"
1819
"github.com/sirupsen/logrus"
1920
"github.com/spf13/cobra"
2021
"golang.org/x/crypto/ssh"
21-
agent "golang.org/x/crypto/ssh/agent"
2222
"golang.org/x/term"
2323
)
2424

@@ -121,14 +121,14 @@ func StartSSHSession(ctx context.Context, user, command string, agentForwarding
121121
)
122122

123123
// request agent forwarding
124-
authSock := os.Getenv("SSH_AUTH_SOCK")
124+
authSock := devsshagent.GetSSHAuthSocket()
125125
if agentForwarding && authSock != "" {
126-
err = agent.ForwardToRemote(sshClient, authSock)
126+
err = devsshagent.ForwardToRemote(sshClient, authSock)
127127
if err != nil {
128128
return errors.Errorf("forward agent: %v", err)
129129
}
130130

131-
err = agent.RequestAgentForwarding(session)
131+
err = devsshagent.RequestAgentForwarding(session)
132132
if err != nil {
133133
return errors.Errorf("request agent forwarding: %v", err)
134134
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ require (
7171
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
7272
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
7373
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
74+
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
7475
)
7576

7677
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,6 +1412,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
14121412
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
14131413
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
14141414
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
1415+
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
1416+
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
14151417
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
14161418
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
14171419
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=

pkg/devcontainer/sshtunnel/sshtunnel.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import (
1313
client2 "github.com/loft-sh/devpod/pkg/client"
1414
config2 "github.com/loft-sh/devpod/pkg/devcontainer/config"
1515
devssh "github.com/loft-sh/devpod/pkg/ssh"
16+
devsshagent "github.com/loft-sh/devpod/pkg/ssh/agent"
1617
"github.com/pkg/errors"
1718
"github.com/sirupsen/logrus"
18-
gosshagent "golang.org/x/crypto/ssh/agent"
1919
)
2020

2121
type AgentInjectFunc func(context.Context, string, *os.File, *os.File, io.WriteCloser) error
@@ -105,13 +105,13 @@ func ExecuteCommand(
105105

106106
log.Debugf("SSH session created")
107107

108-
identityAgent := os.Getenv("SSH_AUTH_SOCK")
108+
identityAgent := devsshagent.GetSSHAuthSocket()
109109
if identityAgent != "" {
110-
err = gosshagent.ForwardToRemote(sshClient, identityAgent)
110+
err = devsshagent.ForwardToRemote(sshClient, identityAgent)
111111
if err != nil {
112112
errChan <- errors.Wrap(err, "forward agent")
113113
}
114-
err = gosshagent.RequestAgentForwarding(sess)
114+
err = devsshagent.RequestAgentForwarding(sess)
115115
if err != nil {
116116
errChan <- errors.Wrap(err, "request agent forwarding failed")
117117
}

pkg/ssh/agent/agent_unix.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//go:build !windows
2+
3+
package agent
4+
5+
import (
6+
"os"
7+
8+
"golang.org/x/crypto/ssh"
9+
gosshagent "golang.org/x/crypto/ssh/agent"
10+
)
11+
12+
func GetSSHAuthSocket() string {
13+
sshAuthSocket := os.Getenv("SSH_AUTH_SOCK")
14+
if sshAuthSocket != "" {
15+
return sshAuthSocket
16+
}
17+
18+
return ""
19+
}
20+
21+
func ForwardToRemote(client *ssh.Client, addr string) error {
22+
return gosshagent.ForwardToRemote(client, addr)
23+
}
24+
25+
func RequestAgentForwarding(session *ssh.Session) error {
26+
return gosshagent.RequestAgentForwarding(session)
27+
}

pkg/ssh/agent/agent_windows.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package agent
2+
3+
import (
4+
"io"
5+
"os"
6+
"sync"
7+
8+
"github.com/pkg/errors"
9+
"golang.org/x/crypto/ssh"
10+
gosshagent "golang.org/x/crypto/ssh/agent"
11+
"gopkg.in/natefinch/npipe.v2"
12+
)
13+
14+
const (
15+
channelType = "[email protected]"
16+
defaultNamedPipe = "\\\\.\\pipe\\openssh-ssh-agent"
17+
)
18+
19+
func GetSSHAuthSocket() string {
20+
sshAuthSocket := os.Getenv("SSH_AUTH_SOCK")
21+
if sshAuthSocket != "" {
22+
return sshAuthSocket
23+
}
24+
if _, err := os.Stat(defaultNamedPipe); err == nil {
25+
return defaultNamedPipe
26+
}
27+
28+
return ""
29+
}
30+
31+
func ForwardToRemote(client *ssh.Client, addr string) error {
32+
channels := client.HandleChannelOpen(channelType)
33+
if channels == nil {
34+
return errors.New("agent: already have handler for " + channelType)
35+
}
36+
conn, err := npipe.Dial(addr)
37+
if err != nil {
38+
return err
39+
}
40+
conn.Close()
41+
42+
go func() {
43+
for ch := range channels {
44+
channel, reqs, err := ch.Accept()
45+
if err != nil {
46+
continue
47+
}
48+
go ssh.DiscardRequests(reqs)
49+
go forwardNamedPipe(channel, addr)
50+
}
51+
}()
52+
return nil
53+
}
54+
55+
func RequestAgentForwarding(session *ssh.Session) error {
56+
return gosshagent.RequestAgentForwarding(session)
57+
}
58+
59+
func forwardNamedPipe(channel ssh.Channel, addr string) {
60+
conn, err := npipe.Dial(addr)
61+
if err != nil {
62+
return
63+
}
64+
65+
var wg sync.WaitGroup
66+
wg.Add(2)
67+
go func() {
68+
io.Copy(conn, channel)
69+
wg.Done()
70+
}()
71+
go func() {
72+
io.Copy(channel, conn)
73+
channel.CloseWrite()
74+
wg.Done()
75+
}()
76+
77+
wg.Wait()
78+
conn.Close()
79+
channel.Close()
80+
}

pkg/ssh/ssh_add.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import (
99
"time"
1010

1111
"github.com/loft-sh/devpod/pkg/command"
12+
devsshagent "github.com/loft-sh/devpod/pkg/ssh/agent"
1213
"github.com/loft-sh/log"
1314
"github.com/mitchellh/go-homedir"
1415
"golang.org/x/crypto/ssh"
1516
)
1617

1718
func AddPrivateKeysToAgent(ctx context.Context, log log.Logger) error {
18-
if os.Getenv("SSH_AUTH_SOCK") == "" {
19+
if devsshagent.GetSSHAuthSocket() == "" {
1920
return fmt.Errorf("ssh-agent is not started")
2021
} else if !command.Exists("ssh-add") {
2122
return fmt.Errorf("ssh-add couldn't be found")

vendor/gopkg.in/natefinch/npipe.v2/.gitignore

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/gopkg.in/natefinch/npipe.v2/LICENSE.txt

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)