Skip to content

Commit 07d5dc8

Browse files
committed
fix(tailnet): update WorkspaceAgentSSHPort to 22
Change-Id: Ifd986b260f8ac317e37d65111cd4e0bd1dc38af8 Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent cccdf1e commit 07d5dc8

File tree

5 files changed

+141
-16
lines changed

5 files changed

+141
-16
lines changed

agent/agent.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,19 +1360,22 @@ func (a *agent) createTailnet(
13601360
return nil, xerrors.Errorf("update host signer: %w", err)
13611361
}
13621362

1363-
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentSSHPort))
1364-
if err != nil {
1365-
return nil, xerrors.Errorf("listen on the ssh port: %w", err)
1366-
}
1367-
defer func() {
1363+
for _, port := range []int{workspacesdk.AgentSSHPort, workspacesdk.AgentStandardSSHPort} {
1364+
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(port))
13681365
if err != nil {
1369-
_ = sshListener.Close()
1366+
return nil, xerrors.Errorf("listen on the ssh port (%v): %w", port, err)
1367+
}
1368+
// nolint:revive // We do want to run the deferred functions when createTailnet returns.
1369+
defer func() {
1370+
if err != nil {
1371+
_ = sshListener.Close()
1372+
}
1373+
}()
1374+
if err = a.trackGoroutine(func() {
1375+
_ = a.sshServer.Serve(sshListener)
1376+
}); err != nil {
1377+
return nil, err
13701378
}
1371-
}()
1372-
if err = a.trackGoroutine(func() {
1373-
_ = a.sshServer.Serve(sshListener)
1374-
}); err != nil {
1375-
return nil, err
13761379
}
13771380

13781381
reconnectingPTYListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentReconnectingPTYPort))

cli/configssh_test.go

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,118 @@ func TestConfigSSH(t *testing.T) {
169169
<-copyDone
170170
}
171171

172+
func TestConfigSSHOnPort22(t *testing.T) {
173+
t.Parallel()
174+
175+
if runtime.GOOS == "windows" {
176+
t.Skip("See coder/internal#117")
177+
}
178+
179+
const hostname = "test-coder."
180+
const expectedKey = "ConnectionAttempts"
181+
const removeKey = "ConnectTimeout"
182+
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
183+
ConfigSSH: codersdk.SSHConfigResponse{
184+
HostnamePrefix: hostname,
185+
SSHConfigOptions: map[string]string{
186+
// Something we can test for
187+
expectedKey: "3",
188+
removeKey: "",
189+
},
190+
},
191+
})
192+
owner := coderdtest.CreateFirstUser(t, client)
193+
member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
194+
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
195+
OrganizationID: owner.OrganizationID,
196+
OwnerID: memberUser.ID,
197+
}).WithAgent().Do()
198+
_ = agenttest.New(t, client.URL, r.AgentToken)
199+
resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
200+
agentConn, err := workspacesdk.New(client).
201+
DialAgent(context.Background(), resources[0].Agents[0].ID, nil)
202+
require.NoError(t, err)
203+
defer agentConn.Close()
204+
205+
listener, err := net.Listen("tcp", "127.0.0.1:0")
206+
require.NoError(t, err)
207+
defer func() {
208+
_ = listener.Close()
209+
}()
210+
copyDone := make(chan struct{})
211+
go func() {
212+
defer close(copyDone)
213+
var wg sync.WaitGroup
214+
for {
215+
conn, err := listener.Accept()
216+
if err != nil {
217+
break
218+
}
219+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
220+
ssh, err := agentConn.SSHOnPort(ctx, workspacesdk.AgentStandardSSHPort)
221+
cancel()
222+
assert.NoError(t, err)
223+
wg.Add(2)
224+
go func() {
225+
defer wg.Done()
226+
_, _ = io.Copy(conn, ssh)
227+
}()
228+
go func() {
229+
defer wg.Done()
230+
_, _ = io.Copy(ssh, conn)
231+
}()
232+
}
233+
wg.Wait()
234+
}()
235+
236+
sshConfigFile := sshConfigFileName(t)
237+
238+
tcpAddr, valid := listener.Addr().(*net.TCPAddr)
239+
require.True(t, valid)
240+
inv, root := clitest.New(t, "config-ssh",
241+
"--ssh-option", "HostName "+tcpAddr.IP.String(),
242+
"--ssh-option", "Port "+strconv.Itoa(tcpAddr.Port),
243+
"--ssh-config-file", sshConfigFile,
244+
"--skip-proxy-command")
245+
clitest.SetupConfig(t, member, root)
246+
pty := ptytest.New(t)
247+
inv.Stdin = pty.Input()
248+
inv.Stdout = pty.Output()
249+
250+
waiter := clitest.StartWithWaiter(t, inv)
251+
252+
matches := []struct {
253+
match, write string
254+
}{
255+
{match: "Continue?", write: "yes"},
256+
}
257+
for _, m := range matches {
258+
pty.ExpectMatch(m.match)
259+
pty.WriteLine(m.write)
260+
}
261+
262+
waiter.RequireSuccess()
263+
264+
fileContents, err := os.ReadFile(sshConfigFile)
265+
require.NoError(t, err, "read ssh config file")
266+
require.Contains(t, string(fileContents), expectedKey, "ssh config file contains expected key")
267+
require.NotContains(t, string(fileContents), removeKey, "ssh config file should not have removed key")
268+
269+
home := filepath.Dir(filepath.Dir(sshConfigFile))
270+
// #nosec
271+
sshCmd := exec.Command("ssh", "-F", sshConfigFile, hostname+r.Workspace.Name, "echo", "test")
272+
pty = ptytest.New(t)
273+
// Set HOME because coder config is included from ~/.ssh/coder.
274+
sshCmd.Env = append(sshCmd.Env, fmt.Sprintf("HOME=%s", home))
275+
inv.Stderr = pty.Output()
276+
data, err := sshCmd.Output()
277+
require.NoError(t, err)
278+
require.Equal(t, "test", strings.TrimSpace(string(data)))
279+
280+
_ = listener.Close()
281+
<-copyDone
282+
}
283+
172284
func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
173285
t.Parallel()
174286

@@ -317,7 +429,8 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
317429
strings.Join([]string{
318430
headerEnd,
319431
"",
320-
}, "\n")},
432+
}, "\n"),
433+
},
321434
},
322435
args: []string{"--ssh-option", "ForwardAgent=yes"},
323436
matches: []match{
@@ -342,7 +455,8 @@ func TestConfigSSH_FileWriteAndOptionsFlow(t *testing.T) {
342455
strings.Join([]string{
343456
headerEnd,
344457
"",
345-
}, "\n")},
458+
}, "\n"),
459+
},
346460
},
347461
args: []string{"--ssh-option", "ForwardAgent=yes"},
348462
matches: []match{

codersdk/workspacesdk/agentconn.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,15 +165,21 @@ func (c *AgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID, height, w
165165
// SSH pipes the SSH protocol over the returned net.Conn.
166166
// This connects to the built-in SSH server in the workspace agent.
167167
func (c *AgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) {
168+
return c.SSHOnPort(ctx, AgentSSHPort)
169+
}
170+
171+
// SSHOnPort pipes the SSH protocol over the returned net.Conn.
172+
// This connects to the built-in SSH server in the workspace agent on the specified port.
173+
func (c *AgentConn) SSHOnPort(ctx context.Context, port uint16) (*gonet.TCPConn, error) {
168174
ctx, span := tracing.StartSpan(ctx)
169175
defer span.End()
170176

171177
if !c.AwaitReachable(ctx) {
172178
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
173179
}
174180

175-
c.Conn.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSSH)
176-
return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSSHPort))
181+
c.SendConnectedTelemetry(c.agentAddress(), tailnet.TelemetryApplicationSSH)
182+
return c.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), port))
177183
}
178184

179185
// SSHClient calls SSH to create a client that uses a weak cipher

codersdk/workspacesdk/workspacesdk.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var ErrSkipClose = xerrors.New("skip tailnet close")
3131

3232
const (
3333
AgentSSHPort = tailnet.WorkspaceAgentSSHPort
34+
AgentStandardSSHPort = tailnet.WorkspaceAgentStandardSSHPort
3435
AgentReconnectingPTYPort = tailnet.WorkspaceAgentReconnectingPTYPort
3536
AgentSpeedtestPort = tailnet.WorkspaceAgentSpeedtestPort
3637
// AgentHTTPAPIServerPort serves a HTTP server with endpoints for e.g.

tailnet/conn.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const (
5252
WorkspaceAgentSSHPort = 1
5353
WorkspaceAgentReconnectingPTYPort = 2
5454
WorkspaceAgentSpeedtestPort = 3
55+
WorkspaceAgentStandardSSHPort = 22
5556
)
5657

5758
// EnvMagicsockDebugLogging enables super-verbose logging for the magicsock
@@ -745,7 +746,7 @@ func (c *Conn) forwardTCP(src, dst netip.AddrPort) (handler func(net.Conn), opts
745746
return nil, nil, false
746747
}
747748
// See: https://github.com/tailscale/tailscale/blob/c7cea825aea39a00aca71ea02bab7266afc03e7c/wgengine/netstack/netstack.go#L888
748-
if dst.Port() == WorkspaceAgentSSHPort || dst.Port() == 22 {
749+
if dst.Port() == WorkspaceAgentSSHPort || dst.Port() == WorkspaceAgentStandardSSHPort {
749750
opt := tcpip.KeepaliveIdleOption(72 * time.Hour)
750751
opts = append(opts, &opt)
751752
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy