Zero Trust Implementation Module 6 · Making It Stick

Groups as the Single Source of Truth

Last reviewed

Intermediate

What 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 consumer
B→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 with
their philosophy stated (is internal read open by default? say so, on the
record); the per-project template and its arithmetic; nesting; the custom
roles with explicit deny lists where platform ceilings apply; the three
prod chokepoints; the cutover plan (create → populate → WAIT FOR SYNC as
its own phase → grant to groups → strip individuals → verify via the 5.3
matrix); and the scheduled three-direction reconciliation with the
collector-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.)

KNOWLEDGE CHECK

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.

Search lessons