Files
ansiblings/packages/keyman
Benjamin Diedrichsen 3436f3cbe2 [feat] keyman: key rotation, in two halves
Phase 10 of docs/PLAN.md; closes AUDIT §3.6, the README's oldest lie
("Support for key rotation", with no occurrence of "rotat" in src/).

Rotation only ever adds. `rotateKey` generates a replacement under the next
name in the series — prod → prod-2 → prod-3 — and encrypts it *alongside*
the key it replaces, so both are in the vault at once. `retireKey` is a
separate operation, and the only one in keyman that destroys an encrypted
key. The gap between the two is where the new public key gets deployed and
tested: a rotation that replaces the key in one step locks you out of the
host you were rotating for, because the replacement is not on it yet and
the only copy of the one that is has gone.

The name has to change — the vault layout derives the directory from it, so
a replacement also called `prod` *is* the `prod` entry. `nextRotationName`
skips any version already taken in the vault, in tmp or in .ssh, so it
never asks ssh-keygen to overwrite a private key in use. Retirement warns
when nothing in the vault supersedes the key and then makes the user type
its name, since that deletion is unrecoverable.

Three things extracted rather than copied: `listVaultKeys` (vault.ts) now
backs decrypt, rotate and retire; `createKeyPair` and `promptKeyOptions`
(generate.ts) are shared with rotation, which also carries the old key's
comment over as the default. Verified against the real binaries that a
hyphen-suffixed name survives ssh-keygen and age, that the vault entry
round-trips byte-identically, and that ssh-keygen writes the replacement
0600 without help.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:53:11 +02:00
..
2026-07-27 13:09:00 +02:00

Keyman - SSH Key Management with Age Encryption

Keyman is a simple command line tool built around the age encryption tool. It allows you to manage SSH keys in public GitHub repositories securely by encrypting the private keys.

Features

  • 🔐 Encrypt SSH private keys with age encryption
  • 📁 Organized vault structure: vault/keys/ for encrypted keys, vault/tmp/ for decrypted keys
  • ⚙️ Configurable via .keymanrc.json with sensible defaults
  • 🔍 Interactive CLI for encrypting, decrypting, and listing keys
  • 🔄 Support for key rotation

Quick Start

1. Generate Age Encryption Key

# Create vault structure
mkdir -p vault/keys vault/tmp

# Generate age encryption key (keep this secret!)
age-keygen -o vault/age.key

# Add to .gitignore
echo "vault/age.key" >> .gitignore
echo "vault/tmp/" >> .gitignore

2. Generate SSH Keys

# Generate SSH key pair
ssh-keygen -t ed25519 -f vault/tmp/id_deploy -N "" -C "deploy@myapp.dev"

3. Run Keyman

# Run keyman interactively
VAULT_ROOT=./vault keyman

# Or if you have .keymanrc.json configured, just run:
keyman

Configuration

Keyman uses sensible defaults but can be customized via .keymanrc.json:

{
  "vaultRoot": "./vault",
  "keysDir": "keys",
  "tmpDir": "tmp",
  "ageKeyFile": "age.key"
}

Configuration Priority

  1. VAULT_ROOT environment variable (highest priority)
  2. .keymanrc.json file (searched from current directory upward)
  3. Default values (lowest priority)

Default Values

  • vaultRoot: "vault"
  • keysDir: "keys"
  • tmpDir: "tmp"
  • ageKeyFile: "age.key"

Vault Structure

project/
├── vault/
│   ├── age.key         # Master encryption key (NEVER commit!)
│   ├── keys/           # Encrypted keys (safe to commit)
│   │   └── deploy/     # Each key has its own folder
│   │       ├── id_deploy.pub        # Public key
│   │       └── id_deploy.age        # Encrypted private key
│   └── tmp/            # Decrypted keys (NEVER commit!)
│       ├── id_deploy       # Decrypted private key
│       └── id_deploy.pub   # Public key
└── .keymanrc.json      # Configuration (optional)

Operations

Keyman provides an interactive menu-driven interface with the following operations:

  • 📋 List keys - Compact view showing all keys with checkbox indicators for their locations
  • 🔒 Encrypt keys - Encrypt SSH keys from vault/tmp/ and store in vault/keys/
  • 🔓 Decrypt keys - Decrypt keys from vault/keys/ to vault/tmp/ or ~/.ssh/
  • Quit - Exit the program

After completing any operation, keyman automatically returns to the main menu, allowing you to perform multiple operations in a single session without restarting the tool.

List Keys Output

The list command shows a compact, unified view of all SSH keys with their locations:

🔑 SSH Keys:

  Key Name                      [Vault] [Tmp] [.ssh]
  ──────────────────────────────────────────────────────────
  ✅ id_deploy (.pub)              [✓]   [ ]  [✓]
  🔓 id_github (.pub)              [✓]   [✓]  [ ]
  🔒 id_backup (.pub)              [✓]   [ ]  [ ]
  ⚠️  id_local (.pub)               [ ]   [ ]  [✓]

  Legend:
  ✅ = Managed (encrypted in vault + active in .ssh)
  🔓 = Decrypted (in vault + decrypted to tmp)
  🔒 = Encrypted only (in vault, not decrypted)
  ⚠️  = Unmanaged (in .ssh or tmp, not encrypted in vault)

Features:

  • Public keys are indicated with (.pub) suffix instead of separate entries
  • Status emoji shows management state at a glance
  • Checkboxes [✓] show presence in three locations:
    • [Vault] - Encrypted in vault/keys/
    • [Tmp] - Decrypted in vault/tmp/
    • [.ssh] - Active in ~/.ssh/
  • Alphabetically sorted for easy scanning
  • New 🔓 status for keys decrypted to tmp but not yet in .ssh

Example Usage

# Using environment variable
VAULT_ROOT=../../vault keyman

# Using default configuration
keyman

# Keyman will show:
# 📁 Vault Root: /path/to/vault
# 🔑 Keys Directory: /path/to/vault/keys
# 📂 Temp Directory: /path/to/vault/tmp
# 🔐 Age Key: /path/to/vault/age.key

Best Practices

  1. Never commit vault/age.key or vault/tmp/ to version control
  2. Always backup your age.key securely (password manager, encrypted USB)
  3. Commit vault/keys/ - encrypted keys are safe to share
  4. Use environment variables for CI/CD: VAULT_ROOT=/path/to/vault keyman
  5. Keep .keymanrc.json in your project root for team consistency