feat(kanidm): enforce the OAuth2 scope-map drift gate #282

Sammanfogat
supernaut sammanfogade 1 incheckning från feat/275-enforce-drift-gate in i main 2026-07-31 12:04:23 +00:00
Ägare

Part of #275. Flips kanidm_oauth2_drift_fail to true, plus two cleanups.

Enforcement

The gate shipped in warn mode (#281) because the bitborg-web-dev leftover was still live, and a hard gate would have aborted every unrelated apply until someone ran delete-scope-map by hand. That leftover was removed today and all four declared clients now match live exactly.

Verified: --check --tags kanidm exits 0 with changed=0, and the gate reports drift on none of the four. So an additive scope-map leak now stops the play instead of scrolling past.

Fixes a readability defect I introduced

The drift read carries the reconciler token in a header, so it needs no_log — and no_log censors per-item loop labels, so the output was four lines of item=(censored due to no_log).

Rather than drop no_log (which would print the Authorization header on -v or on failure), a debug now names the client set and the enforcement state up front:

Checking OAuth2 scope-map drift for: ['bitborg-web', 'bitborg-web-dev', 'forgejo', 'grafana'] (enforcement: True)

Keeping the token unlogged beats prettier output.

Records why claim maps get no gate — verified, not assumed

#281 listed claim-map drift as "not covered". Today's #280 apply answered it:

Claim maps are authoritative. The apply removed the stale entitlements:ent_lfs and entitlements:ent_lfs_large group entries from the forgejo client purely by declaring the six current groups — even though removeOrphanedClaimMaps operates at claim level and entitlements was present. I had flagged that as unpredictable in #280 and said it might need manual delete-claim-map; it converged on its own.

So the declaration is self-correcting for claim maps, and a gate there would be dead code. Scope maps need one precisely because they are the additive exception. That asymmetry is now written down next to the gate.

Verification of the whole #275 chain, live

Client Origin Scope map Claims
bitborg-web prod callback only (localhost removed) forgejo_users —
bitborg-web-dev localhost only forgejo_admins only —
forgejo unchanged forgejo_users 7, incl. forgejo_role → admin
grafana unchanged grafana_admins —

And the behavioural check that mattered: a Forgejo OIDC login after the claim-map rewrite kept site-admin — confirmed both via the admin API and by the operator seeing the admin panel.

Part of #275. Flips `kanidm_oauth2_drift_fail` to `true`, plus two cleanups. ## Enforcement The gate shipped in warn mode (#281) because the `bitborg-web-dev` leftover was still live, and a hard gate would have aborted every unrelated apply until someone ran `delete-scope-map` by hand. That leftover was removed today and all four declared clients now match live exactly. Verified: `--check --tags kanidm` exits **0** with `changed=0`, and the gate reports drift on none of the four. So an additive scope-map leak now stops the play instead of scrolling past. ## Fixes a readability defect I introduced The drift read carries the reconciler token in a header, so it needs `no_log` — and `no_log` censors per-item loop labels, so the output was four lines of `item=(censored due to no_log)`. Rather than drop `no_log` (which would print the Authorization header on `-v` or on failure), a debug now names the client set and the enforcement state up front: ``` Checking OAuth2 scope-map drift for: ['bitborg-web', 'bitborg-web-dev', 'forgejo', 'grafana'] (enforcement: True) ``` Keeping the token unlogged beats prettier output. ## Records why claim maps get no gate — verified, not assumed #281 listed claim-map drift as "not covered". Today's #280 apply answered it: **Claim maps are authoritative.** The apply removed the stale `entitlements:ent_lfs` and `entitlements:ent_lfs_large` group entries from the `forgejo` client purely by declaring the six current groups — even though `removeOrphanedClaimMaps` operates at *claim* level and `entitlements` was present. I had flagged that as unpredictable in #280 and said it might need manual `delete-claim-map`; it converged on its own. So the declaration is self-correcting for claim maps, and a gate there would be dead code. Scope maps need one precisely because they are the additive exception. That asymmetry is now written down next to the gate. ## Verification of the whole #275 chain, live | Client | Origin | Scope map | Claims | | --- | --- | --- | --- | | `bitborg-web` | prod callback only (localhost removed) | `forgejo_users` | — | | `bitborg-web-dev` | localhost only | `forgejo_admins` only | — | | `forgejo` | unchanged | `forgejo_users` | 7, incl. `forgejo_role → admin` | | `grafana` | unchanged | `grafana_admins` | — | And the behavioural check that mattered: a Forgejo OIDC login **after** the claim-map rewrite kept site-admin — confirmed both via the admin API and by the operator seeing the admin panel.
supernaut lade till 1 incheckning 2026-07-31 11:58:04 +00:00
feat(kanidm): enforce the OAuth2 scope-map drift gate
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m25s
b3174941c8
Part of #275. Flips kanidm_oauth2_drift_fail to true, now that the condition that
forced it to warn is gone.

The gate shipped in warn mode because the gitborg-web-dev leftover was still live
and a hard gate would have aborted every unrelated apply until someone ran
delete-scope-map by hand. That leftover was removed today, and all four declared
clients match live exactly — verified: the gate reports no drift and the check run
exits 0 with changed=0. So an additive scope-map leak should now stop the play
rather than scroll past in the output.

Also fixes a readability defect I introduced with the gate. The read carries the
reconciler token in a header, so it needs no_log, and no_log censors per-item loop
labels — the output was four lines of `item=(censored due to no_log)`. Added a
debug that names the client set and the enforcement state before the read, rather
than dropping no_log: keeping the token unlogged beats prettier output.

And records why claim maps get NO equivalent gate, as a verified fact rather than
an assumption. Scope maps need one because provisioning is additive. Claim maps are
authoritative: today's #280 apply removed the stale entitlements:ent_lfs and
ent_lfs_large group entries purely by declaring the six current groups, even though
removeOrphanedClaimMaps works at claim level and `entitlements` was present. The
declaration converges on its own there, so a claim-map gate would be dead code.
supernaut sammanfogade incheckning 883f56f246 till main 2026-07-31 12:04:23 +00:00
supernaut tog bort grenen feat/275-enforce-drift-gate 2026-07-31 12:04:23 +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!282
Ingen beskrivning angiven.