feat(ansible): hosts trust the OpenBao SSH user CA (SSH CA phase 2) #1467

Merged
k.renner merged 2 commits from ssh-ca-phase2 into main 2026-10-27 08:53:33 +00:00
Member

Phase 2 of the central SSH CA: hosts in the new inventory group [ssh_ca] trust certificates from OpenBao's ssh-client-signer. Additive only: authorized_keys is untouched, so every existing login keeps working. The group starts with proxy-1.renner.uno only (resolves by name, SSH reachable, proxy-2 is its twin so the public edge survives a bad run). Further hosts are added one PR at a time after verification (house rule: group membership, no flags/when:).

Role ssh-ca

  • CA key: fetched from https://openbao.renner.uno/v1/ssh-client-signer/public_key on the controller at run time (CI pods have open egress; OpenBao admits Traefik). Asserted to be an ssh-ed25519 key. No copy in git.
  • Principals (AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u): k.renner for root/ladmin, whichever exists (getent); claude-bot for the new claude-bot account.
  • RevokedKeys /etc/ssh/revoked_keys: always deployed (empty list in files/). sshd refuses all key logins if the option points at an unreadable file.
  • Account claude-bot (password locked), same name as its certificate principal and its Gitea identity.
  • sudo for claude-bot, visudo -cf validated: read-only journalctl, systemctl status|list-units, ss -tulpn, sshd -T; systemctl restart|reload only for units in ssh_ca_claude_sudo_units (empty by default). NOEXEC so the pager can't spawn a shell.
  • Safety: the sshd drop-in is checked with sshd -t and removed again if it fails; after the reload sshd -T must show all three settings (catches an earlier config line taking precedence). Play runs serial: 1.

Pipeline .woodpecker/deploy-ssh-ca.yml

Same shape as deploy-claude-workstation: push to main on role/play/inventory/pipeline changes, or manual deployment deploy-ssh-ca. No --limit.

Also

ssh-sign-owner.hcl header now records the mount, CA and owner-role commands (review-bot suggestion on #1466).

Validation

  • ansible-playbook --syntax-check passes; ansible-inventory --graph ssh_ca → proxy-1.renner.uno only.
  • Sudoers template rendered locally (with a sample unit) → visudo -cf parsed OK.
  • woodpecker-cli lint: only the timeout warning the existing deploy pipelines also have.
  • Not yet run against a host; the first real run is this pipeline after merge.

After merge — verification on proxy-1

  1. Pipeline deploy-ssh-ca green.
  2. Owner: ssh -v -i ~/.ssh/id_gitea_<serial> root@proxy-1.renner.uno → -v shows the ED25519-SK-CERT being accepted.
  3. Claude: 1h cert → ssh claude-bot@proxy-1.renner.uno, sudo -n journalctl -n 5 works, sudo -n systemctl restart ssh is refused.

🤖 Generated with Claude Code

Phase 2 of the central SSH CA: hosts in the new inventory group `[ssh_ca]` trust certificates from OpenBao's `ssh-client-signer`. **Additive only**: `authorized_keys` is untouched, so every existing login keeps working. The group starts with **proxy-1.renner.uno only** (resolves by name, SSH reachable, proxy-2 is its twin so the public edge survives a bad run). Further hosts are added one PR at a time after verification (house rule: group membership, no flags/`when:`). ## Role `ssh-ca` - **CA key**: fetched from `https://openbao.renner.uno/v1/ssh-client-signer/public_key` on the controller at run time (CI pods have open egress; OpenBao admits Traefik). Asserted to be an `ssh-ed25519` key. No copy in git. - **Principals** (`AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u`): `k.renner` for `root`/`ladmin`, whichever exists (getent); `claude-bot` for the new `claude-bot` account. - **`RevokedKeys /etc/ssh/revoked_keys`**: always deployed (empty list in `files/`). sshd refuses *all* key logins if the option points at an unreadable file. - **Account `claude-bot`** (password locked), same name as its certificate principal and its Gitea identity. - **sudo for claude-bot**, `visudo -cf` validated: read-only `journalctl`, `systemctl status|list-units`, `ss -tulpn`, `sshd -T`; `systemctl restart|reload` only for units in `ssh_ca_claude_sudo_units` (empty by default). `NOEXEC` so the pager can't spawn a shell. - **Safety**: the sshd drop-in is checked with `sshd -t` and removed again if it fails; after the reload `sshd -T` must show all three settings (catches an earlier config line taking precedence). Play runs `serial: 1`. ## Pipeline `.woodpecker/deploy-ssh-ca.yml` Same shape as `deploy-claude-workstation`: push to main on role/play/inventory/pipeline changes, or manual deployment `deploy-ssh-ca`. No `--limit`. ## Also `ssh-sign-owner.hcl` header now records the mount, CA and owner-role commands (review-bot suggestion on #1466). ## Validation - `ansible-playbook --syntax-check` passes; `ansible-inventory --graph ssh_ca` → `proxy-1.renner.uno` only. - Sudoers template rendered locally (with a sample unit) → `visudo -cf` parsed OK. - `woodpecker-cli lint`: only the `timeout` warning the existing deploy pipelines also have. - Not yet run against a host; the first real run is this pipeline after merge. ## After merge — verification on proxy-1 1. Pipeline `deploy-ssh-ca` green. 2. Owner: `ssh -v -i ~/.ssh/id_gitea_<serial> root@proxy-1.renner.uno` → `-v` shows the `ED25519-SK-CERT` being accepted. 3. Claude: 1h cert → `ssh claude-bot@proxy-1.renner.uno`, `sudo -n journalctl -n 5` works, `sudo -n systemctl restart ssh` is refused. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-10-27 08:48:09 +00:00
feat(ansible): hosts trust the OpenBao SSH user CA (phase 2)
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
66c56288a7
New role ssh-ca with play configure-ssh-ca.yml (serial: 1) and pipeline
deploy-ssh-ca. Hosts in the new [ssh_ca] group get:

- TrustedUserCAKeys from OpenBao's public_key endpoint, fetched on the
  controller at run time (no copy in git).
- AuthorizedPrincipalsFile: k.renner for root/ladmin (whichever
  exists), claude-bot for Claude's own claude-bot account.
- RevokedKeys, always deployed: sshd refuses all key logins when the
  file is set but missing.
- claude-bot sudo: read-only journalctl/systemctl status/ss/sshd -T,
  restart/reload only for ssh_ca_claude_sudo_units; NOEXEC against
  pager shell escapes; visudo-validated.

The drop-in is tested with sshd -t and removed again on failure; sshd -T
must show all three settings afterwards. authorized_keys is untouched,
so this only adds a login path. [ssh_ca] starts with claude-ws only.

ssh-sign-owner.hcl now also records the mount, CA and owner role
commands (review on #1466).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot added 1 commit 2026-10-27 08:51:01 +00:00
fix(ansible): start the SSH CA rollout on proxy-1
ci/woodpecker/pr/ci-manifests-test Pipeline failed
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
9dcaf42f96
claude-ws is decommissioned. proxy-1 resolves by name, answers on 22,
and has proxy-2 as its twin, so the public edge survives a bad run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
k.renner merged commit d8813a68b7 into main 2026-10-27 08:53:33 +00:00
Sign in to join this conversation.