Resolves #14 Each client now gets a dedicated SSH key pair, ensuring that compromise of one client server does not grant access to other client servers. ## Changes ### Infrastructure (OpenTofu) - Replace shared `hcloud_ssh_key.default` with per-client `hcloud_ssh_key.client` - Each client key read from `keys/ssh/<client_name>.pub` - Server recreated with new key (dev server only, acceptable downtime) ### Key Management - Created `keys/ssh/` directory for SSH keys - Added `.gitignore` to protect private keys from git - Generated ED25519 key pair for dev client - Private key gitignored, public key committed ### Scripts - **`scripts/generate-client-keys.sh`** - Generate SSH key pairs for clients - Updated `scripts/deploy-client.sh` to check for client SSH key ### Documentation - **`docs/ssh-key-management.md`** - Complete SSH key management guide - **`keys/ssh/README.md`** - Quick reference for SSH keys directory ### Configuration - Removed `ssh_public_key` variable from `variables.tf` - Updated `terraform.tfvars` to remove shared SSH key reference - Updated `terraform.tfvars.example` with new key generation instructions ## Security Improvements ✅ Client isolation: Each client has dedicated SSH key ✅ Granular rotation: Rotate keys per-client without affecting others ✅ Defense in depth: Minimize blast radius of key compromise ✅ Proper key storage: Private keys gitignored, backups documented ## Testing - ✅ Generated new SSH key for dev client - ✅ Applied OpenTofu changes (server recreated) - ✅ Tested SSH access: `ssh -i keys/ssh/dev root@78.47.191.38` - ✅ Verified key isolation: Old shared key removed from Hetzner ## Migration Notes For existing clients: 1. Generate key: `./scripts/generate-client-keys.sh <client>` 2. Apply OpenTofu: `cd tofu && tofu apply` (will recreate server) 3. Deploy: `./scripts/deploy-client.sh <client>` For new clients: 1. Generate key first 2. Deploy as normal 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| ssh | ||
| .gitignore | ||
| README.md | ||
Age Encryption Keys
⚠️ CRITICAL: This directory contains encryption keys that are NOT committed to Git.
Key Files
age-key.txt- Age private key for SOPS encryption (GITIGNORED)
Backup Checklist
Before proceeding with any infrastructure work, ensure you have:
- Copied
age-key.txtto password manager - Created offline backup (printed or encrypted USB)
- Verified backup can decrypt secrets successfully
Key Recovery
If you lose access to age-key.txt:
- Check password manager for backup
- Check offline backups (printed copy, USB drive)
- If no backup exists: Secrets are PERMANENTLY LOST
- You will need to regenerate all secrets
- Re-encrypt all
.sops.yamlfiles - Update all services with new credentials
Generating a New Key
Only do this if you've lost the original key or need to rotate for security:
# Generate new Age key
age-keygen -o age-key.txt
# Extract public key
grep "public key:" age-key.txt
# Update .sops.yaml in repository root with new public key
# Re-encrypt all secrets
cd ..
for file in secrets/**/*.sops.yaml; do
SOPS_AGE_KEY_FILE=keys/age-key.txt sops updatekeys -y "$file"
done
Security Notes
- This directory is in
.gitignore - Keys should never be shared via email, Slack, or unencrypted channels
- Always use secure methods for key distribution (password manager, encrypted channels)