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).
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)
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-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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase 2 of the central SSH CA: hosts in the new inventory group
[ssh_ca]trust certificates from OpenBao'sssh-client-signer. Additive only:authorized_keysis 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-cahttps://openbao.renner.uno/v1/ssh-client-signer/public_keyon the controller at run time (CI pods have open egress; OpenBao admits Traefik). Asserted to be anssh-ed25519key. No copy in git.AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u):k.rennerforroot/ladmin, whichever exists (getent);claude-botfor the newclaude-botaccount.RevokedKeys /etc/ssh/revoked_keys: always deployed (empty list infiles/). sshd refuses all key logins if the option points at an unreadable file.claude-bot(password locked), same name as its certificate principal and its Gitea identity.visudo -cfvalidated: read-onlyjournalctl,systemctl status|list-units,ss -tulpn,sshd -T;systemctl restart|reloadonly for units inssh_ca_claude_sudo_units(empty by default).NOEXECso the pager can't spawn a shell.sshd -tand removed again if it fails; after the reloadsshd -Tmust show all three settings (catches an earlier config line taking precedence). Play runsserial: 1.Pipeline
.woodpecker/deploy-ssh-ca.ymlSame shape as
deploy-claude-workstation: push to main on role/play/inventory/pipeline changes, or manual deploymentdeploy-ssh-ca. No--limit.Also
ssh-sign-owner.hclheader now records the mount, CA and owner-role commands (review-bot suggestion on #1466).Validation
ansible-playbook --syntax-checkpasses;ansible-inventory --graph ssh_ca→proxy-1.renner.unoonly.visudo -cfparsed OK.woodpecker-cli lint: only thetimeoutwarning the existing deploy pipelines also have.After merge — verification on proxy-1
deploy-ssh-cagreen.ssh -v -i ~/.ssh/id_gitea_<serial> root@proxy-1.renner.uno→-vshows theED25519-SK-CERTbeing accepted.ssh claude-bot@proxy-1.renner.uno,sudo -n journalctl -n 5works,sudo -n systemctl restart sshis refused.🤖 Generated with Claude Code