Skip to content

Commit 8f0d454

Browse files
committed
feat: Add test for coder ssh --forward-agent
1 parent 51c4128 commit 8f0d454

File tree

1 file changed

+84
-0
lines changed

1 file changed

+84
-0
lines changed

cli/ssh_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ package cli_test
22

33
import (
44
"context"
5+
"crypto/ecdsa"
6+
"crypto/elliptic"
7+
"crypto/rand"
8+
"errors"
59
"io"
610
"net"
11+
"path/filepath"
712
"runtime"
813
"testing"
914
"time"
@@ -12,6 +17,7 @@ import (
1217
"github.com/stretchr/testify/assert"
1318
"github.com/stretchr/testify/require"
1419
"golang.org/x/crypto/ssh"
20+
gosshagent "golang.org/x/crypto/ssh/agent"
1521

1622
"cdr.dev/slog"
1723
"cdr.dev/slog/sloggers/slogtest"
@@ -135,6 +141,84 @@ func TestSSH(t *testing.T) {
135141
require.NoError(t, err)
136142
_ = clientOutput.Close()
137143
})
144+
//nolint:paralleltest // Disabled due to use of t.Setenv.
145+
t.Run("ForwardAgent", func(t *testing.T) {
146+
if runtime.GOOS == "windows" {
147+
t.Skip("Test not supported on windows")
148+
}
149+
150+
client, workspace, agentToken := setupWorkspaceForSSH(t)
151+
152+
tGoContext(t, func(ctx context.Context) {
153+
// Run this async so the SSH command has to wait for
154+
// the build and agent to connect!
155+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
156+
agentClient := codersdk.New(client.URL)
157+
agentClient.SessionToken = agentToken
158+
agentCloser := agent.New(agentClient.ListenWorkspaceAgent, &agent.Options{
159+
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
160+
})
161+
<-ctx.Done()
162+
_ = agentCloser.Close()
163+
})
164+
165+
// Generate private key.
166+
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
167+
require.NoError(t, err)
168+
kr := gosshagent.NewKeyring()
169+
kr.Add(gosshagent.AddedKey{
170+
PrivateKey: privateKey,
171+
})
172+
173+
// Start up ssh agent listening on unix socket.
174+
tmpdir := t.TempDir()
175+
agentSock := filepath.Join(tmpdir, "agent.sock")
176+
l, err := net.Listen("unix", agentSock)
177+
require.NoError(t, err)
178+
defer l.Close()
179+
tGo(t, func() {
180+
for {
181+
fd, err := l.Accept()
182+
if err != nil {
183+
t.Logf("accept error: %v", err)
184+
return
185+
}
186+
187+
err = gosshagent.ServeAgent(kr, fd)
188+
if !errors.Is(err, io.EOF) {
189+
assert.NoError(t, err)
190+
}
191+
}
192+
})
193+
194+
t.Setenv("SSH_AUTH_SOCK", agentSock)
195+
cmd, root := clitest.New(t,
196+
"ssh",
197+
workspace.Name,
198+
"--forward-agent",
199+
)
200+
clitest.SetupConfig(t, client, root)
201+
pty := ptytest.New(t)
202+
cmd.SetIn(pty.Input())
203+
cmd.SetOut(pty.Output())
204+
cmd.SetErr(io.Discard)
205+
tGo(t, func() {
206+
err := cmd.Execute()
207+
assert.NoError(t, err)
208+
})
209+
210+
// Ensure that SSH_AUTH_SOCK is set.
211+
pty.WriteLine("env")
212+
pty.ExpectMatch("SSH_AUTH_SOCK=/tmp") // E.g. /tmp/auth-agent3167016167/listener.sock
213+
// Ensure that ssh-add lists our key.
214+
pty.WriteLine("ssh-add -L")
215+
keys, err := kr.List()
216+
require.NoError(t, err)
217+
pty.ExpectMatch(keys[0].String())
218+
219+
// kthxbye
220+
pty.WriteLine("exit")
221+
})
138222
}
139223

140224
// tGoContext runs fn in a goroutine passing a context that will be

0 commit comments

Comments
 (0)