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

Commit34de3bc

Browse files
committed
feat: support --hostname-suffix flag on coder ssh
1 parenta2314ad commit34de3bc

File tree

5 files changed

+134
-55
lines changed

5 files changed

+134
-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: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func (r *RootCmd) ssh() *serpent.Command {
6363
var (
6464
stdiobool
6565
hostPrefixstring
66+
hostnameSuffixstring
6667
forwardAgentbool
6768
forwardGPGbool
6869
identityAgentstring
@@ -200,11 +201,14 @@ func (r *RootCmd) ssh() *serpent.Command {
200201
parsedEnv=append(parsedEnv, [2]string{k,v})
201202
}
202203

203-
namedWorkspace:=strings.TrimPrefix(inv.Args[0],hostPrefix)
204-
// Support "--" as a delimiter between owner and workspace name
205-
namedWorkspace=strings.Replace(namedWorkspace,"--","/",1)
204+
deploymentSSHConfig:= codersdk.SSHConfigResponse{
205+
HostnamePrefix:hostPrefix,
206+
HostnameSuffix:hostnameSuffix,
207+
}
206208

207-
workspace,workspaceAgent,err:=getWorkspaceAndAgent(ctx,inv,client,!disableAutostart,namedWorkspace)
209+
workspace,workspaceAgent,err:=findWorkspaceAndAgentByHostname(
210+
ctx,inv,client,
211+
inv.Args[0],deploymentSSHConfig,disableAutostart)
208212
iferr!=nil {
209213
returnerr
210214
}
@@ -563,6 +567,12 @@ func (r *RootCmd) ssh() *serpent.Command {
563567
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.",
564568
Value:serpent.StringOf(&hostPrefix),
565569
},
570+
{
571+
Flag:"hostname-suffix",
572+
Env:"CODER_SSH_HOSTNAME_SUFFIX",
573+
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 should be specified WITHOUT a leading . character.",
574+
Value:serpent.StringOf(&hostnameSuffix),
575+
},
566576
{
567577
Flag:"forward-agent",
568578
FlagShorthand:"A",
@@ -655,6 +665,34 @@ func (r *RootCmd) ssh() *serpent.Command {
655665
returncmd
656666
}
657667

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

‎cli/ssh_test.go

Lines changed: 69 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1647,67 +1647,85 @@ func TestSSH(t *testing.T) {
16471647
}
16481648
})
16491649

1650-
t.Run("SSHHostPrefix",func(t*testing.T) {
1650+
t.Run("SSHHost",func(t*testing.T) {
16511651
t.Parallel()
1652-
client,workspace,agentToken:=setupWorkspaceForAgent(t)
1653-
_,_=tGoContext(t,func(ctx context.Context) {
1654-
// Run this async so the SSH command has to wait for
1655-
// the build and agent to connect!
1656-
_=agenttest.New(t,client.URL,agentToken)
1657-
<-ctx.Done()
1658-
})
16591652

1660-
clientOutput,clientInput:=io.Pipe()
1661-
serverOutput,serverInput:=io.Pipe()
1662-
deferfunc() {
1663-
for_,c:=range []io.Closer{clientOutput,clientInput,serverOutput,serverInput} {
1664-
_=c.Close()
1665-
}
1666-
}()
1653+
testCases:= []struct {
1654+
name,hostnameFormatstring
1655+
flags []string
1656+
}{
1657+
{"Prefix","coder.dummy.com--%s--%s", []string{"--ssh-host-prefix","coder.dummy.com--"}},
1658+
{"Suffix","%s--%s.coder", []string{"--hostname-suffix","coder"}},
1659+
{"Both","%s--%s.coder", []string{"--hostname-suffix","coder","--ssh-host-prefix","coder.dummy.com--"}},
1660+
}
1661+
for_,tc:=rangetestCases {
1662+
t.Run(tc.name,func(t*testing.T) {
1663+
t.Parallel()
16671664

1668-
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
1669-
defercancel()
1665+
client,workspace,agentToken:=setupWorkspaceForAgent(t)
1666+
_,_=tGoContext(t,func(ctx context.Context) {
1667+
// Run this async so the SSH command has to wait for
1668+
// the build and agent to connect!
1669+
_=agenttest.New(t,client.URL,agentToken)
1670+
<-ctx.Done()
1671+
})
16701672

1671-
user,err:=client.User(ctx,codersdk.Me)
1672-
require.NoError(t,err)
1673+
clientOutput,clientInput:=io.Pipe()
1674+
serverOutput,serverInput:=io.Pipe()
1675+
deferfunc() {
1676+
for_,c:=range []io.Closer{clientOutput,clientInput,serverOutput,serverInput} {
1677+
_=c.Close()
1678+
}
1679+
}()
16731680

1674-
inv,root:=clitest.New(t,"ssh","--stdio","--ssh-host-prefix","coder.dummy.com--",fmt.Sprintf("coder.dummy.com--%s--%s",user.Username,workspace.Name))
1675-
clitest.SetupConfig(t,client,root)
1676-
inv.Stdin=clientOutput
1677-
inv.Stdout=serverInput
1678-
inv.Stderr=io.Discard
1681+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitLong)
1682+
defercancel()
16791683

1680-
cmdDone:=tGo(t,func() {
1681-
err:=inv.WithContext(ctx).Run()
1682-
assert.NoError(t,err)
1683-
})
1684+
user,err:=client.User(ctx,codersdk.Me)
1685+
require.NoError(t,err)
16841686

1685-
conn,channels,requests,err:=ssh.NewClientConn(&stdioConn{
1686-
Reader:serverOutput,
1687-
Writer:clientInput,
1688-
},"",&ssh.ClientConfig{
1689-
// #nosec
1690-
HostKeyCallback:ssh.InsecureIgnoreHostKey(),
1691-
})
1692-
require.NoError(t,err)
1693-
deferconn.Close()
1687+
args:= []string{"ssh","--stdio"}
1688+
args=append(args,tc.flags...)
1689+
args=append(args,fmt.Sprintf(tc.hostnameFormat,user.Username,workspace.Name))
1690+
inv,root:=clitest.New(t,args...)
1691+
clitest.SetupConfig(t,client,root)
1692+
inv.Stdin=clientOutput
1693+
inv.Stdout=serverInput
1694+
inv.Stderr=io.Discard
16941695

1695-
sshClient:=ssh.NewClient(conn,channels,requests)
1696-
session,err:=sshClient.NewSession()
1697-
require.NoError(t,err)
1698-
defersession.Close()
1696+
cmdDone:=tGo(t,func() {
1697+
err:=inv.WithContext(ctx).Run()
1698+
assert.NoError(t,err)
1699+
})
16991700

1700-
command:="sh -c exit"
1701-
ifruntime.GOOS=="windows" {
1702-
command="cmd.exe /c exit"
1703-
}
1704-
err=session.Run(command)
1705-
require.NoError(t,err)
1706-
err=sshClient.Close()
1707-
require.NoError(t,err)
1708-
_=clientOutput.Close()
1701+
conn,channels,requests,err:=ssh.NewClientConn(&stdioConn{
1702+
Reader:serverOutput,
1703+
Writer:clientInput,
1704+
},"",&ssh.ClientConfig{
1705+
// #nosec
1706+
HostKeyCallback:ssh.InsecureIgnoreHostKey(),
1707+
})
1708+
require.NoError(t,err)
1709+
deferconn.Close()
17091710

1710-
<-cmdDone
1711+
sshClient:=ssh.NewClient(conn,channels,requests)
1712+
session,err:=sshClient.NewSession()
1713+
require.NoError(t,err)
1714+
defersession.Close()
1715+
1716+
command:="sh -c exit"
1717+
ifruntime.GOOS=="windows" {
1718+
command="cmd.exe /c exit"
1719+
}
1720+
err=session.Run(command)
1721+
require.NoError(t,err)
1722+
err=sshClient.Close()
1723+
require.NoError(t,err)
1724+
_=clientOutput.Close()
1725+
1726+
<-cmdDone
1727+
})
1728+
}
17111729
})
17121730
}
17131731

‎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 should 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