Skip to content

0212 — PBS agent credential moves from root@pam!claude (Admin) to a dedicated claude@pbs (Audit)

The agent’s Proxmox Backup Server credential was root@pam!claude — an API token in the system-root realm with the Admin role at /, i.e. full control of the backup root, when the ~10 consumers that use it only ever read (backup task status, datastore/job config) and, in one place, prune. This ADR records moving that credential to a dedicated claude@pbs user holding only the built-in Audit (read-only) role, closing the CPS 234 §21 gap that the highest-blast-radius appliance was reached with a root-realm admin token. It closes the PBS half of Issue #1660 under Epic #1652.

Implemented — 2026-07-20 (accepted 2026-07-19). Config-as-code landed in configure_pbs_config.yml (PLAY 3, asserts the claude@pbs user + Audit ACL) and vars/rotate/pbs_xt035.yml (future rotations repointed to claude@pbs/Audit). The one-time credential swap ran via the supervised handoff /tmp/pbs_least_privilege_swap.sh (mint → verify read + verify write-denied → store in Infisical → revoke old → clean stale ACLs → prove monitoring green), executed by Arron because it writes the secret store and revokes a live credential. Verified live 2026-07-20 (independently of the script’s own PASS): claude@pbs!claude (Audit) serves the agent, datastore + node reads → HTTP 200, admin write (POST /config/datastore) → HTTP 403 denied, root@pam!claude-* revoked (no tokens on root@pam), stale hermes@pbs/hermes@pve!hermes ACLs removed, consumers read via refreshed Infisical env → 200, 0 xt035 Zabbix problems. Closes the PBS half of Issue #1660; the prune gap is tracked as follow-up Issue #1837.

root@pam in PBS authenticates against the host’s Linux root credential; a root@pam!claude token with Admin at / can create/delete datastores, prune any backup, manage remotes and users — none of which the agent needs. The dedicated read-only token for the xv035 remote (sync@pbs!xt035remote, DatastoreReader, ADR-0152) already set the least-privilege precedent; this extends it to the agent’s own general-purpose PBS credential.

  1. Dedicated identity, read-only role. A token-only (no password) claude@pbs user with built-in Audit at / (read everything, write nothing), token claude@pbs!claude stored in Infisical Infrastructure /pbs → PBS_API_TOKEN. Every read consumer (morning-health, schedule-reconciler, the health checks) is satisfied by Audit.

  2. Grant the role to the USER, persistently. PBS API tokens are privilege-separated by default, so effective rights are the intersection of the user’s ACL and the token’s ACL (#1592). The claude@pbs user holds Audit at / in configure_pbs_config.yml, so a token regenerated by the rotation adapter always intersects back to Audit — token rotation never silently drops privilege or requires re-granting the user.

  3. Verify-before-revoke, mute-first. The swap mints and fully verifies the new token (reads → 200, an admin write → 403) and confirms Infisical serves it before revoking root@pam!claude-*. Alertmanager + Zabbix on xt035 are muted for the window first.

  4. Tidy up decommissioned ACLs. The swap also removes the stale hermes@pbs and hermes@pve!hermes ACL entries (residue of the decommissioned hermes host, #513).

The Audit-vs-prune trade-off (flagged, not silently resolved)

Section titled “The Audit-vs-prune trade-off (flagged, not silently resolved)”

Audit is read-only, but one existing consumer — pbs_prune_vmid.sh, the decom Phase-2 helper — issues snapshot DELETEs and returns 403 under Audit. This is a genuine fork:

  • (a) Keep the agent token Audit (chosen) — the day-to-day credential is read-only, the strongest posture. The decom-prune capability becomes a separate, narrowly-scoped token (Datastore.Modify on /datastore only), raised as follow-up Issue #1837 rather than crammed into this swap. Decom is infrequent and supervised, so the interim gap is accepted and tracked, not hidden.
  • (b) Give the agent token a prune-capable role (e.g. DatastoreAdmin on /datastore) — keeps decom working on one token but is less least-privilege.

Chosen (a): least-privilege means exactly the privileges each purpose needs, and “prune during decom” is a distinct purpose that deserves its own scoped credential — not a standing write grant on the agent’s general token. The trade-off is surfaced to Arron (register + Pushover + the swap-script header) so the role can be flipped before running if he prefers (b).

  • Rotate within root@pam!claude (the existing adapter’s default). Rejected — it keeps the system-root realm + Admin role; a rotation is not a privilege reduction.
  • Custom ClaudeOps role bundling Audit + Datastore.Modify on the one token. Rejected as the default — it re-broadens the agent token with a standing write; the separate decom-prune token is the cleaner separation. Left available as option (b) if Arron prefers one credential.
  • The agent can no longer mutate the backup root through its PBS token — a real reduction in blast radius on the appliance that holds every backup. CPS 234 §21 has a least-privilege, config-as-code PBS access record (the Privileged Access Register).
  • A tracked follow-up is owed: the dedicated decom-prune token, after which pbs_prune_vmid.sh is repointed off the (now read-only) shared token. Until then, decom PBS pruning needs an elevated path.
  • Future PBS token rotations run unchanged through the existing rotation role, now targeting claude@pbs/Audit.