🔐 A modern Python library for persistent SSH agent management across sessions.
- 🔄 Persistent SSH agent management across sessions
- 🔑 Automatic SSH key loading and caching
- 🪟 Windows-optimized implementation
- 🔗 Seamless Git integration
- 🌐 Cross-platform compatibility (Windows, Linux, macOS)
- 📦 No external dependencies beyond standard SSH tools
- 🔒 Secure key management and session control
- ⚡ Asynchronous operation support
- 🧪 Complete unit test coverage with performance benchmarks
- 📝 Comprehensive type hints support
- 🔐 Support for multiple SSH key types (Ed25519, ECDSA, RSA)
- 🌍 IPv6 support
- 📚 Multi-language documentation support
- 🔍 Enhanced SSH configuration validation
- 🛠️ Modern development toolchain (Poetry, Commitizen, Black)
pip install persistent-ssh-agent
- Python 3.8-3.13
- OpenSSH (ssh-agent, ssh-add) installed and available in PATH
- Git (optional, for Git operations)
from persistent_ssh_agent import PersistentSSHAgent
# Create an instance with custom expiration time (default is 24 hours)
ssh_agent = PersistentSSHAgent(expiration_time=86400)
# Set up SSH for a specific host
if ssh_agent.setup_ssh('github.com'):
print("✅ SSH authentication ready!")
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
# Create custom SSH configuration
config = SSHConfig(
identity_file='~/.ssh/github_key', # Optional specific identity file
identity_passphrase='your-passphrase', # Optional passphrase
ssh_options={ # Optional SSH options
'StrictHostKeyChecking': 'yes',
'PasswordAuthentication': 'no',
'PubkeyAuthentication': 'yes'
}
)
# Initialize with custom config and agent reuse settings
ssh_agent = PersistentSSHAgent(
config=config,
expiration_time=86400, # Optional: Set agent expiration time (default 24 hours)
reuse_agent=True # Optional: Control agent reuse behavior (default True)
)
# Set up SSH authentication
if ssh_agent.setup_ssh('github.com'):
# Get Git SSH command for the host
ssh_command = ssh_agent.get_git_ssh_command('github.com')
if ssh_command:
print("✅ Git SSH command ready!")
The reuse_agent
parameter controls how the SSH agent handles existing sessions:
-
When
reuse_agent=True
(default):- Attempts to reuse an existing SSH agent if available
- Reduces the number of agent startups and key additions
- Improves performance by avoiding unnecessary agent operations
-
When
reuse_agent=False
:- Always starts a new SSH agent session
- Useful when you need a fresh agent state
- May be preferred in certain security-sensitive environments
Example with agent reuse disabled:
# Always start a new agent session
ssh_agent = PersistentSSHAgent(reuse_agent=False)
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
# Create configuration with common options
config = SSHConfig(
ssh_options={
'BatchMode': 'yes',
'StrictHostKeyChecking': 'yes',
'ServerAliveInterval': '60'
}
)
# Initialize agent
agent = PersistentSSHAgent(config=config)
# Set up SSH for multiple hosts
hosts = ['github.com', 'gitlab.com', 'bitbucket.org']
for host in hosts:
if agent.setup_ssh(host):
print(f"✅ SSH configured for {host}")
else:
print(f"❌ Failed to configure SSH for {host}")
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
# Create configuration with global options
config = SSHConfig(
# Set identity file (optional)
identity_file='~/.ssh/id_ed25519',
# Set global SSH options
ssh_options={
'StrictHostKeyChecking': 'yes',
'PasswordAuthentication': 'no',
'PubkeyAuthentication': 'yes',
'BatchMode': 'yes',
'ConnectTimeout': '30'
}
)
# Initialize agent with global configuration
agent = PersistentSSHAgent(config=config)
import asyncio
from persistent_ssh_agent import PersistentSSHAgent
async def setup_multiple_hosts(hosts: list[str]) -> dict[str, bool]:
"""Set up SSH for multiple hosts concurrently."""
ssh_agent = PersistentSSHAgent()
results = {}
async def setup_host(host: str):
results[host] = await ssh_agent.async_setup_ssh(host)
await asyncio.gather(*[setup_host(host) for host in hosts])
return results
# Usage example
async def main():
hosts = ['github.com', 'gitlab.com', 'bitbucket.org']
results = await setup_multiple_hosts(hosts)
for host, success in results.items():
print(f"{host}: {'✅' if success else '❌'}")
asyncio.run(main())
-
Key Management:
- Store SSH keys in standard locations (
~/.ssh/
) - Use Ed25519 keys for better security
- Keep private keys protected (600 permissions)
- Store SSH keys in standard locations (
-
Error Handling:
try: ssh_agent = PersistentSSHAgent() success = ssh_agent.setup_ssh('github.com') if not success: print("⚠️ SSH setup failed") except Exception as e: print(f"❌ Error: {e}")
-
Session Management:
- Agent information persists across sessions
- Automatic cleanup of expired sessions
- Configurable expiration time
- Multi-session concurrent management
-
Security Features:
- Automatic key unloading after expiration
- Secure temporary file handling
- Platform-specific security measures
- Key usage tracking
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
def setup_ci_ssh():
"""Set up SSH for CI environment."""
# Create configuration with key content
config = SSHConfig(
identity_content=os.environ.get('SSH_PRIVATE_KEY'),
ssh_options={'BatchMode': 'yes'}
)
ssh_agent = PersistentSSHAgent(config=config)
if ssh_agent.setup_ssh('github.com'):
print("✅ SSH agent started successfully")
return True
raise RuntimeError("Failed to start SSH agent")
from git import Repo
from persistent_ssh_agent import PersistentSSHAgent
import os
def clone_repo(repo_url: str, local_path: str) -> Repo:
"""Clone a repository using persistent SSH authentication."""
ssh_agent = PersistentSSHAgent()
# Extract hostname and set up SSH
hostname = ssh_agent._extract_hostname(repo_url)
if not hostname or not ssh_agent.setup_ssh(hostname):
raise RuntimeError("Failed to set up SSH authentication")
# Get SSH command and configure environment
ssh_command = ssh_agent.get_git_ssh_command(hostname)
if not ssh_command:
raise RuntimeError("Failed to get SSH command")
# Clone with GitPython
env = os.environ.copy()
env['GIT_SSH_COMMAND'] = ssh_command
return Repo.clone_from(
repo_url,
local_path,
env=env
)
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
# Create config instance
config = SSHConfig()
# Add global configuration
config.add_global_config({
'AddKeysToAgent': 'yes',
'UseKeychain': 'yes'
})
# Add host-specific configuration
config.add_host_config('*.github.com', {
'User': 'git',
'IdentityFile': '~/.ssh/github_ed25519',
'PreferredAuthentications': 'publickey'
})
# Initialize agent with config
agent = PersistentSSHAgent(config=config)
The library automatically manages SSH keys based on your SSH configuration:
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
# Use specific key
config = SSHConfig(identity_file='~/.ssh/id_ed25519')
agent = PersistentSSHAgent(config=config)
# Or let the library automatically detect and use available keys
agent = PersistentSSHAgent()
if agent.setup_ssh('github.com'):
print("✅ SSH key loaded and ready!")
The library supports the following key types in order of preference:
- Ed25519 (recommended, most secure)
- ECDSA
- ECDSA with security key
- Ed25519 with security key
- RSA
- DSA (legacy, not recommended)
The library provides comprehensive SSH configuration validation with support for:
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
# Create custom SSH configuration with validation
config = SSHConfig()
# Add host configuration with various options
config.add_host_config('github.com', {
# Connection Settings
'IdentityFile': '~/.ssh/github_key',
'User': 'git',
'Port': '22',
# Security Settings
'StrictHostKeyChecking': 'yes',
'PasswordAuthentication': 'no',
'PubkeyAuthentication': 'yes',
# Connection Optimization
'Compression': 'yes',
'ConnectTimeout': '60',
'ServerAliveInterval': '60',
'ServerAliveCountMax': '3',
# Proxy and Forwarding
'ProxyCommand': 'ssh -W %h:%p bastion',
'ForwardAgent': 'yes'
})
# Initialize with validated config
ssh_agent = PersistentSSHAgent(config=config)
Supported configuration categories:
- Connection Settings: Port, Hostname, User, IdentityFile
- Security Settings: StrictHostKeyChecking, BatchMode, PasswordAuthentication
- Connection Optimization: Compression, ConnectTimeout, ServerAliveInterval
- Proxy and Forwarding: ProxyCommand, ForwardAgent, ForwardX11
- Environment Settings: RequestTTY, SendEnv
- Multiplexing Options: ControlMaster, ControlPath, ControlPersist
For detailed validation rules and supported options, see SSH Configuration Validation
The library supports multiple SSH key types:
- Ed25519 (recommended, most secure)
- ECDSA
- ECDSA with security key
- Ed25519 with security key
- RSA
- DSA (legacy, not recommended)
-
SSH Key Management:
- Automatic detection and loading of SSH keys (Ed25519, ECDSA, RSA)
- Support for key content injection (useful in CI/CD)
- Secure key file permissions handling
- Optional passphrase support
-
Configuration Security:
- Strict hostname validation
- Secure default settings
- Support for security-focused SSH options
-
Session Management:
- Secure storage of agent information
- Platform-specific security measures
- Automatic cleanup of expired sessions
- Cross-platform compatibility
The library provides comprehensive type hints for all public interfaces:
from typing import Optional
from persistent_ssh_agent import PersistentSSHAgent
from persistent_ssh_agent.config import SSHConfig
def setup_ssh(hostname: str, key_file: Optional[str] = None) -> bool:
config = SSHConfig(identity_file=key_file)
agent = PersistentSSHAgent(config=config)
return agent.setup_ssh(hostname)
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.