Groups as the Single Source of Truth
Last reviewed
IntermediateWhat you'll learn
~18 min- Project one role-based group layer into every consuming system - individuals are drift by definition
- Reconcile in three directions, and distrust inventories your collector can't fully see
- Enforce high-consequence boundaries outside the permission model, at chokepoints
Where access rot actually comes from
Module 2 built beautiful policy — targeted, as Lesson 2.1 insisted, at groups. This lesson is why that insistence carries a whole architecture, drawn from a real org-scale cleanup. The rot pattern is universal: each system (the DevOps platform, the cloud, the scanner, the SaaS tools) accumulates its own direct-to-individual grants; five years later nobody can answer “what can Dana reach?” without a week of archaeology, and every offboarding is a scavenger hunt.
The architecture that ends it:
DIRECTORY GROUPS (role-based, system-agnostic, THE truth) org layer: all-staff-read · platform-admins · prod-authority per-project: proj-X-admins · proj-X-contributors (fixed naming template => group count is ARITHMETIC: f(projects)) │ projected, never copied ┌─────────┼──────────────┬─────────────────┐ ▼ ▼ ▼ ▼ DevOps cloud RBAC scanner (next system: project (custom role project join the roles + deny lists) roles projection)The rules that make it a system rather than a diagram: groups are named for roles, not systems (“proj-dispatch-admins”, never “jenkins-admins-2”) so one grant decision projects everywhere; nothing is granted to individuals in any consuming system — at cutover, existing direct assignments are stripped, and afterward an individual grant isn’t a shortcut, it’s drift by definition; membership changes happen in exactly one place, and every consumer inherits within a sync cycle — which is also what makes Module 2’s access reviews meaningful (review the group once, not five systems’ copies) and offboarding instant.
Nesting keeps it maintainable (platform-admins nests into every per-project admin group — one membership, N projections), and the fixed naming template makes coverage computable: for P projects there must be exactly 2P per-project groups, and any deviation is a finding, not a mystery.
Reconciliation: three directions, and a trap
Projection drifts — someone will hand-grant in a console eventually. The governance loop is a scheduled three-direction reconciliation:
A→B every directory group projects correctly into each consumerB→A everything granted in each consumer traces back to a group (orphan individual grants => strip list)Ø coverage both ways: projects with no groups; groups whose project died (dormancy: no resources, no pipelines, no activity => access is risk with no offsetting function - retire it)And the trap, learned expensively in the source case: your collector’s credential scope masquerades as inventory truth. The reconciliation script’s own service account could only see a subset of projects — so its “complete inventory” silently under-counted, and the access model built on it under-provisioned some teams and missed others entirely. The meta-rule: before trusting any automated inventory, verify what the inventory-taker itself is allowed to see. (This is Lesson 1.1’s tenets applied to your own tooling — the census is only as honest as the census-taker’s permissions.)
The chokepoint principle
One design decision in the source architecture deserves promotion to a principle. Production wasn’t protected by making the permission model perfect — it was protected by three independent chokepoints that don’t care about project permissions: restricted service connections (only prod-authority reaches the prod credentials), restricted agent pools (prod deploys run only on gated runners), and human approval gates. A project admin with every project-level permission still can’t touch production, because production safety was deliberately enforced somewhere other than the permission model — the same reasoning as Lesson 2.3’s PAWs and 3.4’s brokers: put the highest-consequence boundary behind a mechanism that survives permission-model mistakes. Lesson 5.3’s matrix proves it from both sides forever after.
Design the group architecture for this estate: the org-layer groups withtheir philosophy stated (is internal read open by default? say so, on therecord); the per-project template and its arithmetic; nesting; the customroles with explicit deny lists where platform ceilings apply; the threeprod chokepoints; the cutover plan (create → populate → WAIT FOR SYNC asits own phase → grant to groups → strip individuals → verify via the 5.3matrix); and the scheduled three-direction reconciliation with thecollector-scope check as step zero.(That “wait for sync” phase earns its place in the plan: directory propagation is minutes-to-hours, and every migration that skips the wait “verifies” against stale state and un-learns the lesson at 5 p.m.)
A scanner-coverage audit shows 12 of 60 projects unscanned. The scanner team proposes fixing it directly: 'grant our scanner service account access to the 12 projects in the scanner console.' Under this lesson's architecture, what's wrong and what's right?
Key takeaway
One role-named group layer, projected into every consumer, individuals stripped and forbidden, membership changed in one place — then kept honest by three-direction reconciliation (with the collector’s own scope verified first) and backstopped by chokepoints that don’t trust the permission model. Access answers become queries, offboarding becomes instant, and Lesson 5.3’s personas get their ground truth. Next: making the whole estate as movable as its groups.