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
This commit is contained in:
Spike Curtis
2025-04-07 21:33:33 +04:00
committed by GitHub
parent aa0a63a295
commit d312e82a51
5 changed files with 137 additions and 61 deletions

View File

@ -65,6 +65,7 @@ func (r *RootCmd) ssh() *serpent.Command {
var (
stdio bool
hostPrefix string
hostnameSuffix string
forwardAgent bool
forwardGPG bool
identityAgent string
@ -202,10 +203,14 @@ func (r *RootCmd) ssh() *serpent.Command {
parsedEnv = append(parsedEnv, [2]string{k, v})
}
workspaceInput := strings.TrimPrefix(inv.Args[0], hostPrefix)
// convert workspace name format into owner/workspace.agent
namedWorkspace := normalizeWorkspaceInput(workspaceInput)
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, inv, client, !disableAutostart, namedWorkspace)
deploymentSSHConfig := codersdk.SSHConfigResponse{
HostnamePrefix: hostPrefix,
HostnameSuffix: hostnameSuffix,
}
workspace, workspaceAgent, err := findWorkspaceAndAgentByHostname(
ctx, inv, client,
inv.Args[0], deploymentSSHConfig, disableAutostart)
if err != nil {
return err
}
@ -564,6 +569,12 @@ func (r *RootCmd) ssh() *serpent.Command {
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.",
Value: serpent.StringOf(&hostPrefix),
},
{
Flag: "hostname-suffix",
Env: "CODER_SSH_HOSTNAME_SUFFIX",
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.",
Value: serpent.StringOf(&hostnameSuffix),
},
{
Flag: "forward-agent",
FlagShorthand: "A",
@ -656,6 +667,30 @@ func (r *RootCmd) ssh() *serpent.Command {
return cmd
}
// findWorkspaceAndAgentByHostname parses the hostname from the commandline and finds the workspace and agent it
// corresponds to, taking into account any name prefixes or suffixes configured (e.g. myworkspace.coder, or
// vscode-coder--myusername--myworkspace).
func findWorkspaceAndAgentByHostname(
ctx context.Context, inv *serpent.Invocation, client *codersdk.Client,
hostname string, config codersdk.SSHConfigResponse, disableAutostart bool,
) (
codersdk.Workspace, codersdk.WorkspaceAgent, error,
) {
// for suffixes, we don't explicitly get the . and must add it. This is to ensure that the suffix is always
// interpreted as a dotted label in DNS names, not just any string suffix. That is, a suffix of 'coder' will
// match a hostname like 'en.coder', but not 'encoder'.
qualifiedSuffix := "." + config.HostnameSuffix
switch {
case config.HostnamePrefix != "" && strings.HasPrefix(hostname, config.HostnamePrefix):
hostname = strings.TrimPrefix(hostname, config.HostnamePrefix)
case config.HostnameSuffix != "" && strings.HasSuffix(hostname, qualifiedSuffix):
hostname = strings.TrimSuffix(hostname, qualifiedSuffix)
}
hostname = normalizeWorkspaceInput(hostname)
return getWorkspaceAndAgent(ctx, inv, client, !disableAutostart, hostname)
}
// watchAndClose ensures closer is called if the context is canceled or
// the workspace reaches the stopped state.
//