From 54b74f71b6a223aced1174ebbd5f9998250f656a Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 18 May 2022 16:38:35 +0300 Subject: [PATCH 01/11] feat: Add SSH agent forwarding support to coder agent --- agent/agent.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/agent/agent.go b/agent/agent.go index 75787b4cfc5e1..55d169cd35ec0 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -380,6 +380,16 @@ func (a *agent) handleSSHSession(session ssh.Session) error { return err } + if ssh.AgentRequested(session) { + l, err := ssh.NewAgentListener() + if err != nil { + return xerrors.Errorf("new agent listener: %w", err) + } + defer l.Close() + go ssh.ForwardAgentConnections(l, session) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", "SSH_AUTH_SOCK", l.Addr().String())) + } + sshPty, windowSize, isPty := session.Pty() if isPty { cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", sshPty.Term)) From 3df1a0891cec60b1267d9b91f9fe23cf39695df8 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 12:31:22 +0300 Subject: [PATCH 02/11] feat: Add forward agent flag to `coder ssh` --- cli/ssh.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cli/ssh.go b/cli/ssh.go index 58d3677b579de..c17c5c8d697c4 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -15,6 +15,7 @@ import ( "github.com/mattn/go-isatty" "github.com/spf13/cobra" gossh "golang.org/x/crypto/ssh" + gosshagent "golang.org/x/crypto/ssh/agent" "golang.org/x/term" "golang.org/x/xerrors" @@ -32,6 +33,7 @@ func ssh() *cobra.Command { var ( stdio bool shuffle bool + forwardAgent bool wsPollInterval time.Duration ) cmd := &cobra.Command{ @@ -108,6 +110,17 @@ func ssh() *cobra.Command { return err } + if forwardAgent && os.Getenv("SSH_AUTH_SOCK") != "" { + err = gosshagent.ForwardToRemote(sshClient, os.Getenv("SSH_AUTH_SOCK")) + if err != nil { + return xerrors.Errorf("forward agent failed: %w", err) + } + err = gosshagent.RequestAgentForwarding(sshSession) + if err != nil { + return xerrors.Errorf("request agent forwarding failed: %w", err) + } + } + stdoutFile, valid := cmd.OutOrStdout().(*os.File) if valid && isatty.IsTerminal(stdoutFile.Fd()) { state, err := term.MakeRaw(int(os.Stdin.Fd())) @@ -156,8 +169,9 @@ func ssh() *cobra.Command { } cliflag.BoolVarP(cmd.Flags(), &stdio, "stdio", "", "CODER_SSH_STDIO", false, "Specifies whether to emit SSH output over stdin/stdout.") cliflag.BoolVarP(cmd.Flags(), &shuffle, "shuffle", "", "CODER_SSH_SHUFFLE", false, "Specifies whether to choose a random workspace") - cliflag.DurationVarP(cmd.Flags(), &wsPollInterval, "workspace-poll-interval", "", "CODER_WORKSPACE_POLL_INTERVAL", workspacePollInterval, "Specifies how often to poll for workspace automated shutdown.") _ = cmd.Flags().MarkHidden("shuffle") + cliflag.BoolVarP(cmd.Flags(), &forwardAgent, "forward-agent", "", "CODER_SSH_FORWARD_AGENT", false, "Specifies whether to forward the SSH agent specified in $SSH_AUTH_SOCK") + cliflag.DurationVarP(cmd.Flags(), &wsPollInterval, "workspace-poll-interval", "", "CODER_WORKSPACE_POLL_INTERVAL", workspacePollInterval, "Specifies how often to poll for workspace automated shutdown.") return cmd } From 51c412865313a69d51f0952a9c3276f7c001f60f Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 14:30:43 +0300 Subject: [PATCH 03/11] refactor: Share setup between SSH tests, sync goroutines --- cli/ssh_test.go | 148 ++++++++++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 68 deletions(-) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 172d250ae8302..4c0fc27c57c0a 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -1,6 +1,7 @@ package cli_test import ( + "context" "io" "net" "runtime" @@ -14,6 +15,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/agent" "github.com/coder/coder/cli/clitest" "github.com/coder/coder/coderd/coderdtest" @@ -23,49 +25,52 @@ import ( "github.com/coder/coder/pty/ptytest" ) +func setupWorkspaceForSSH(t *testing.T) (*codersdk.Client, codersdk.Workspace, string) { + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) + user := coderdtest.CreateFirstUser(t, client) + agentToken := uuid.NewString() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionDryRun: echo.ProvisionComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "dev", + Type: "google_compute_instance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Auth: &proto.Agent_Token{ + Token: agentToken, + }, + }}, + }}, + }, + }, + }}, + }) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + + return client, workspace, agentToken +} + func TestSSH(t *testing.T) { - t.Skip("This is causing test flakes. TODO @cian fix this") t.Parallel() t.Run("ImmediateExit", func(t *testing.T) { t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) - user := coderdtest.CreateFirstUser(t, client) - agentToken := uuid.NewString() - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionDryRun: echo.ProvisionComplete, - Provision: []*proto.Provision_Response{{ - Type: &proto.Provision_Response_Complete{ - Complete: &proto.Provision_Complete{ - Resources: []*proto.Resource{{ - Name: "dev", - Type: "google_compute_instance", - Agents: []*proto.Agent{{ - Id: uuid.NewString(), - Auth: &proto.Agent_Token{ - Token: agentToken, - }, - }}, - }}, - }, - }, - }}, - }) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + client, workspace, agentToken := setupWorkspaceForSSH(t) cmd, root := clitest.New(t, "ssh", workspace.Name) clitest.SetupConfig(t, client, root) - doneChan := make(chan struct{}) pty := ptytest.New(t) cmd.SetIn(pty.Input()) cmd.SetErr(pty.Output()) cmd.SetOut(pty.Output()) - go func() { - defer close(doneChan) + tGo(t, func() { err := cmd.Execute() assert.NoError(t, err) - }() + }) pty.ExpectMatch("Waiting") coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) agentClient := codersdk.New(client.URL) @@ -78,37 +83,12 @@ func TestSSH(t *testing.T) { }) // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. pty.WriteLine("exit") - <-doneChan }) t.Run("Stdio", func(t *testing.T) { t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) - user := coderdtest.CreateFirstUser(t, client) - agentToken := uuid.NewString() - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionDryRun: echo.ProvisionComplete, - Provision: []*proto.Provision_Response{{ - Type: &proto.Provision_Response_Complete{ - Complete: &proto.Provision_Complete{ - Resources: []*proto.Resource{{ - Name: "dev", - Type: "google_compute_instance", - Agents: []*proto.Agent{{ - Id: uuid.NewString(), - Auth: &proto.Agent_Token{ - Token: agentToken, - }, - }}, - }}, - }, - }, - }}, - }) - coderdtest.AwaitTemplateVersionJob(t, client, version.ID) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) - go func() { + client, workspace, agentToken := setupWorkspaceForSSH(t) + + tGoContext(t, func(ctx context.Context) { // Run this async so the SSH command has to wait for // the build and agent to connect! coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) @@ -117,25 +97,22 @@ func TestSSH(t *testing.T) { agentCloser := agent.New(agentClient.ListenWorkspaceAgent, &agent.Options{ Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), }) - t.Cleanup(func() { - _ = agentCloser.Close() - }) - }() + <-ctx.Done() + _ = agentCloser.Close() + }) clientOutput, clientInput := io.Pipe() serverOutput, serverInput := io.Pipe() cmd, root := clitest.New(t, "ssh", "--stdio", workspace.Name) clitest.SetupConfig(t, client, root) - doneChan := make(chan struct{}) cmd.SetIn(clientOutput) cmd.SetOut(serverInput) cmd.SetErr(io.Discard) - go func() { - defer close(doneChan) + tGo(t, func() { err := cmd.Execute() assert.NoError(t, err) - }() + }) conn, channels, requests, err := ssh.NewClientConn(&stdioConn{ Reader: serverOutput, @@ -157,10 +134,45 @@ func TestSSH(t *testing.T) { err = sshClient.Close() require.NoError(t, err) _ = clientOutput.Close() - <-doneChan }) } +// tGoContext runs fn in a goroutine passing a context that will be +// canceled on test completion and wait until fn has finished executing. +// +// NOTE(mafredri): This could be moved to a helper library. +func tGoContext(t *testing.T, fn func(context.Context)) { + t.Helper() + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + t.Cleanup(func() { + cancel() + <-done + }) + go func() { + fn(ctx) + close(done) + }() +} + +// tGo runs fn in a goroutine and waits until fn has completed before +// test completion. +// +// NOTE(mafredri): This could be moved to a helper library. +func tGo(t *testing.T, fn func()) { + t.Helper() + + done := make(chan struct{}) + t.Cleanup(func() { + <-done + }) + go func() { + fn() + close(done) + }() +} + type stdioConn struct { io.Reader io.Writer From 8f0d4542864c921765f12a87cd4ffda4fd7094cd Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 15:43:35 +0300 Subject: [PATCH 04/11] feat: Add test for `coder ssh --forward-agent` --- cli/ssh_test.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 4c0fc27c57c0a..308f79d43e7e3 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -2,8 +2,13 @@ package cli_test import ( "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" "io" "net" + "path/filepath" "runtime" "testing" "time" @@ -12,6 +17,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" + gosshagent "golang.org/x/crypto/ssh/agent" "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" @@ -135,6 +141,84 @@ func TestSSH(t *testing.T) { require.NoError(t, err) _ = clientOutput.Close() }) + //nolint:paralleltest // Disabled due to use of t.Setenv. + t.Run("ForwardAgent", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Test not supported on windows") + } + + client, workspace, agentToken := setupWorkspaceForSSH(t) + + tGoContext(t, func(ctx context.Context) { + // Run this async so the SSH command has to wait for + // the build and agent to connect! + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + agentClient := codersdk.New(client.URL) + agentClient.SessionToken = agentToken + agentCloser := agent.New(agentClient.ListenWorkspaceAgent, &agent.Options{ + Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), + }) + <-ctx.Done() + _ = agentCloser.Close() + }) + + // Generate private key. + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + kr := gosshagent.NewKeyring() + kr.Add(gosshagent.AddedKey{ + PrivateKey: privateKey, + }) + + // Start up ssh agent listening on unix socket. + tmpdir := t.TempDir() + agentSock := filepath.Join(tmpdir, "agent.sock") + l, err := net.Listen("unix", agentSock) + require.NoError(t, err) + defer l.Close() + tGo(t, func() { + for { + fd, err := l.Accept() + if err != nil { + t.Logf("accept error: %v", err) + return + } + + err = gosshagent.ServeAgent(kr, fd) + if !errors.Is(err, io.EOF) { + assert.NoError(t, err) + } + } + }) + + t.Setenv("SSH_AUTH_SOCK", agentSock) + cmd, root := clitest.New(t, + "ssh", + workspace.Name, + "--forward-agent", + ) + clitest.SetupConfig(t, client, root) + pty := ptytest.New(t) + cmd.SetIn(pty.Input()) + cmd.SetOut(pty.Output()) + cmd.SetErr(io.Discard) + tGo(t, func() { + err := cmd.Execute() + assert.NoError(t, err) + }) + + // Ensure that SSH_AUTH_SOCK is set. + pty.WriteLine("env") + pty.ExpectMatch("SSH_AUTH_SOCK=/tmp") // E.g. /tmp/auth-agent3167016167/listener.sock + // Ensure that ssh-add lists our key. + pty.WriteLine("ssh-add -L") + keys, err := kr.List() + require.NoError(t, err) + pty.ExpectMatch(keys[0].String()) + + // kthxbye + pty.WriteLine("exit") + }) } // tGoContext runs fn in a goroutine passing a context that will be From ce9c05dccb289a0fe3ac887822b8c020e4d7b3e8 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 16:26:42 +0300 Subject: [PATCH 05/11] fix: Fix TestSSH/ImmediateExit flakyness with sleep --- cli/ssh_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 308f79d43e7e3..d09538ab5911e 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -87,8 +87,11 @@ func TestSSH(t *testing.T) { t.Cleanup(func() { _ = agentCloser.Close() }) + // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. pty.WriteLine("exit") + // Wait before closing agent to give `coder ssh` time to exit cleanly. + time.Sleep(3 * time.Second) }) t.Run("Stdio", func(t *testing.T) { t.Parallel() From f1596de59cc4b0a3ab3fdc62a93da5fe80e004c9 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 16:52:56 +0300 Subject: [PATCH 06/11] fix: Fix ForwardAgent test on macOS --- cli/ssh_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index d09538ab5911e..9d9e1165000f9 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -211,15 +211,17 @@ func TestSSH(t *testing.T) { }) // Ensure that SSH_AUTH_SOCK is set. + // Linux: /tmp/auth-agent3167016167/listener.sock + // macOS: /var/folders/ng/m1q0wft14hj0t3rtjxrdnzsr0000gn/T/auth-agent3245553419/listener.sock pty.WriteLine("env") - pty.ExpectMatch("SSH_AUTH_SOCK=/tmp") // E.g. /tmp/auth-agent3167016167/listener.sock + pty.ExpectMatch("SSH_AUTH_SOCK=") // Ensure that ssh-add lists our key. pty.WriteLine("ssh-add -L") keys, err := kr.List() require.NoError(t, err) pty.ExpectMatch(keys[0].String()) - // kthxbye + // And we're done. pty.WriteLine("exit") }) } From 5044d11d9b12833fe518ecc5035149aeb0f3310d Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 16:58:28 +0300 Subject: [PATCH 07/11] fix: Fix test flakes and implement Deans suggestion for helpers --- cli/ssh_test.go | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 9d9e1165000f9..609d1555d5334 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -73,7 +73,7 @@ func TestSSH(t *testing.T) { cmd.SetIn(pty.Input()) cmd.SetErr(pty.Output()) cmd.SetOut(pty.Output()) - tGo(t, func() { + cmdDone := tGo(t, func() { err := cmd.Execute() assert.NoError(t, err) }) @@ -90,14 +90,13 @@ func TestSSH(t *testing.T) { // Shells on Mac, Windows, and Linux all exit shells with the "exit" command. pty.WriteLine("exit") - // Wait before closing agent to give `coder ssh` time to exit cleanly. - time.Sleep(3 * time.Second) + <-cmdDone }) t.Run("Stdio", func(t *testing.T) { t.Parallel() client, workspace, agentToken := setupWorkspaceForSSH(t) - tGoContext(t, func(ctx context.Context) { + _, _ = tGoContext(t, func(ctx context.Context) { // Run this async so the SSH command has to wait for // the build and agent to connect! coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) @@ -118,7 +117,7 @@ func TestSSH(t *testing.T) { cmd.SetIn(clientOutput) cmd.SetOut(serverInput) cmd.SetErr(io.Discard) - tGo(t, func() { + cmdDone := tGo(t, func() { err := cmd.Execute() assert.NoError(t, err) }) @@ -143,6 +142,8 @@ func TestSSH(t *testing.T) { err = sshClient.Close() require.NoError(t, err) _ = clientOutput.Close() + + <-cmdDone }) //nolint:paralleltest // Disabled due to use of t.Setenv. t.Run("ForwardAgent", func(t *testing.T) { @@ -152,7 +153,7 @@ func TestSSH(t *testing.T) { client, workspace, agentToken := setupWorkspaceForSSH(t) - tGoContext(t, func(ctx context.Context) { + _, _ = tGoContext(t, func(ctx context.Context) { // Run this async so the SSH command has to wait for // the build and agent to connect! coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) @@ -179,7 +180,7 @@ func TestSSH(t *testing.T) { l, err := net.Listen("unix", agentSock) require.NoError(t, err) defer l.Close() - tGo(t, func() { + _ = tGo(t, func() { for { fd, err := l.Accept() if err != nil { @@ -205,7 +206,7 @@ func TestSSH(t *testing.T) { cmd.SetIn(pty.Input()) cmd.SetOut(pty.Output()) cmd.SetErr(io.Discard) - tGo(t, func() { + cmdDone := tGo(t, func() { err := cmd.Execute() assert.NoError(t, err) }) @@ -223,43 +224,51 @@ func TestSSH(t *testing.T) { // And we're done. pty.WriteLine("exit") + <-cmdDone }) } // tGoContext runs fn in a goroutine passing a context that will be // canceled on test completion and wait until fn has finished executing. +// Done and cancel are returned for optionally waiting until completion +// or early cancellation. // // NOTE(mafredri): This could be moved to a helper library. -func tGoContext(t *testing.T, fn func(context.Context)) { +func tGoContext(t *testing.T, fn func(context.Context)) (done <-chan struct{}, cancel context.CancelFunc) { t.Helper() ctx, cancel := context.WithCancel(context.Background()) - done := make(chan struct{}) + doneC := make(chan struct{}) t.Cleanup(func() { cancel() <-done }) go func() { fn(ctx) - close(done) + close(doneC) }() + + return doneC, cancel } // tGo runs fn in a goroutine and waits until fn has completed before -// test completion. +// test completion. Done is returned for optionally waiting for fn to +// exit. // // NOTE(mafredri): This could be moved to a helper library. -func tGo(t *testing.T, fn func()) { +func tGo(t *testing.T, fn func()) (done <-chan struct{}) { t.Helper() - done := make(chan struct{}) + doneC := make(chan struct{}) t.Cleanup(func() { - <-done + <-doneC }) go func() { fn() - close(done) + close(doneC) }() + + return doneC } type stdioConn struct { From e754672242bff5b543e3523be50c43c5545499af Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 19:58:27 +0300 Subject: [PATCH 08/11] fix: Add example to config-ssh --- cli/configssh.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cli/configssh.go b/cli/configssh.go index ed8f785b178de..f511dd07f47da 100644 --- a/cli/configssh.go +++ b/cli/configssh.go @@ -38,6 +38,11 @@ func configSSH() *cobra.Command { Annotations: workspaceCommand, Use: "config-ssh", Short: "Populate your SSH config with Host entries for all of your workspaces", + Example: ` + - You can use -o (or --ssh-option) so set SSH options to be used for all your + workspaces. + + ` + cliui.Styles.Code.Render("$ coder config-ssh -o ForwardAgent=yes"), RunE: func(cmd *cobra.Command, args []string) error { client, err := createClient(cmd) if err != nil { From 8d3df94701054d47bc7c8508bdc79acd929f0104 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 20:01:47 +0300 Subject: [PATCH 09/11] fix: Ignore closed errors in test --- cli/ssh_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index 609d1555d5334..c928d04af19b5 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -184,7 +184,9 @@ func TestSSH(t *testing.T) { for { fd, err := l.Accept() if err != nil { - t.Logf("accept error: %v", err) + if !errors.Is(err, net.ErrClosed) { + t.Logf("accept error: %v", err) + } return } From ea46d7b1c9ba7bacfedc75796c36bc2f89552291 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 24 May 2022 20:03:24 +0300 Subject: [PATCH 10/11] fix: Allow forwarding agent via -A --- cli/ssh.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/ssh.go b/cli/ssh.go index c17c5c8d697c4..85c3d9fd9b7b9 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -170,7 +170,7 @@ func ssh() *cobra.Command { cliflag.BoolVarP(cmd.Flags(), &stdio, "stdio", "", "CODER_SSH_STDIO", false, "Specifies whether to emit SSH output over stdin/stdout.") cliflag.BoolVarP(cmd.Flags(), &shuffle, "shuffle", "", "CODER_SSH_SHUFFLE", false, "Specifies whether to choose a random workspace") _ = cmd.Flags().MarkHidden("shuffle") - cliflag.BoolVarP(cmd.Flags(), &forwardAgent, "forward-agent", "", "CODER_SSH_FORWARD_AGENT", false, "Specifies whether to forward the SSH agent specified in $SSH_AUTH_SOCK") + cliflag.BoolVarP(cmd.Flags(), &forwardAgent, "forward-agent", "A", "CODER_SSH_FORWARD_AGENT", false, "Specifies whether to forward the SSH agent specified in $SSH_AUTH_SOCK") cliflag.DurationVarP(cmd.Flags(), &wsPollInterval, "workspace-poll-interval", "", "CODER_WORKSPACE_POLL_INTERVAL", workspacePollInterval, "Specifies how often to poll for workspace automated shutdown.") return cmd From 326b94282f4de6345b1b07e2a6b64ea56d4efd1b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 25 May 2022 21:15:07 +0300 Subject: [PATCH 11/11] Update cli/ssh_test.go Co-authored-by: Cian Johnston --- cli/ssh_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/ssh_test.go b/cli/ssh_test.go index c928d04af19b5..eae32161953d7 100644 --- a/cli/ssh_test.go +++ b/cli/ssh_test.go @@ -32,6 +32,7 @@ import ( ) func setupWorkspaceForSSH(t *testing.T) (*codersdk.Client, codersdk.Workspace, string) { + t.Helper() client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true}) user := coderdtest.CreateFirstUser(t, client) agentToken := uuid.NewString()