diff --git a/formulas/rent-a-human-caddy-ssh.toml b/formulas/rent-a-human-caddy-ssh.toml new file mode 100644 index 0000000..57dfc77 --- /dev/null +++ b/formulas/rent-a-human-caddy-ssh.toml @@ -0,0 +1,167 @@ +# formulas/rent-a-human-caddy-ssh.toml — Provision SSH key for Caddy log collection +# +# "Rent a Human" — walk the operator through provisioning a purpose-limited +# SSH keypair so collect-engagement.sh can fetch Caddy access logs remotely. +# +# The key uses a `command=` restriction so it can ONLY cat the access log. +# No interactive shell, no port forwarding, no agent forwarding. +# +# Parent vision issue: #426 +# Sprint: website-observability-wire-up (ops PR #10) +# Consumed by: site/collect-engagement.sh (issue #745) + +name = "rent-a-human-caddy-ssh" +description = "Provision a purpose-limited SSH keypair for remote Caddy log collection" +version = 1 + +# ── Step 1: Generate keypair ───────────────────────────────────────────────── + +[[steps]] +id = "generate-keypair" +title = "Generate a dedicated ed25519 keypair" +description = """ +Generate a purpose-limited SSH keypair for Caddy log collection. + +Run on your local machine (NOT the Caddy host): + +``` +ssh-keygen -t ed25519 -f caddy-collect -N '' -C 'disinto-collect-engagement' +``` + +This produces two files: + - caddy-collect (private key — goes into the vault) + - caddy-collect.pub (public key — goes onto the Caddy host) + +Do NOT set a passphrase (-N '') — the factory runs unattended. +""" + +# ── Step 2: Install public key on Caddy host ───────────────────────────────── + +[[steps]] +id = "install-public-key" +title = "Install the public key on the Caddy host with command= restriction" +needs = ["generate-keypair"] +description = """ +Install the public key on the Caddy host with a strict command= restriction +so this key can ONLY read the access log. + +1. SSH into the Caddy host as the user who owns /var/log/caddy/access.log. + +2. Open (or create) ~/.ssh/authorized_keys: + mkdir -p ~/.ssh && chmod 700 ~/.ssh + nano ~/.ssh/authorized_keys + +3. Add this line (all on ONE line — do not wrap): + + command="cat /var/log/caddy/access.log",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... disinto-collect-engagement + + Replace "AAAA..." with the contents of caddy-collect.pub. + + To build the line automatically: + echo "command=\"cat /var/log/caddy/access.log\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding $(cat caddy-collect.pub)" + +4. Set permissions: + chmod 600 ~/.ssh/authorized_keys + +What the restrictions do: + - command="cat /var/log/caddy/access.log" + Forces this key to only execute `cat /var/log/caddy/access.log`, + regardless of what the client requests. + - no-port-forwarding — blocks SSH tunnels + - no-X11-forwarding — blocks X11 + - no-agent-forwarding — blocks agent forwarding + +If the access log is at a different path, update the command= restriction +AND set CADDY_ACCESS_LOG in the factory environment to match. +""" + +# ── Step 3: Add private key to vault secrets ───────────────────────────────── + +[[steps]] +id = "store-private-key" +title = "Add the private key to .env.vault.enc as CADDY_SSH_KEY" +needs = ["generate-keypair"] +description = """ +Store the private key in the factory's encrypted vault secrets. + +1. Read the private key: + cat caddy-collect + +2. Add it to .env.vault.enc (or .env.vault for plaintext fallback) as + CADDY_SSH_KEY. The key is multi-line, so use the base64-encoded form: + + echo "CADDY_SSH_KEY=$(base64 -w0 caddy-collect)" >> .env.vault.enc + + Or, if using SOPS-encrypted vault, decrypt first, add the variable, + then re-encrypt. + +3. IMPORTANT: After storing, securely delete the local private key file: + shred -u caddy-collect 2>/dev/null || rm -f caddy-collect + rm -f caddy-collect.pub + + The public key is already installed on the Caddy host; the private key + now lives only in the vault. + +Never commit the private key to any git repository. +""" + +# ── Step 4: Configure Caddy host address ───────────────────────────────────── + +[[steps]] +id = "store-caddy-host" +title = "Add the Caddy host address to .env.vault.enc as CADDY_HOST" +needs = ["install-public-key"] +description = """ +Store the Caddy host connection string so collect-engagement.sh knows +where to SSH. + +1. Add to .env.vault.enc (or .env.vault for plaintext fallback): + + echo "CADDY_HOST=user@caddy-host-ip-or-domain" >> .env.vault.enc + + Replace user@caddy-host-ip-or-domain with the actual SSH user and host + (e.g. debian@203.0.113.42 or deploy@caddy.disinto.ai). + +2. If using SOPS, decrypt/add/re-encrypt as above. +""" + +# ── Step 5: Test the connection ────────────────────────────────────────────── + +[[steps]] +id = "test-connection" +title = "Verify the SSH key works and returns the access log" +needs = ["install-public-key", "store-private-key", "store-caddy-host"] +description = """ +Test the end-to-end connection before the factory tries to use it. + +1. From the factory host (or anywhere with the private key), run: + + ssh -i caddy-collect -o StrictHostKeyChecking=accept-new user@caddy-host + + Expected behavior: + - Outputs the contents of /var/log/caddy/access.log + - Disconnects immediately (command= restriction forces this) + + If you already shredded the local key, decode it from the vault: + echo "$CADDY_SSH_KEY" | base64 -d > /tmp/caddy-collect-test + chmod 600 /tmp/caddy-collect-test + ssh -i /tmp/caddy-collect-test -o StrictHostKeyChecking=accept-new user@caddy-host + rm -f /tmp/caddy-collect-test + +2. Verify the output is Caddy structured JSON (one JSON object per line): + ssh -i /tmp/caddy-collect-test user@caddy-host | head -1 | jq . + + You should see fields like: ts, request, status, duration. + +3. If the connection fails: + - Permission denied → check authorized_keys format (must be one line) + - Connection refused → check sshd is running on the Caddy host + - Empty output → check /var/log/caddy/access.log exists and is readable + by the SSH user + - "jq: error" → Caddy may be using Combined Log Format instead of + structured JSON; check Caddy's log configuration + +4. Once verified, the factory's collect-engagement.sh can use this key + to fetch logs remotely via: + ssh -i $CADDY_HOST +"""