ssh-client-go

module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 8, 2026 License: MIT

README

ssh-client-go

Go Report Card Release Go Reference

A unified SSH client library for Go that provides seamless key discovery and signing operations across ssh-agent and filesystem keys.

Features

  • Unified Key Discovery: Discovers keys from both ssh-agent and filesystem (~/.ssh/id_*)
  • Passphrase Support: Interactive passphrase prompts for encrypted private keys
  • Standard SSH Behavior: Follows OpenSSH client key discovery and iteration patterns
  • Custom Key Paths: Support for non-standard key locations
  • Hardware Keys: Works with hardware-backed SSH keys (PKCS#11, PIV cards)
  • Flexible Configuration: Environment variables and programmatic configuration
  • Comprehensive Error Handling: Detailed error messages for debugging

Installation

go get github.com/nikogura/ssh-client-go/pkg/sshclient

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/nikogura/ssh-client-go/pkg/sshclient"
)

func main() {
    // Create client with default config (agent + filesystem keys)
    config := &sshclient.Config{
        UseAgent:       true,  // Try ssh-agent first
        IdentitiesOnly: false, // Also try filesystem keys
    }

    client, err := sshclient.NewUnifiedSSHClient(config)
    if err != nil {
        log.Fatal(err)
    }

    // Discover all available keys
    keys, err := client.GetKeys()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Found %d SSH keys\n", len(keys))
    for _, key := range keys {
        fmt.Printf("  - %s (%s)\n", key.Fingerprint, key.Source)
    }

    // Sign data with the first key
    data := []byte("hello world")
    signature, pubKey, err := client.SignWithKey(keys[0], data)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Signed with key: %s\n", key.Fingerprint)
}

Configuration

Using Environment Variables
config := &sshclient.Config{
    UseAgent:       os.Getenv("SSH_USE_AGENT") != "false",
    IdentitiesOnly: os.Getenv("SSH_IDENTITIES_ONLY") == "true",
}

// Parse custom key paths
if keyPaths := os.Getenv("SSH_KEY_PATHS"); keyPaths != "" {
    config.SSHKeyPaths = strings.Split(keyPaths, ":")
}
Environment Variables
  • SSH_KEY_PATHS - Colon-separated list of custom SSH key paths
  • SSH_USE_AGENT - Use ssh-agent for key discovery (default: true)
  • SSH_IDENTITIES_ONLY - Only use keys specified via SSHKeyPaths (default: false)
Programmatic Configuration
config := &sshclient.Config{
    SSHKeyPaths:    []string{"/custom/path/id_rsa", "/custom/path/id_ed25519"},
    UseAgent:       true,
    IdentitiesOnly: false,
}

Key Discovery Behavior

The library follows standard SSH client behavior for key discovery:

  1. Agent Keys (if UseAgent is true and agent available):

    • Queries ssh-agent for all loaded keys
    • Returns agent keys if found and no specific paths requested
  2. Filesystem Keys (if agent keys not found or specific paths requested):

    • Scans standard SSH locations: ~/.ssh/id_rsa, id_ecdsa, id_ed25519, etc.
    • Or uses custom paths from SSHKeyPaths
    • Prompts for passphrases on encrypted keys (up to 3 attempts)
  3. Combined Results:

    • Returns all discovered keys for iteration
    • Caller can try each key until one succeeds (standard SSH behavior)

API Reference

Types
type Config struct {
    SSHKeyPaths    []string // Custom SSH key paths
    UseAgent       bool     // Whether to use ssh-agent
    IdentitiesOnly bool     // Only use specified keys
}

type SSHKey struct {
    PublicKey   ssh.PublicKey
    Blob        []byte      // Public key bytes
    Comment     string
    Signer      ssh.Signer  // For filesystem keys
    AgentKey    *agent.Key  // For agent keys
    Source      string      // "agent" or filesystem path
    Fingerprint string      // SHA256 fingerprint
}

type UnifiedSSHClient struct {
    // ... internal fields
}
Functions
NewUnifiedSSHClient(config *Config) (*UnifiedSSHClient, error)

Creates a new unified SSH client with the given configuration.

(c *UnifiedSSHClient) GetKeys() ([]*SSHKey, error)

Discovers and returns all available SSH keys from agent and/or filesystem.

(c *UnifiedSSHClient) SignWithKey(key *SSHKey, data []byte) (*ssh.Signature, ssh.PublicKey, error)

Signs data with the specified SSH key.

FormatPublicKey(key *SSHKey) string

Formats an SSH public key in authorized_keys format.

Advanced Usage

Custom Key Paths
config := &sshclient.Config{
    SSHKeyPaths: []string{
        "/path/to/custom/id_rsa",
        "/path/to/custom/id_ed25519",
    },
    UseAgent:       false, // Skip agent
    IdentitiesOnly: true,  // Only use specified keys
}
Multi-Key Authentication
client, _ := sshclient.NewUnifiedSSHClient(config)
keys, _ := client.GetKeys()

// Try each key until one succeeds
for _, key := range keys {
    signature, pubKey, err := client.SignWithKey(key, data)
    if err == nil {
        fmt.Printf("Authenticated with key: %s\n", key.Fingerprint)
        break
    }
    fmt.Printf("Key %s failed: %v\n", key.Fingerprint, err)
}
Agent-Only Keys
config := &sshclient.Config{
    UseAgent:       true,
    IdentitiesOnly: true, // Skip filesystem even if agent fails
}

Error Handling

The library uses sentinel errors for common cases:

keys, err := client.GetKeys()
if errors.Is(err, sshclient.ErrKeyNotFound) {
    // Specific key path not found (non-fatal for discovery)
}

Testing

# Run unit tests
make test-unit

# Run integration tests (requires ssh-agent)
make test-integration

# Run all tests
make test

# Run linter
make lint

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Inspired by kubectl-ssh-oidc
  • Follows OpenSSH client behavior for key discovery and iteration
  • Uses golang.org/x/crypto/ssh for SSH operations
  • kubectl-ssh-oidc - Kubernetes authentication via SSH-signed OIDC tokens
  • vlogin - HashiCorp Vault authentication via SSH keys

Made with ❤️ for the Go community

Directories

Path Synopsis
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL