From e071f1c5a7d3ebe8b03ffe6ab9bb680d02eef8cb Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 9 Aug 2021 04:22:12 +0000 Subject: [PATCH 1/4] feat: Add option to disable SSH connection cache --- internal/cmd/configssh.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index 3fc693d1..37733ac6 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -34,6 +34,7 @@ const sshEndToken = "# ------------END-CODER-ENTERPRISE------------" func configSSHCmd() *cobra.Command { var ( configpath string + noCache = false remove = false ) @@ -41,15 +42,16 @@ func configSSHCmd() *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, &noCache), } cmd.Flags().StringVar(&configpath, "filepath", filepath.Join("~", ".ssh", "config"), "override the default path of your ssh config file") + cmd.Flags().BoolVar(&noCache, "no-cache", false, "disable ssh connection caching") 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, noCache *bool) 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, *noCache) 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, noCache bool) 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,14 +242,14 @@ func makeNewConfigs(binPath string, workspaces []coderutil.WorkspaceWithWorkspac continue } - newConfig += makeSSHConfig(binPath, workspace.Workspace.Name, privateKeyFilepath) + newConfig += makeSSHConfig(binPath, workspace.Workspace.Name, privateKeyFilepath, noCache) } newConfig += fmt.Sprintf("\n%s\n", sshEndToken) return newConfig } -func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string) string { +func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string, noCache bool) string { entry := fmt.Sprintf( `Host coder.%s HostName coder.%s @@ -258,7 +260,7 @@ func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string) string { IdentityFile="%s" `, workspaceName, workspaceName, binPath, workspaceName, privateKeyFilepath) - if runtime.GOOS == "linux" || runtime.GOOS == "darwin" { + if !noCache && (runtime.GOOS == "linux" || runtime.GOOS == "darwin") { entry += ` ControlMaster auto ControlPath ~/.ssh/.connection-%r@%h:%p ControlPersist 600 From 33e15b33cbacdb5594037f0c3a999838cd837ad4 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 9 Aug 2021 04:44:17 +0000 Subject: [PATCH 2/4] Refactor to enable arbitrary options --- internal/cmd/configssh.go | 54 ++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index 37733ac6..9920d58b 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -33,25 +33,25 @@ const sshEndToken = "# ------------END-CODER-ENTERPRISE------------" func configSSHCmd() *cobra.Command { var ( - configpath string - noCache = false - 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, &noCache), + 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().BoolVar(&noCache, "no-cache", false, "disable ssh connection caching") + 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, noCache *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() @@ -120,7 +120,7 @@ func configSSH(configpath *string, remove *bool, noCache *bool) func(cmd *cobra. return xerrors.Errorf("Failed to get executable path: %w", err) } - newConfig := makeNewConfigs(binPath, workspacesWithProviders, privateKeyFilepath, *noCache) + newConfig := makeNewConfigs(binPath, workspacesWithProviders, privateKeyFilepath, *additionalOptions) err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm) if err != nil { @@ -228,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, noCache bool) 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 }) @@ -242,32 +242,34 @@ func makeNewConfigs(binPath string, workspaces []coderutil.WorkspaceWithWorkspac continue } - newConfig += makeSSHConfig(binPath, workspace.Workspace.Name, privateKeyFilepath, noCache) + newConfig += makeSSHConfig(binPath, workspace.Workspace.Name, privateKeyFilepath, additionalOptions) } newConfig += fmt.Sprintf("\n%s\n", sshEndToken) return newConfig } -func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string, noCache bool) 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) - - if !noCache && (runtime.GOOS == "linux" || runtime.GOOS == "darwin") { - entry += ` ControlMaster auto - ControlPath ~/.ssh/.connection-%r@%h:%p - ControlPersist 600 -` +func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string, additionalOptions []string) string { + // Custom user options come first to maximizessh customization. + options := additionalOptions + 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" { + 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 { From 840bf994a2466fc7bdd89b1bcd66dfae93265c24 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 9 Aug 2021 06:10:42 +0000 Subject: [PATCH 3/4] Generate docs --- docs/coder_config-ssh.md | 1 + 1 file changed, 1 insertion(+) 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 ``` From 4410549b4d62e55765606df5a2bf74da3ef8f4bf Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 9 Aug 2021 06:15:04 +0000 Subject: [PATCH 4/4] Add comment for duplicated values --- internal/cmd/configssh.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index 9920d58b..8818c9e2 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -251,7 +251,14 @@ func makeNewConfigs(binPath string, workspaces []coderutil.WorkspaceWithWorkspac func makeSSHConfig(binPath, workspaceName, privateKeyFilepath string, additionalOptions []string) string { // Custom user options come first to maximizessh customization. - options := additionalOptions + 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),