feat(kanidm): declare the whole account policy, and record why secrets cannot be #307
Inga granskare
Etiketter
Inga etiketter
area/backups
area/ci
area/control-panel
area/identity
area/infra
area/observability
area/payments
area/security
area/storage
area/web
blocked
needs-info
needs-triage
ready-for-implementation
type
bug
type
chore
type
docs
type
epic
type
feature
type
task
wontfix
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Inget förfallodatum satt.
Beroenden
Inga beroenden satta
Referens
bitborg/bitborg-infra!307
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "feat/kanidm-declared-state"
Borttagning av en gren är permanent. Även om den borttagna grenen kan fortsätta existera en kort tid innan den faktiskt tas bort, kan det INTE ångras i de flesta fall. Vill du fortsätta?
Closes #275.
Most of #275 had already shipped through #277–#282 — all four OAuth2 clients are declared and the
scope-map drift gate is enforced. What was left was the account policy and two undocumented decisions.
Account policy is now declared in full
kanidm_account_policy_credential_type_minimumwas a single scalar; it is replaced by akanidm_account_policytable covering all eight knobs the CLI exposes, each carrying itsserver-side fallback and the reason for the declared value. The assertion gate loops the table, so an
upstream default that changes on upgrade now fails a converge instead of silently altering credential
requirements for every person.
Three findings worth reading before merging
1. Client secrets cannot be sourced from vault — this is a limit, not a deferred task. Upstream
Kanidm has no API to set an OAuth2 basic secret; it can only read or reset one. The provisioning
tool's
basicSecretFiletargets an endpoint that exists only in a patched Kanidm server — its ownsource says "Only works when using the patch. Do not specify otherwise!" and "Did you compile kanidm
with the necessary patch?" (
src/client.rs:396-426). Forking the identity provider is a differentorder of risk from the existing
entryManagedBypatch on a helper tool, so it stays undone — but it isnow recorded as a limit, together with its consequence (a rebuild mints fresh random secrets and breaks
Forgejo, the portal and Grafana SSO simultaneously) and the recovery sequence, in the runbook.
2. ⚠
authsession_expirybeing unset means never, not 24 hours — so production auth sessionseffectively never expire. Absent resolves to
MAXIMUM_AUTH_SESSION_EXPIRY(u32::MAX), not thetest-only
DEFAULT_constant of 86400 s (accountpolicy.rs:27-29). Andreset-auth-expiryis an HTTPDELETE(libs/client/src/group.rs:40-46), so it purges the value rather than restoring a default.This is recorded as a documented recommendation and not applied — changing session lifetime is its
own reviewed change. Filed separately.
3. The old readability guard had a hole. It skipped silently when
credential_type_minimumwaspurged — the one case it exists to catch. It now guards on the attribute map being non-empty, which also
distinguishes Kanidm's habit of answering a denied read with HTTP 200 and a
nullbody.--no-auto-remove: recommend keeping itDeclaring the clients does not make dropping the flag safe. Auto-remove is not OAuth2-scoped — it
deletes orphaned groups and persons, and neither the builtin
idm_*groups nor a narrowedforgejo_usersis safe under it. It is also not what protects a declared entity (present: falsedeletes regardless), and the leak that actually bit us — #278's stale scope map — is invisible to it.
Recorded rather than silently flipped.
Verified
site.yml --syntax-checkclean.ansible-lint roles/kanidm roles/caddy→ 14 failures, identical to theHEADbaseline (confirmedby
git stash; the rule mix diff is empty). None on changed lines.['bitborg-web','bitborg-web-dev','forgejo','grafana'];basicSecretFileabsent everywhere.live-as-is → all 8 pass; purged
credential_type_minimum→ fail; downgraded toany→ fail; anundeclared
authsession_expiryset → only that one fails; a denied read (nullbody) → all skip.and 404 for a control id.
Applying — read this first
Run the normal apply. The only new runtime behaviour is the account-policy gate, which is read-only.
Watch its output on the first run. That is where the seven
null-declared knobs are confirmedagainst live for the first time. If one turns out to be set on the server, the play fails with the
value and the reconcile command — that is intended. The fix is to adopt the live value in
kanidm_account_policywith a reason, or unset it on the server.Two things could not be verified without credentials, and are stated in the runbook rather than glossed:
the live values of those seven knobs (the cached
idm_adminsession had expired), and whether anundeclared OAuth2 client exists — OIDC discovery proves the declared ones are real but cannot
enumerate, which needs
kanidm system oauth2 listasidm_admin. If the reconciler token cannot readidm_all_persons' attributes at all, the gate reports "could not verify" and skips by design, and thewhole policy check is inert — so check the first apply's output for that message specifically.
ac81541c31fb13a6dd89fb13a6dd893f3da7a1c1