fix: use SSH agent for authentication

Try SSH_AUTH_SOCK agent first (handles macOS Keychain, ssh-add, and
custom key paths configured in the shell). Fall back to common key
file names (id_ed25519, id_ecdsa, id_rsa) if no agent is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SW 2026-06-11 10:16:28 +02:00
parent c6657daa96
commit 1501b15a9d

View file

@ -3,11 +3,13 @@ package sshclient
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"net"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
) )
type Client struct { type Client struct {
@ -140,17 +142,23 @@ func parseTarget(target string) (user, host string, err error) {
} }
func buildAuthMethods(keyPath string) ([]ssh.AuthMethod, error) { func buildAuthMethods(keyPath string) ([]ssh.AuthMethod, error) {
candidates := []string{} var methods []ssh.AuthMethod
// SSH agent — uses whatever keys are loaded in the agent (macOS Keychain, ssh-add, etc.)
if m := agentAuth(); m != nil {
methods = append(methods, m)
}
// Explicit key file or common defaults
var candidates []string
if keyPath != "" { if keyPath != "" {
candidates = append(candidates, keyPath) candidates = append(candidates, keyPath)
} else { } else {
home, _ := os.UserHomeDir() home, _ := os.UserHomeDir()
candidates = append(candidates, for _, name := range []string{"id_ed25519", "id_ecdsa", "id_rsa"} {
filepath.Join(home, ".ssh", "id_ed25519"), candidates = append(candidates, filepath.Join(home, ".ssh", name))
filepath.Join(home, ".ssh", "id_rsa"), }
)
} }
for _, p := range candidates { for _, p := range candidates {
data, err := os.ReadFile(p) data, err := os.ReadFile(p)
if err != nil { if err != nil {
@ -160,10 +168,26 @@ func buildAuthMethods(keyPath string) ([]ssh.AuthMethod, error) {
if err != nil { if err != nil {
continue continue
} }
return []ssh.AuthMethod{ssh.PublicKeys(signer)}, nil methods = append(methods, ssh.PublicKeys(signer))
} }
return nil, fmt.Errorf("no usable SSH private key found (tried: %v)", candidates) if len(methods) == 0 {
return nil, fmt.Errorf("no SSH auth method available — no agent (SSH_AUTH_SOCK) and no key files found in %v", candidates)
}
return methods, nil
}
// agentAuth returns an auth method backed by the SSH agent if SSH_AUTH_SOCK is set.
func agentAuth() ssh.AuthMethod {
sock := os.Getenv("SSH_AUTH_SOCK")
if sock == "" {
return nil
}
conn, err := net.Dial("unix", sock)
if err != nil {
return nil
}
return ssh.PublicKeysCallback(agent.NewClient(conn).Signers)
} }
func parseMeminfo(raw string) (totalMB, usedMB int64) { func parseMeminfo(raw string) (totalMB, usedMB int64) {