diff --git a/docs/coder_config-ssh.md b/docs/coder_config-ssh.md index 1816b29a..4114239f 100644 --- a/docs/coder_config-ssh.md +++ b/docs/coder_config-ssh.md @@ -15,6 +15,7 @@ coder config-ssh [flags] ``` --filepath string override the default path of your ssh config file (default "~/.ssh/config") -h, --help help for config-ssh + -o, --option strings additional options injected in the ssh config (ex. disable caching with "-o ControlPath=none") --remove remove the auto-generated Coder ssh config ``` diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index 3fc693d1..8818c9e2 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -33,23 +33,25 @@ const sshEndToken = "# ------------END-CODER-ENTERPRISE------------" func configSSHCmd() *cobra.Command { var ( - configpath string - remove = false + configpath string + remove = false + additionalOptions []string ) cmd := &cobra.Command{ Use: "config-ssh", Short: "Configure SSH to access Coder workspaces", Long: "Inject the proper OpenSSH configuration into your local SSH config file.", - RunE: configSSH(&configpath, &remove), + RunE: configSSH(&configpath, &remove, &additionalOptions), } cmd.Flags().StringVar(&configpath, "filepath", filepath.Join("~", ".ssh", "config"), "override the default path of your ssh config file") + cmd.Flags().StringSliceVarP(&additionalOptions, "option", "o", []string{}, "additional options injected in the ssh config (ex. disable caching with \"-o ControlPath=none\")") cmd.Flags().BoolVar(&remove, "remove", false, "remove the auto-generated Coder ssh config") return cmd } -func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []string) error { +func configSSH(configpath *string, remove *bool, additionalOptions *[]string) func(cmd *cobra.Command, _ []string) error { return func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() usr, err := user.Current() @@ -118,7 +120,7 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st return xerrors.Errorf("Failed to get executable path: %w", err) } - newConfig := makeNewConfigs(binPath, workspacesWithProviders, privateKeyFilepath) + newConfig := makeNewConfigs(binPath, workspacesWithProviders, privateKeyFilepath, *additionalOptions) err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm) if err != nil { @@ -226,7 +228,7 @@ func writeSSHKey(ctx context.Context, client coder.Client, privateKeyPath string return ioutil.WriteFile(privateKeyPath, []byte(key.PrivateKey), 0600) } -func makeNewConfigs(binPath string, workspaces []coderutil.WorkspaceWithWorkspaceProvider, privateKeyFilepath string) string { +func makeNewConfigs(binPath string, workspaces []coderutil.WorkspaceWithWorkspaceProvider, privateKeyFilepath string, additionalOptions []string) string { newConfig := fmt.Sprintf("\n%s\n%s\n\n", sshStartToken, sshStartMessage) sort.Slice(workspaces, func(i, j int) bool { return workspaces[i].Workspace.Name < workspaces[j].Workspace.Name }) @@ -240,32 +242,41 @@ func makeNewConfigs(binPath string, workspaces []coderutil.WorkspaceWithWorkspac continue } - newConfig += makeSSHConfig(binPath, workspace.Workspace.Name, privateKeyFilepath) + newConfig += makeSSHConfig(binPath, workspace.Workspace.Name, privateKeyFilepath, additionalOptions) } newConfig += fmt.Sprintf("\n%s\n", sshEndToken) return newConfig } -func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string) string { - entry := fmt.Sprintf( - `Host coder.%s - HostName coder.%s - ProxyCommand "%s" tunnel %s 12213 stdio - StrictHostKeyChecking no - ConnectTimeout=0 - IdentitiesOnly yes - IdentityFile="%s" -`, workspaceName, workspaceName, binPath, workspaceName, privateKeyFilepath) +func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string, additionalOptions []string) string { + // Custom user options come first to maximizessh customization. + options := []string{} + if len(additionalOptions) > 0 { + options = []string{ + "# Custom options. Duplicated values will always prefer the first!", + } + options = append(options, additionalOptions...) + options = append(options, "# End custom options.") + } + options = append(options, + fmt.Sprintf("HostName coder.%s", workspaceName), + fmt.Sprintf("ProxyCommand %q tunnel %s 12213 stdio", binPath, workspaceName), + "StrictHostKeyChecking no", + "ConnectTimeout=0", + "IdentitiesOnly yes", + fmt.Sprintf("IdentityFile=%q", privateKeyFilepath), + ) if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { - entry += ` ControlMaster auto - ControlPath ~/.ssh/.connection-%r@%h:%p - ControlPersist 600 -` + options = append(options, + "ControlMaster auto", + "ControlPath ~/.ssh/.connection-%r@%h:%p", + "ControlPersist 600", + ) } - return entry + return fmt.Sprintf("Host coder.%s\n\t%s\n\n", workspaceName, strings.Join(options, "\n\t")) } func writeStr(filename, data string) error {