feat(token-audit): age-based service-account token monitoring (#75) #96

Sammanfogat
supernaut sammanfogade 1 incheckning från feat/75-token-rotation in i main 2026-07-18 15:55:37 +00:00
Ägare

Refs #75 (doesn't close it — the account + PAT mint + apply are operator steps; see the issue comment for the full status and the deliberate deferral of fully-autonomous rotation).

What this adds

  • token-audit role (observe-only, reconciler shape): daily systemd user timer lists each ADR 0024 service account's tokens via the v16 admin token API and writes textfile metrics (gitborg_forgejo_token_created_timestamp_seconds{account,token_name,token_id}, gitborg_forgejo_token_count, run status/timestamp). Inert until vault_forgejo_token_audit_token is set.
  • gitborg-token-audit service account (admin role — listing other users' tokens requires the admin API — but its PAT is minted read:admin only, so the audit can never mint/revoke).
  • Alerts: ForgejoTokenRotationDue (newest token per account > 80d, alert_token_rotation_due_days) and TokenAuditFailed.
  • Runbook § Rotate a service-account PAT: the v16 API procedure (list → mint date-stamped token → vault → apply role tag → verify → revoke old id), replacing the password-reset + basic-auth dance; gitborg-ci org-Actions-secret special case noted.
  • ADR 0024 amendment in bitborg-docs (branch docs/75-adr-0024-token-api): basic-auth caveat superseded.

Why not fully-autonomous rotation (issue's "rotation timer")

Every consumer role re-renders its token from vault on apply — host-side rotation would be clobbered by the next site.yml run (old revoked token written back → outage). Moving runtime truth to the host is an ADR-level change (create-if-missing in 3 roles, self-rotation bootstrap, crash recovery). Deferred with rationale on the issue; alert + one-command procedure captures most of the value.

Verification

  • Prod swagger (16.0.0) confirms the three admin token routes + created_at in AccessToken.
  • Metric pipeline smoke-tested (jq TSV → GNU date epoch conversion handles the +02:00 offsets jq's fromdateiso8601 chokes on; label values sanitized).
  • ansible-playbook --syntax-check + ansible-lint (token-audit, monitoring): clean.

Apply-day (after merge)

  1. site.yml --tags forgejo → creates gitborg-token-audit.
  2. Mint its PAT: POST /api/v1/admin/users/gitborg-token-audit/tokens {"name":"token-audit-YYYYMMDD","scopes":["read:admin"]}.
  3. Vault as vault_forgejo_token_audit_token; site.yml --tags token-audit,monitoring.
  4. systemctl --user start gitborg-token-audit.service once and check token-audit.prom.
Refs #75 (doesn't close it — the account + PAT mint + apply are operator steps; see the issue comment for the full status and the deliberate deferral of fully-autonomous rotation). ## What this adds - **`token-audit` role** (observe-only, reconciler shape): daily systemd user timer lists each ADR 0024 service account's tokens via the v16 admin token API and writes textfile metrics (`gitborg_forgejo_token_created_timestamp_seconds{account,token_name,token_id}`, `gitborg_forgejo_token_count`, run status/timestamp). Inert until `vault_forgejo_token_audit_token` is set. - **`gitborg-token-audit` service account** (admin role — listing other users' tokens requires the admin API — but its PAT is minted `read:admin` only, so the audit can never mint/revoke). - **Alerts**: `ForgejoTokenRotationDue` (newest token per account > 80d, `alert_token_rotation_due_days`) and `TokenAuditFailed`. - **Runbook § Rotate a service-account PAT**: the v16 API procedure (list → mint date-stamped token → vault → apply role tag → verify → revoke old id), replacing the password-reset + basic-auth dance; gitborg-ci org-Actions-secret special case noted. - **ADR 0024 amendment** in bitborg-docs (branch `docs/75-adr-0024-token-api`): basic-auth caveat superseded. ## Why not fully-autonomous rotation (issue's "rotation timer") Every consumer role re-renders its token from vault on apply — host-side rotation would be clobbered by the next `site.yml` run (old revoked token written back → outage). Moving runtime truth to the host is an ADR-level change (create-if-missing in 3 roles, self-rotation bootstrap, crash recovery). Deferred with rationale on the issue; alert + one-command procedure captures most of the value. ## Verification - Prod swagger (16.0.0) confirms the three admin token routes + `created_at` in `AccessToken`. - Metric pipeline smoke-tested (jq TSV → GNU date epoch conversion handles the +02:00 offsets jq's `fromdateiso8601` chokes on; label values sanitized). - `ansible-playbook --syntax-check` + `ansible-lint` (token-audit, monitoring): clean. ## Apply-day (after merge) 1. `site.yml --tags forgejo` → creates `gitborg-token-audit`. 2. Mint its PAT: `POST /api/v1/admin/users/gitborg-token-audit/tokens {"name":"token-audit-YYYYMMDD","scopes":["read:admin"]}`. 3. Vault as `vault_forgejo_token_audit_token`; `site.yml --tags token-audit,monitoring`. 4. `systemctl --user start gitborg-token-audit.service` once and check `token-audit.prom`.
supernaut tvångsskickade feat/75-token-rotation från 991f477d70
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m36s
till f4e2a07bc7
Alla kontroller lyckades
ci / ci (pull_request) Successful in 2m13s
2026-07-18 14:58:35 +00:00
Jämför
supernaut tvångsskickade feat/75-token-rotation från f4e2a07bc7
Alla kontroller lyckades
ci / ci (pull_request) Successful in 2m13s
till a42022412e
Alla kontroller lyckades
ci / ci (pull_request) Successful in 2m17s
2026-07-18 15:23:38 +00:00
Jämför
supernaut sammanfogade incheckning b5ab664e2c till main 2026-07-18 15:55:37 +00:00
supernaut tog bort grenen feat/75-token-rotation 2026-07-18 15:55:37 +00:00
supernaut refererade denna ändringsförfrågan från en incheckning 2026-07-18 16:53:17 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-infra!96
Ingen beskrivning angiven.