From a3de9768f29032512d7ba7bd062257b40261bdd9 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Fri, 7 Jun 2024 02:43:59 -0700 Subject: [PATCH] fix: allow ssh-agent to work in prod This runs a separate shell script to get the correct environment variable instead of relying on one that comes from the environment waveterm was launched from. It also corrects the number of retries to adjust for the number of signers provided by the ssh-agent. --- wavesrv/pkg/remote/remote.go | 8 +++++--- wavesrv/pkg/remote/sshclient.go | 26 ++++++++++++++------------ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index f738723af..fa557ebfc 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -1314,14 +1314,15 @@ func (wsh *WaveshellProc) RunInstall(autoInstall bool) { wsh.WriteToPtyBuffer("*error: cannot install on a local remote\n") return } - _, err = shellapi.MakeShellApi(packet.ShellType_bash) + sapi, err := shellapi.MakeShellApi(wsh.GetShellType()) if err != nil { wsh.WriteToPtyBuffer("*error: %v\n", err) return } if wsh.Client == nil { remoteDisplayName := fmt.Sprintf("%s [%s]", remoteCopy.RemoteAlias, remoteCopy.RemoteCanonicalName) - client, err := ConnectToClient(makeClientCtx, remoteCopy.SSHOpts, remoteDisplayName) + sshAuthSock, _ := exec.CommandContext(makeClientCtx, sapi.GetLocalShellPath(), "-c", "echo \"${SSH_AUTH_SOCK}\"").CombinedOutput() + client, err := ConnectToClient(makeClientCtx, remoteCopy.SSHOpts, remoteDisplayName, strings.TrimSpace(string(sshAuthSock))) if err != nil { statusErr := fmt.Errorf("ssh cannot connect to client: %w", err) wsh.setInstallErrorStatus(statusErr) @@ -1614,7 +1615,8 @@ func (wsh *WaveshellProc) createWaveshellSession(clientCtx context.Context, remo wsSession = shexec.CmdWrap{Cmd: ecmd} } else if wsh.Client == nil { remoteDisplayName := fmt.Sprintf("%s [%s]", remoteCopy.RemoteAlias, remoteCopy.RemoteCanonicalName) - client, err := ConnectToClient(clientCtx, remoteCopy.SSHOpts, remoteDisplayName) + sshAuthSock, _ := exec.CommandContext(clientCtx, sapi.GetLocalShellPath(), "-c", "echo \"${SSH_AUTH_SOCK}\"").CombinedOutput() + client, err := ConnectToClient(clientCtx, remoteCopy.SSHOpts, remoteDisplayName, strings.TrimSpace(string(sshAuthSock))) if err != nil { return nil, fmt.Errorf("ssh cannot connect to client: %w", err) } diff --git a/wavesrv/pkg/remote/sshclient.go b/wavesrv/pkg/remote/sshclient.go index 3c720b0bf..f0c053e10 100644 --- a/wavesrv/pkg/remote/sshclient.go +++ b/wavesrv/pkg/remote/sshclient.go @@ -66,7 +66,7 @@ func createDummySigner() ([]ssh.Signer, error) { // they were successes. An error in this function prevents any other // keys from being attempted. But if there's an error because of a dummy // file, the library can still try again with a new key. -func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, passphrase string) func() ([]ssh.Signer, error) { +func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, passphrase string, authSockSignersExt []ssh.Signer) func() ([]ssh.Signer, error) { var identityFiles []string existingKeys := make(map[string][]byte) @@ -84,15 +84,8 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, // require pointer to modify list in closure identityFilesPtr := &identityFiles - sshAuthSock := os.Getenv("SSH_AUTH_SOCK") - conn, err := net.Dial("unix", sshAuthSock) var authSockSigners []ssh.Signer - if err != nil { - log.Printf("Failed to open SSH_AUTH_SOCK: %v", err) - } else { - agentClient := agent.NewClient(conn) - authSockSigners, _ = agentClient.Signers() - } + authSockSigners = append(authSockSigners, authSockSignersExt...) authSockSignersPtr := &authSockSigners return func() ([]ssh.Signer, error) { @@ -546,7 +539,7 @@ func DialContext(ctx context.Context, network string, addr string, config *ssh.C return ssh.NewClient(c, chans, reqs), nil } -func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Client, error) { +func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDisplayName string, sshAuthSock string) (*ssh.Client, error) { sshConfigKeywords, err := findSshConfigKeywords(opts.SSHHost) if err != nil { return nil, err @@ -557,7 +550,16 @@ func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDispla return nil, err } - publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(connCtx, sshKeywords, opts.SSHPassword)) + conn, err := net.Dial("unix", sshAuthSock) + var authSockSigners []ssh.Signer + if err != nil { + log.Printf("Failed to open SSH_AUTH_SOCK: %v", err) + } else { + agentClient := agent.NewClient(conn) + authSockSigners, _ = agentClient.Signers() + } + + publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(connCtx, sshKeywords, opts.SSHPassword, authSockSigners)) keyboardInteractive := ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(connCtx, opts.SSHPassword, remoteDisplayName)) passwordCallback := ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(connCtx, opts.SSHPassword, remoteDisplayName)) @@ -572,7 +574,7 @@ func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDispla // exclude gssapi-with-mic and hostbased until implemented authMethodMap := map[string]ssh.AuthMethod{ - "publickey": ssh.RetryableAuthMethod(publicKeyCallback, len(sshKeywords.IdentityFile)), + "publickey": ssh.RetryableAuthMethod(publicKeyCallback, len(sshKeywords.IdentityFile)+len(authSockSigners)), "keyboard-interactive": ssh.RetryableAuthMethod(keyboardInteractive, attemptsAllowed), "password": ssh.RetryableAuthMethod(passwordCallback, attemptsAllowed), }