package auth import ( "fmt" "net" "strings" "time" "golang.org/x/crypto/ssh" ) const defaultSSHPort = "22" const sshDialTimeout = 10 * time.Second // TestSSHConnection dials host over SSH using the given private key and username. // Host key verification is intentionally skipped for this connectivity check; // trust-on-first-use / known_hosts can be added later. func TestSSHConnection(hostIP string, username string, privateKeyPEM string, passphrase string) error { hostIP = strings.TrimSpace(hostIP) username = strings.TrimSpace(username) if hostIP == "" { return fmt.Errorf("host IP is required") } if username == "" { return fmt.Errorf("username is required") } signer, err := parseSigner(privateKeyPEM, passphrase) if err != nil { return err } config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ ssh.PublicKeys(signer), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: sshDialTimeout, } address := net.JoinHostPort(hostIP, defaultSSHPort) client, err := ssh.Dial("tcp", address, config) if err != nil { return fmt.Errorf("ssh dial %s: %w", address, err) } defer client.Close() return nil } func parseSigner(privateKeyPEM string, passphrase string) (ssh.Signer, error) { trimmed := strings.TrimSpace(privateKeyPEM) if trimmed == "" { return nil, fmt.Errorf("private key is required") } var ( signer ssh.Signer err error ) if strings.TrimSpace(passphrase) == "" { signer, err = ssh.ParsePrivateKey([]byte(trimmed)) } else { signer, err = ssh.ParsePrivateKeyWithPassphrase( []byte(trimmed), []byte(passphrase), ) } if err != nil { return nil, fmt.Errorf("parse private key: %w", err) } return signer, nil }