SSH Key Lifecycle Management
SSH Key Lifecycle Management
Every SSH key follows a lifecycle from generation through retirement. Understanding and managing each lifecycle phase ensures keys remain secure and access stays controlled throughout their existence. Proper lifecycle management prevents key sprawl, reduces security risks, and maintains compliance with security policies.
The key lifecycle begins with secure generation using appropriate algorithms and key sizes. Keys must be distributed securely to authorized users and systems, tracked throughout their active use, rotated regularly to limit exposure, and eventually retired when no longer needed. Each phase requires specific controls and procedures to maintain security.
Implement a comprehensive key lifecycle framework:
#!/bin/bash
# ssh-key-lifecycle.sh
# Complete SSH key lifecycle management system
# Configuration
KEY_REPO="/opt/ssh-key-management"
KEY_DB="$KEY_REPO/keys.db"
LOG_FILE="/var/log/ssh-key-lifecycle.log"
# Initialize key repository
init_key_repository() {
mkdir -p "$KEY_REPO"/{active,staging,archived,revoked}
# Create SQLite database for key tracking
sqlite3 "$KEY_DB" << 'EOF'
CREATE TABLE IF NOT EXISTS ssh_keys (
key_id TEXT PRIMARY KEY,
fingerprint TEXT UNIQUE,
user_email TEXT,
key_type TEXT,
key_size INTEGER,
created_date TEXT,
expiry_date TEXT,
last_used TEXT,
status TEXT,
purpose TEXT,
approved_by TEXT,
revoked_date TEXT,
revoke_reason TEXT
);
CREATE TABLE IF NOT EXISTS key_usage (
usage_id INTEGER PRIMARY KEY AUTOINCREMENT,
key_id TEXT,
used_date TEXT,
source_ip TEXT,
destination TEXT,
success BOOLEAN,
FOREIGN KEY(key_id) REFERENCES ssh_keys(key_id)
);
CREATE TABLE IF NOT EXISTS key_rotation (
rotation_id INTEGER PRIMARY KEY AUTOINCREMENT,
old_key_id TEXT,
new_key_id TEXT,
rotation_date TEXT,
reason TEXT,
performed_by TEXT
);
EOF
log "Key repository initialized at $KEY_REPO"
}
# Generate new SSH key with metadata
generate_managed_key() {
local user_email=$1
local key_purpose=$2
local expiry_days=${3:-365}
# Generate unique key ID
local key_id="ssh-$(date +%Y%m%d)-$(openssl rand -hex 4)"
local key_path="$KEY_REPO/staging/${key_id}"
# Generate key
ssh-keygen -t ed25519 -f "$key_path" -C "$user_email" -N ""
# Calculate fingerprint
local fingerprint=$(ssh-keygen -lf "${key_path}.pub" | awk '{print $2}')
# Calculate expiry date
local expiry_date=$(date -d "+${expiry_days} days" +%Y-%m-%d)
# Record in database
sqlite3 "$KEY_DB" << EOF
INSERT INTO ssh_keys (
key_id, fingerprint, user_email, key_type, key_size,
created_date, expiry_date, status, purpose
) VALUES (
'$key_id', '$fingerprint', '$user_email', 'ed25519', 255,
'$(date +%Y-%m-%d)', '$expiry_date', 'pending', '$key_purpose'
);
EOF
log "Generated key $key_id for $user_email (expires: $expiry_date)"
echo "$key_id"
}
# Approve and activate key
activate_key() {
local key_id=$1
local approver=$2
# Move key to active directory
mv "$KEY_REPO/staging/${key_id}"* "$KEY_REPO/active/"
# Update database
sqlite3 "$KEY_DB" << EOF
UPDATE ssh_keys
SET status = 'active', approved_by = '$approver'
WHERE key_id = '$key_id';
EOF
# Deploy to authorized systems
deploy_key "$key_id"
log "Activated key $key_id (approved by: $approver)"
}
# Track key usage
track_key_usage() {
local key_fingerprint=$1
local source_ip=$2
local destination=$3
local success=$4
# Get key ID from fingerprint
local key_id=$(sqlite3 "$KEY_DB" "SELECT key_id FROM ssh_keys WHERE fingerprint='$key_fingerprint';")
if [ -n "$key_id" ]; then
# Record usage
sqlite3 "$KEY_DB" << EOF
INSERT INTO key_usage (key_id, used_date, source_ip, destination, success)
VALUES ('$key_id', '$(date +%Y-%m-%d\ %H:%M:%S)', '$source_ip', '$destination', $success);
UPDATE ssh_keys SET last_used = '$(date +%Y-%m-%d\ %H:%M:%S)' WHERE key_id = '$key_id';
EOF
fi
}
# Rotate key
rotate_key() {
local old_key_id=$1
local reason=$2
local performer=$3
# Get key metadata
local user_email=$(sqlite3 "$KEY_DB" "SELECT user_email FROM ssh_keys WHERE key_id='$old_key_id';")
local purpose=$(sqlite3 "$KEY_DB" "SELECT purpose FROM ssh_keys WHERE key_id='$old_key_id';")
# Generate new key
local new_key_id=$(generate_managed_key "$user_email" "$purpose" 365)
# Activate new key
activate_key "$new_key_id" "$performer"
# Schedule old key revocation
schedule_key_revocation "$old_key_id" 30 "Key rotation"
# Record rotation
sqlite3 "$KEY_DB" << EOF
INSERT INTO key_rotation (old_key_id, new_key_id, rotation_date, reason, performed_by)
VALUES ('$old_key_id', '$new_key_id', '$(date +%Y-%m-%d)', '$reason', '$performer');
EOF
log "Rotated key $old_key_id to $new_key_id (reason: $reason)"
}
# Revoke key
revoke_key() {
local key_id=$1
local reason=$2
# Move to revoked directory
mv "$KEY_REPO/active/${key_id}"* "$KEY_REPO/revoked/" 2>/dev/null
# Update database
sqlite3 "$KEY_DB" << EOF
UPDATE ssh_keys
SET status = 'revoked', revoked_date = '$(date +%Y-%m-%d)', revoke_reason = '$reason'
WHERE key_id = '$key_id';
EOF
# Remove from all systems
remove_key_deployment "$key_id"
log "Revoked key $key_id (reason: $reason)"
}
# Monitor key expiration
monitor_key_expiration() {
log "Checking for expiring keys..."
# Find keys expiring in next 30 days
sqlite3 "$KEY_DB" << 'EOF' | while read key_data; do
SELECT key_id || '|' || user_email || '|' || expiry_date
FROM ssh_keys
WHERE status = 'active'
AND date(expiry_date) <= date('now', '+30 days')
AND date(expiry_date) > date('now');
EOF
IFS='|' read -r key_id user_email expiry_date <<< "$key_data"
# Send notification
send_expiry_notification "$key_id" "$user_email" "$expiry_date"
# Auto-rotate if within 7 days
if [ $(date -d "$expiry_date" +%s) -le $(date -d "+7 days" +%s) ]; then
rotate_key "$key_id" "Approaching expiration" "automated"
fi
done
}