SSH Profile Management - Detailed Documentation¶
Table of Contents¶
- Architecture Overview
- Database Design
- Security Model
- Command Reference
- Authentication Methods
- Implementation Details
- Best Practices
- Troubleshooting
Architecture Overview¶
The SSH management system in Sloth Runner follows a secure, profile-based architecture that separates connection metadata from authentication credentials.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Sloth Runner CLI โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ SSH Profile Manager โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ Profile โ โ SQLite โ โ SSH โ โ
โ โ Commands โโโโ Database โ โ Executor โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Security Layer โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ No Password โ โ Key File โ โ Password โ โ
โ โ Storage โ โ Validation โ โ from STDIN โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Database Design¶
Schema Definition¶
-- Main profiles table
CREATE TABLE ssh_profiles (
-- Primary identifier
name TEXT PRIMARY KEY NOT NULL,
-- Connection details
host TEXT NOT NULL,
user TEXT NOT NULL,
port INTEGER DEFAULT 22 CHECK(port > 0 AND port <= 65535),
-- Authentication (key-based only stored)
key_path TEXT,
-- Metadata
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used TIMESTAMP,
use_count INTEGER DEFAULT 0,
-- Configuration
connection_timeout INTEGER DEFAULT 30,
keepalive_interval INTEGER DEFAULT 60,
strict_host_checking BOOLEAN DEFAULT TRUE,
-- Constraints
UNIQUE(host, user, port),
CHECK(length(name) > 0 AND length(name) <= 50),
CHECK(length(host) > 0),
CHECK(length(user) > 0)
);
-- Index for faster lookups
CREATE INDEX idx_ssh_profiles_host ON ssh_profiles(host);
CREATE INDEX idx_ssh_profiles_last_used ON ssh_profiles(last_used);
-- Audit log table
CREATE TABLE ssh_audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
profile_name TEXT NOT NULL,
action TEXT NOT NULL, -- 'connect', 'execute', 'fail'
command TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
success BOOLEAN,
error_message TEXT,
FOREIGN KEY(profile_name) REFERENCES ssh_profiles(name) ON DELETE CASCADE
);
-- Trigger to update timestamp
CREATE TRIGGER update_ssh_profile_timestamp
AFTER UPDATE ON ssh_profiles
BEGIN
UPDATE ssh_profiles
SET updated_at = CURRENT_TIMESTAMP
WHERE name = NEW.name;
END;
Data Storage Location¶
Default Locations: - Unix/Linux: $HOME/.sloth-runner/ssh_profiles.db
- macOS: $HOME/.sloth-runner/ssh_profiles.db
- Windows: %USERPROFILE%\.sloth-runner\ssh_profiles.db
Custom Location:
Security Model¶
1. Credential Separation¶
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ What IS Stored โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ Profile name โ
โ โ Hostname/IP โ
โ โ Username โ
โ โ Port number โ
โ โ SSH key path โ
โ โ Connection preferences โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ What is NEVER Stored โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ Passwords โ
โ โ Passphrases โ
โ โ Private key contents โ
โ โ Session tokens โ
โ โ Sudo passwords โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
2. Authentication Flow¶
graph TD
A[User executes command] --> B{Authentication method?}
B -->|Key-based| C[Read key path from DB]
B -->|Password| D[Read from STDIN]
C --> E[Validate key file exists]
E --> F[Check key permissions]
F --> G[Establish SSH connection]
D --> H[Never log password]
H --> I[Pass to SSH client]
I --> G
G --> J{Connection successful?}
J -->|Yes| K[Execute command]
J -->|No| L[Log failure without credentials]
K --> M[Audit log entry]
L --> M
3. File Permissions Requirements¶
SSH Key Files:
# Private key - read-only by owner
chmod 600 ~/.ssh/id_rsa
# Public key - readable by all
chmod 644 ~/.ssh/id_rsa.pub
# SSH config directory
chmod 700 ~/.ssh
Database File:
# Database - read/write by owner only
chmod 600 ~/.sloth-runner/ssh_profiles.db
# Directory - accessible by owner only
chmod 700 ~/.sloth-runner
Command Reference¶
sloth-runner ssh add
¶
Complete Syntax:
sloth-runner ssh add <profile-name> \
--host <hostname-or-ip> \
--user <username> \
[--port <port-number>] \
--key <private-key-path> \
[--description "<text>"] \
[--timeout <seconds>] \
[--keepalive <seconds>] \
[--no-strict-host-checking]
Parameters:
Parameter | Required | Default | Description |
---|---|---|---|
<profile-name> | Yes | - | Unique identifier (alphanumeric, hyphen, underscore) |
--host | Yes | - | Target hostname or IP address |
--user | Yes | - | SSH username |
--port | No | 22 | SSH port (1-65535) |
--key | Yes | - | Path to private SSH key |
--description | No | - | Profile description (max 255 chars) |
--timeout | No | 30 | Connection timeout in seconds |
--keepalive | No | 60 | Keepalive interval in seconds |
--no-strict-host-checking | No | false | Disable host key verification (NOT RECOMMENDED) |
Examples:
# Basic profile
sloth-runner ssh add prod-web \
--host 192.168.1.10 \
--user ubuntu \
--key ~/.ssh/prod_key
# Complete profile with all options
sloth-runner ssh add staging-db \
--host staging-db.internal \
--user postgres \
--port 2222 \
--key ~/.ssh/staging_key \
--description "Staging database server" \
--timeout 60 \
--keepalive 30
# Profile for bastion/jump host
sloth-runner ssh add bastion \
--host bastion.example.com \
--user jump \
--port 22022 \
--key ~/.ssh/bastion_key \
--description "Bastion host for production access"
sloth-runner ssh list
¶
Syntax:
Options: - --format
: Output format (table, json, yaml, csv) - --filter
: Filter expression (e.g., "host=192.168.*")
Examples:
# List all profiles (table format)
sloth-runner ssh list
# JSON output for scripting
sloth-runner ssh list --format json
# Filter by hostname pattern
sloth-runner ssh list --filter "host=*.example.com"
# CSV for export
sloth-runner ssh list --format csv > ssh_profiles.csv
sloth-runner ssh show
¶
Syntax:
Example Output:
Profile: production-web
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Host: prod.example.com
User: ubuntu
Port: 22
Key Path: /home/user/.ssh/prod_key
Description: Production web server
Created: 2024-01-15 10:30:00
Last Modified: 2024-01-20 14:45:30
Last Used: 2024-01-22 09:15:00
Use Count: 47
Timeout: 30s
Keepalive: 60s
Strict Host: Enabled
sloth-runner ssh update
¶
Syntax:
sloth-runner ssh update <profile-name> \
[--host <new-hostname>] \
[--user <new-username>] \
[--port <new-port>] \
[--key <new-key-path>] \
[--description "<new-description>"] \
[--timeout <seconds>] \
[--keepalive <seconds>]
Example:
# Update only the key path
sloth-runner ssh update prod-web --key ~/.ssh/new_prod_key
# Update multiple fields
sloth-runner ssh update staging \
--host new-staging.example.com \
--port 2222 \
--description "New staging environment"
sloth-runner ssh remove
¶
Syntax:
Options: - --force
: Skip confirmation prompt
Example:
# Remove with confirmation
sloth-runner ssh remove old-server
# Force remove without confirmation
sloth-runner ssh remove temp-server --force
sloth-runner ssh test
¶
Test SSH connectivity without executing commands.
Syntax:
Examples:
# Test with key authentication
sloth-runner ssh test production
# Test with password authentication
echo -n "password" | sloth-runner ssh test legacy-server --ssh-password-stdin -
Authentication Methods¶
1. SSH Key Authentication (Recommended)¶
Setup Process:
# 1. Generate SSH key pair
ssh-keygen -t ed25519 -f ~/.ssh/sloth_key -C "sloth-runner"
# 2. Copy public key to server
ssh-copy-id -i ~/.ssh/sloth_key.pub user@host
# 3. Add profile with key
sloth-runner ssh add myserver \
--host hostname \
--user username \
--key ~/.ssh/sloth_key
# 4. Test connection
sloth-runner ssh test myserver
2. Password Authentication (When Required)¶
โ ๏ธ Security Warning: Password authentication should only be used when key-based authentication is not possible.
Password Input Methods:
# Method 1: From file (most common)
echo -n "password" > pass.txt
chmod 600 pass.txt
sloth-runner run stack --file task.sloth \
--ssh server --ssh-password-stdin - < pass.txt
shred -u pass.txt
# Method 2: From environment variable
export SSH_PASS="password"
sloth-runner run stack --file task.sloth \
--ssh server --ssh-password-stdin - < <(echo -n "$SSH_PASS")
unset SSH_PASS
# Method 3: Interactive input (for manual execution)
sloth-runner run stack --file task.sloth \
--ssh server --ssh-password-stdin -
# (Type password and press Ctrl+D)
# Method 4: From password manager
pass show server/ssh | sloth-runner run stack --file task.sloth \
--ssh server --ssh-password-stdin -
3. SSH Agent Integration¶
Using SSH Agent:
# Start SSH agent
eval $(ssh-agent -s)
# Add key to agent
ssh-add ~/.ssh/private_key
# Profile uses key from agent
sloth-runner ssh add server \
--host hostname \
--user username \
--key ~/.ssh/private_key # Key will be accessed via agent
4. Jump Host / Bastion Configuration¶
ProxyJump Setup:
# 1. Create SSH config
cat >> ~/.ssh/config <<EOF
Host production-*
ProxyJump bastion
User ubuntu
IdentityFile ~/.ssh/prod_key
Host bastion
HostName bastion.example.com
User jump
IdentityFile ~/.ssh/bastion_key
EOF
# 2. Add profile referencing SSH config
sloth-runner ssh add production-web \
--host production-web \
--user ubuntu \
--key ~/.ssh/prod_key
Implementation Details¶
Connection Establishment¶
// Pseudo-code for SSH connection
func EstablishSSHConnection(profile SSHProfile, password *string) (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: profile.User,
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Or proper validation
Timeout: time.Duration(profile.Timeout) * time.Second,
}
// Authentication method selection
if password != nil {
config.Auth = []ssh.AuthMethod{
ssh.Password(*password),
}
// Clear password from memory immediately after use
*password = ""
} else if profile.KeyPath != "" {
key, err := ioutil.ReadFile(profile.KeyPath)
if err != nil {
return nil, fmt.Errorf("unable to read private key: %v", err)
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, fmt.Errorf("unable to parse private key: %v", err)
}
config.Auth = []ssh.AuthMethod{
ssh.PublicKeys(signer),
}
}
address := fmt.Sprintf("%s:%d", profile.Host, profile.Port)
client, err := ssh.Dial("tcp", address, config)
if err != nil {
return nil, fmt.Errorf("failed to dial: %v", err)
}
return client, nil
}
Password Handling¶
// Secure password reading from stdin
func ReadPasswordFromStdin() (string, error) {
// Check if stdin is available
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) != 0 {
// Interactive mode - use terminal
fmt.Print("SSH Password: ")
password, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println() // New line after password
return string(password), err
}
// Pipe mode - read from stdin
reader := bufio.NewReader(os.Stdin)
password, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
return "", err
}
// Remove any trailing whitespace/newlines
password = strings.TrimSpace(password)
// Validate password is not empty
if password == "" {
return "", fmt.Errorf("empty password received")
}
return password, nil
}
Best Practices¶
1. Profile Naming Conventions¶
# Environment-based
production-web
staging-db
development-api
# Service-based
nginx-prod
postgres-staging
redis-cache
# Location-based
us-east-1-web
eu-west-2-db
asia-pac-cache
2. Key Management¶
# Dedicated keys per environment
~/.ssh/keys/
โโโ production/
โ โโโ web_servers
โ โโโ db_servers
โ โโโ cache_servers
โโโ staging/
โ โโโ all_servers
โโโ development/
โโโ local_key
# Profile creation with organized keys
sloth-runner ssh add prod-web-1 \
--host 10.0.1.10 \
--user deploy \
--key ~/.ssh/keys/production/web_servers
3. Security Checklist¶
- Use SSH keys instead of passwords whenever possible
- Store private keys with 600 permissions
- Use strong passphrases for SSH keys
- Rotate SSH keys regularly (monthly/quarterly)
- Use different keys for different environments
- Enable SSH agent forwarding only when necessary
- Implement proper host key verification
- Audit SSH access logs regularly
- Use jump hosts for production access
- Implement session recording for compliance
4. Automation Guidelines¶
#!/bin/bash
# Secure automation script template
set -euo pipefail # Exit on error, undefined variables, pipe failures
# Configuration
readonly PROFILE_NAME="production"
readonly TASK_FILE="deploy.sloth"
readonly STACK_NAME="deploy-$(date +%Y%m%d-%H%M%S)"
readonly LOG_FILE="/var/log/sloth-runner/deploy.log"
# Logging function
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# Error handling
trap 'log "ERROR: Command failed at line $LINENO"' ERR
# Main execution
main() {
log "Starting deployment to $PROFILE_NAME"
# Verify profile exists
if ! sloth-runner ssh show "$PROFILE_NAME" > /dev/null 2>&1; then
log "ERROR: SSH profile '$PROFILE_NAME' not found"
exit 1
fi
# Execute deployment
if sloth-runner run "$STACK_NAME" \
--file "$TASK_FILE" \
--ssh "$PROFILE_NAME" \
'deployment_script.sh'; then
log "Deployment successful"
else
log "Deployment failed"
exit 1
fi
}
main "$@"
Troubleshooting¶
Common Issues and Solutions¶
1. Profile Not Found¶
Error:
Solution:
# Check if profile exists
sloth-runner ssh list | grep myserver
# If missing, add it
sloth-runner ssh add myserver --host hostname --user username --key ~/.ssh/key
2. Key Permission Error¶
Error:
Solution:
3. Password Authentication Failing¶
Error:
Debugging Steps:
# 1. Verify password file has no newline
hexdump -C pass.txt | tail -1
# Should NOT show 0a (newline) at the end
# 2. Test SSH directly
ssh user@host -o PreferredAuthentications=password
# 3. Check for special characters
# Ensure password is properly escaped in file
4. Connection Timeout¶
Error:
Solution:
# Increase timeout
sloth-runner ssh update myserver --timeout 60
# Test connectivity
ping hostname
nc -zv hostname 22
5. Host Key Verification Failed¶
Error:
Solution:
# Option 1: Add host to known_hosts
ssh-keyscan -H hostname >> ~/.ssh/known_hosts
# Option 2: Disable strict checking (NOT RECOMMENDED for production)
sloth-runner ssh add server --host hostname --user user --key ~/.ssh/key --no-strict-host-checking
Debug Mode¶
Enable verbose logging for troubleshooting:
# Set debug environment variable
export SLOTH_RUNNER_DEBUG=true
# Run command with debug output
sloth-runner run stack --file task.sloth --ssh server --debug
# View debug logs
tail -f ~/.sloth-runner/debug.log
Audit Log Analysis¶
-- View recent connections
SELECT * FROM ssh_audit_log
ORDER BY timestamp DESC
LIMIT 10;
-- Find failed authentications
SELECT profile_name, timestamp, error_message
FROM ssh_audit_log
WHERE success = 0
ORDER BY timestamp DESC;
-- Connection statistics
SELECT
profile_name,
COUNT(*) as total_connections,
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful,
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed
FROM ssh_audit_log
GROUP BY profile_name;
Migration and Backup¶
Backup Profiles¶
# Backup database
cp ~/.sloth-runner/ssh_profiles.db ~/.sloth-runner/ssh_profiles.db.backup
# Export to JSON
sloth-runner ssh list --format json > ssh_profiles_backup.json
# Encrypted backup
tar czf - ~/.sloth-runner/ssh_profiles.db | \
openssl enc -aes-256-cbc -salt -out ssh_profiles.tar.gz.enc
Restore Profiles¶
# Restore from backup
cp ~/.sloth-runner/ssh_profiles.db.backup ~/.sloth-runner/ssh_profiles.db
# Restore from encrypted backup
openssl enc -d -aes-256-cbc -in ssh_profiles.tar.gz.enc | tar xzf -
Migration Between Systems¶
# Export on source system
sloth-runner ssh export > profiles.json
# Import on target system
sloth-runner ssh import < profiles.json
Security Compliance¶
SOC2 Compliance¶
- โ No password storage in database
- โ Audit logging for all connections
- โ Encrypted communication (SSH)
- โ Access control via file permissions
- โ Session management and timeout
PCI-DSS Requirements¶
- โ Strong cryptography (SSH keys)
- โ No clear-text password storage
- โ Audit trail for access
- โ Regular key rotation capability
- โ Secure key storage
GDPR Considerations¶
- โ No personal data in logs
- โ Encrypted data transmission
- โ Right to deletion (remove profile)
- โ Data portability (export/import)
Remember: This system is designed with security as the primary concern. Never compromise on security for convenience.