reconciler doesn't expand nested Kanidm entitlement groups — tier_pro users get zero quota #166
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#166
Läser in…
Hänvisa till i nytt ärende
Ingen beskrivning angiven.
Ta bort grenen "%!s()"
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?
Severity: HIGH (enforcement inversion — Pro users get less quota than Basic). Found live during the #125 apply (2026-07-20).
What
The entitlement taxonomy (ADR 0016,
roles/kanidm/defaults/main.yml) grants entitlements by nesting a tier group into the entitlement groups it confers — e.g.ent_lfs.member = [tier_pro],ent_orgs.member = [tier_pro],ent_runners_shared.member = [tier_pro]. The comment is explicit: "a tier is a MEMBER OF the entitlement groups it grants, so a person in the tier inherits those entitlements via transitive memberOf."But the reconciler's
members()(roles/reconciler/templates/gitborg-reconciler.sh.j2) reads:.attrs.memberis direct membership only. Forent_lfsit returns the stringtier_pro(the nested group), not the persons intier_pro. Soin_set "$user" "$LFS_PRO_M"never matches a real Pro user.Impact
Every tier_pro user misses
lfs-pro,allow_create_organization(ent_orgs), and the Actions unit (ent_runners_shared). With #125's fail-closed fallback they now land inparticipant(size:all = 0) — a paying Pro seat gets zero hosting quota. (Before #125 they hit thelfs-basicfallback = unlimited non-LFS, so the entitlement miss was masked; #125 made it acute.)Observed in the live reconciler run:
renovate cap 100provesalexanderkjallIS intier_pro(the Renovate cap readstier_prodirectly), yet quota/org/actions all fell through.tier_basicis unaffected (the reconciler reads it directly since #125); only the nestedtier_pro → ent_*path is broken. Acute blast radius is currently nil (alexanderkjall owns no repos), so this is fix-forward, not an incident.Fix
Make
members()expand nested groups (Kanidmmemberis not transitive; onlymemberofis). Recurse into any member that is itself a group, with a cycle guard — soent_lfs → tier_pro → {persons}resolves. Alternatively query each person's transitivememberof. Keep the abort-on-Kanidm-read-failure behaviour.Verify after fix
A tier_pro user resolves to
lfs=lfs-pro org_create=true actions=true; a tier_participant/tierless user still → participant; tier_basic → lfs-basic. Dry-run the reconciler and read the journal.Related: #125 (M8 — partial/again mis-tier). Split out because this is a distinct, higher-severity resolution bug, not just an empty-read guard.
Fix implemented on branch
feat/166-nested-group-expansion(committed, push pending — SSH agent re-locked).members()now recurses into nested groups (Kanidmmemberis direct-only) with a cycle guard, soent_lfs → tier_pro → {persons}resolves and Pro users get lfs-pro/org-create/actions instead of failing closed to participant. Verified with a mocked-topology unit test (nesting, mixed direct+nested, cycle, direct). Ready to push + PR; after apply, verify a tier_pro user resolves tolfs=lfs-pro org_create=true actions=true.