Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd312e82

Browse files
authored
feat: support --hostname-suffix flag on coder ssh (#17279)
Adds `hostname-suffix` flag to `coder ssh` command for use in SSH Config ProxyCommands.Also enforces that Coder server doesn't start the suffix with a dot.part of:#16828
1 parentaa0a63a commitd312e82

File tree

5 files changed

+131
-55
lines changed

5 files changed

+131
-55
lines changed

‎cli/server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,15 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
620620
returnxerrors.Errorf("parse ssh config options %q: %w",vals.SSHConfig.SSHConfigOptions.String(),err)
621621
}
622622

623+
// The workspace hostname suffix is always interpreted as implicitly beginning with a single dot, so it is
624+
// a config error to explicitly include the dot. This ensures that we always interpret the suffix as a
625+
// separate DNS label, and not just an ordinary string suffix. E.g. a suffix of 'coder' will match
626+
// 'en.coder' but not 'encoder'.
627+
ifstrings.HasPrefix(vals.WorkspaceHostnameSuffix.String(),".") {
628+
returnxerrors.Errorf("you must omit any leading . in workspace hostname suffix: %s",
629+
vals.WorkspaceHostnameSuffix.String())
630+
}
631+
623632
options:=&coderd.Options{
624633
AccessURL:vals.AccessURL.Value(),
625634
AppHostname:appHostname,

‎cli/ssh.go

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func (r *RootCmd) ssh() *serpent.Command {
6565
var (
6666
stdiobool
6767
hostPrefixstring
68+
hostnameSuffixstring
6869
forwardAgentbool
6970
forwardGPGbool
7071
identityAgentstring
@@ -202,10 +203,14 @@ func (r *RootCmd) ssh() *serpent.Command {
202203
parsedEnv=append(parsedEnv, [2]string{k,v})
203204
}
204205

205-
workspaceInput:=strings.TrimPrefix(inv.Args[0],hostPrefix)
206-
// convert workspace name format into owner/workspace.agent
207-
namedWorkspace:=normalizeWorkspaceInput(workspaceInput)
208-
workspace,workspaceAgent,err:=getWorkspaceAndAgent(ctx,inv,client,!disableAutostart,namedWorkspace)
206+
deploymentSSHConfig:= codersdk.SSHConfigResponse{
207+
HostnamePrefix:hostPrefix,
208+
HostnameSuffix:hostnameSuffix,
209+
}
210+
211+
workspace,workspaceAgent,err:=findWorkspaceAndAgentByHostname(
212+
ctx,inv,client,
213+
inv.Args[0],deploymentSSHConfig,disableAutostart)
209214
iferr!=nil {
210215
returnerr
211216
}
@@ -564,6 +569,12 @@ func (r *RootCmd) ssh() *serpent.Command {
564569
Description:"Strip this prefix from the provided hostname to determine the workspace name. This is useful when used as part of an OpenSSH proxy command.",
565570
Value:serpent.StringOf(&hostPrefix),
566571
},
572+
{
573+
Flag:"hostname-suffix",
574+
Env:"CODER_SSH_HOSTNAME_SUFFIX",
575+
Description:"Strip this suffix from the provided hostname to determine the workspace name. This is useful when used as part of an OpenSSH proxy command. The suffix must be specified without a leading . character.",
576+
Value:serpent.StringOf(&hostnameSuffix),
577+
},
567578
{
568579
Flag:"forward-agent",
569580
FlagShorthand:"A",
@@ -656,6 +667,30 @@ func (r *RootCmd) ssh() *serpent.Command {
656667
returncmd
657668
}
658669

670+
// findWorkspaceAndAgentByHostname parses the hostname from the commandline and finds the workspace and agent it
671+
// corresponds to, taking into account any name prefixes or suffixes configured (e.g. myworkspace.coder, or
672+
// vscode-coder--myusername--myworkspace).
673+
funcfindWorkspaceAndAgentByHostname(
674+
ctx context.Context,inv*serpent.Invocation,client*codersdk.Client,
675+
hostnamestring,config codersdk.SSHConfigResponse,disableAutostartbool,
676+
) (
677+
codersdk.Workspace, codersdk.WorkspaceAgent,error,
678+
) {
679+
// for suffixes, we don't explicitly get the . and must add it. This is to ensure that the suffix is always
680+
// interpreted as a dotted label in DNS names, not just any string suffix. That is, a suffix of 'coder' will
681+
// match a hostname like 'en.coder', but not 'encoder'.
682+
qualifiedSuffix:="."+config.HostnameSuffix
683+
684+
switch {
685+
caseconfig.HostnamePrefix!=""&&strings.HasPrefix(hostname,config.HostnamePrefix):
686+
hostname=strings.TrimPrefix(hostname,config.HostnamePrefix)
687+
caseconfig.HostnameSuffix!=""&&strings.HasSuffix(hostname,qualifiedSuffix):
688+
hostname=strings.TrimSuffix(hostname,qualifiedSuffix)
689+
}
690+
hostname=normalizeWorkspaceInput(hostname)
691+
returngetWorkspaceAndAgent(ctx,inv,client,!disableAutostart,hostname)
692+
}
693+
659694
// watchAndClose ensures closer is called if the context is canceled or
660695
// the workspace reaches the stopped state.
661696
//

‎cli/ssh_test.go

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,67 +1690,85 @@ func TestSSH(t *testing.T) {
16901690
}
16911691
})
16921692

1693-
t.Run("SSHHostPrefix",func(t*testing.T) {
1693+
t.Run("SSHHost",func(t*testing.T) {
16941694
t.Parallel()
1695-
client,workspace,agentToken:=setupWorkspaceForAgent(t)
1696-
_,_=tGoContext(t,func(ctx context.Context) {
1697-
// Run this async so the SSH command has to wait for
1698-
// the build and agent to connect!
1699-
_=agenttest.New(t,client.URL,agentToken)
1700-
<-ctx.Done()
1701-
})
17021695

1703-
clientOutput,clientInput:=io.Pipe()
1704-
serverOutput,serverInput:=io.Pipe()
1705-
deferfunc() {
1706-
for_,c:=range []io.Closer{clientOutput,clientInput,serverOutput,serverInput} {
1707-
_=c.Close()
1708-
}
1709-
}()
1696+
testCases:= []struct {
1697+
name,hostnameFormatstring
1698+
flags []string
1699+
}{
1700+
{"Prefix","coder.dummy.com--%s--%s", []string{"--ssh-host-prefix","coder.dummy.com--"}},
1701+
{"Suffix","%s--%s.coder", []string{"--hostname-suffix","coder"}},
1702+
{"Both","%s--%s.coder", []string{"--hostname-suffix","coder","--ssh-host-prefix","coder.dummy.com--"}},
1703+
}
1704+
for_,tc:=rangetestCases {
1705+
t.Run(tc.name,func(t*testing.T) {
1706+
t.Parallel()
17101707

1711-
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
1712-
defercancel()
1708+
client,workspace,agentToken:=setupWorkspaceForAgent(t)
1709+
_,_=tGoContext(t,func(ctx context.Context) {
1710+
// Run this async so the SSH command has to wait for
1711+
// the build and agent to connect!
1712+
_=agenttest.New(t,client.URL,agentToken)
1713+
<-ctx.Done()
1714+
})
17131715

1714-
user,err:=client.User(ctx,codersdk.Me)
1715-
require.NoError(t,err)
1716+
clientOutput,clientInput:=io.Pipe()
1717+
serverOutput,serverInput:=io.Pipe()
1718+
deferfunc() {
1719+
for_,c:=range []io.Closer{clientOutput,clientInput,serverOutput,serverInput} {
1720+
_=c.Close()
1721+
}
1722+
}()
17161723

1717-
inv,root:=clitest.New(t,"ssh","--stdio","--ssh-host-prefix","coder.dummy.com--",fmt.Sprintf("coder.dummy.com--%s--%s",user.Username,workspace.Name))
1718-
clitest.SetupConfig(t,client,root)
1719-
inv.Stdin=clientOutput
1720-
inv.Stdout=serverInput
1721-
inv.Stderr=io.Discard
1724+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
1725+
defercancel()
17221726

1723-
cmdDone:=tGo(t,func() {
1724-
err:=inv.WithContext(ctx).Run()
1725-
assert.NoError(t,err)
1726-
})
1727+
user,err:=client.User(ctx,codersdk.Me)
1728+
require.NoError(t,err)
17271729

1728-
conn,channels,requests,err:=ssh.NewClientConn(&stdioConn{
1729-
Reader:serverOutput,
1730-
Writer:clientInput,
1731-
},"",&ssh.ClientConfig{
1732-
// #nosec
1733-
HostKeyCallback:ssh.InsecureIgnoreHostKey(),
1734-
})
1735-
require.NoError(t,err)
1736-
deferconn.Close()
1730+
args:= []string{"ssh","--stdio"}
1731+
args=append(args,tc.flags...)
1732+
args=append(args,fmt.Sprintf(tc.hostnameFormat,user.Username,workspace.Name))
1733+
inv,root:=clitest.New(t,args...)
1734+
clitest.SetupConfig(t,client,root)
1735+
inv.Stdin=clientOutput
1736+
inv.Stdout=serverInput
1737+
inv.Stderr=io.Discard
17371738

1738-
sshClient:=ssh.NewClient(conn,channels,requests)
1739-
session,err:=sshClient.NewSession()
1740-
require.NoError(t,err)
1741-
defersession.Close()
1739+
cmdDone:=tGo(t,func() {
1740+
err:=inv.WithContext(ctx).Run()
1741+
assert.NoError(t,err)
1742+
})
17421743

1743-
command:="sh -c exit"
1744-
ifruntime.GOOS=="windows" {
1745-
command="cmd.exe /c exit"
1746-
}
1747-
err=session.Run(command)
1748-
require.NoError(t,err)
1749-
err=sshClient.Close()
1750-
require.NoError(t,err)
1751-
_=clientOutput.Close()
1744+
conn,channels,requests,err:=ssh.NewClientConn(&stdioConn{
1745+
Reader:serverOutput,
1746+
Writer:clientInput,
1747+
},"",&ssh.ClientConfig{
1748+
// #nosec
1749+
HostKeyCallback:ssh.InsecureIgnoreHostKey(),
1750+
})
1751+
require.NoError(t,err)
1752+
deferconn.Close()
17521753

1753-
<-cmdDone
1754+
sshClient:=ssh.NewClient(conn,channels,requests)
1755+
session,err:=sshClient.NewSession()
1756+
require.NoError(t,err)
1757+
defersession.Close()
1758+
1759+
command:="sh -c exit"
1760+
ifruntime.GOOS=="windows" {
1761+
command="cmd.exe /c exit"
1762+
}
1763+
err=session.Run(command)
1764+
require.NoError(t,err)
1765+
err=sshClient.Close()
1766+
require.NoError(t,err)
1767+
_=clientOutput.Close()
1768+
1769+
<-cmdDone
1770+
})
1771+
}
17541772
})
17551773
}
17561774

‎cli/testdata/coder_ssh_--help.golden

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ OPTIONS:
2323
locally and will not be started for you. If a GPG agent is already
2424
running in the workspace, it will be attempted to be killed.
2525

26+
--hostname-suffix string, $CODER_SSH_HOSTNAME_SUFFIX
27+
Strip this suffix from the provided hostname to determine the
28+
workspace name. This is useful when used as part of an OpenSSH proxy
29+
command. The suffix must be specified without a leading . character.
30+
2631
--identity-agent string, $CODER_SSH_IDENTITY_AGENT
2732
Specifies which identity agent to use (overrides $SSH_AUTH_SOCK),
2833
forward agent must also be enabled.

‎docs/reference/cli/ssh.md

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

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp