Tier 0 Pipeline Design¶
Part of the Security Model. This page explains why a CI pipeline must never hold Tier 0 access, what can safely be automated, and who is eligible to let Tier 0 changes go live. A pipeline may still run a Tier 0 apply when a PIM-activated senior gives it access for minutes, not standing — see Tier 0 Just-in-Time CI Apply. It applies the Access Tiering Model. For the full Lane A and Lane B operating model, including the comparison matrix, runbooks and preflight checks, see Lane A and Lane B Delivery Methodology.
Related implementations:
- Platform Service Principal Vending: machine identity vending under Lane A
- Tenant Root Bootstrap: Day-0 root of trust bootstrap sequence
What if this were applied differently — a pipeline holding Owner/UAA at root¶
It's worth walking through the counterfactual explicitly, because the complexity this tenant has built (two repos, a human CLI gate, PIM activation, senior-engineer-only eligibility) is a direct, traceable response to specific standards violations — not complexity for its own sake.
To be precise about which standard is doing the work here: CAF doesn't flatly prohibit a pipeline holding privileged access (see the model). What follows describes a pipeline that fails CAF's own stated conditions for when that's acceptable — no equivalent governance, no built-in escalation limit — not merely a hypothetical that falls short of this tenant's stricter-than-required choice.
Suppose a CI-applied pipeline identity held Owner/UAA at the Tenant Root Group, instead of a human applying azure-privileged-access-grants under PIM.
flowchart TD
subgraph "Counterfactual: CI holds Owner at root"
MR["Merge request to a CI-applied project"] --> CIM["CI auto-applies on merge<br/>(no second gate)"]
CIM --> LIVE1["Owner at tenant root, live<br/>— no human judged this specific action"]
end
subgraph "What this tenant actually does"
MR2["Merge request to azure-privileged-access-grants"] --> PLAN["CI plans only<br/>(read-only identity)"]
PLAN --> REVIEW["Senior-engineer review"]
REVIEW --> ACT["PIM activation of the operator persona<br/>(MFA + justification, time-boxed)"]
ACT --> HUMANAPPLY["Human, local az login,<br/>./apply.ps1 applies the saved plan"]
HUMANAPPLY --> LIVE2["Owner at tenant root, live,<br/>tied to a named person"]
end
Here's specifically what breaks, standard by standard:
- CAF's own conditions for a privileged pipeline. No PIM-equivalent governance exists for this SP (it's a standing grant, not time-bound or approved per-use), and nothing structurally stops it from applying a role assignment wider than intended — both of CAF's stated conditions fail, not just this tenant's stricter internal rule.
- EAM's plane-isolation principle. EAM's whole model rests on lower-trust systems never being able to manage higher-trust ones. A CI runner triggered by anyone with merge rights — or by anyone who compromises that runner or its OIDC token — is a lower-trust surface than a human on a managed device with an activated privileged role. Giving it Control-plane authority collapses the isolation the model depends on.
- AC-6(5) Privileged Accounts. This control requires privileged accounts be restricted to explicitly authorized individuals for explicitly authorized purposes. A CI identity used for routine merges is neither — it's a shared, purpose-generic identity, and "someone merged a PR" is not an authorized privileged purpose.
- AC-5 Separation of Duties. The reviewer approving a Terraform diff is judging "is this code correct," not "should Global-Admin-equivalent access go live right now." Merge-to-apply collapses those into one action with one judgment call, removing the second, purpose-specific check separation of duties requires.
- AC-2(6) Dynamic Privilege Management. A CI identity holding standing
Ownerat root — even if only to execute a Terraform apply — is a standing privileged grant, the opposite of the just-in-time model NIST and EAM both call for.
The concrete failure path: a merge to main — malicious, or just a mistake — auto-applies immediately. No activation step exists to catch it, because the controls that would normally gate it (PIM activation rules, a named approver) are defined by the same automated pipeline that just applied the change. One bad merge is full tenant compromise, with no human ever having made a judgment call about that specific action.
That failure path is exactly why the added complexity is necessary, not incidental:
- The two-repo split (
solution-privileged-access-allowlist/azure-privileged-access-grants) exists so a compromised pipeline identity can't widen its own allow-list — this is CAF's own named condition (prevent self-escalation) satisfied directly, and it also addresses AC-6(5) (bounding what the "purpose" can ever be) and the EAM isolation principle (the allow-list is unreachable from the lower-trust repo). - The human gate on every apply exists so no merge, by itself, can ever make Tier 0 access live — this is where the design goes beyond CAF's minimum (equivalent governance) rather than attempting to simulate it for a service principal; it also addresses AC-5 (separates "code review" from "privilege activation") and AC-2(6) (nothing is standing; every use requires reactivation). The gate is either a person applying locally (Lane A apply) or a person giving CI's write identity a 30-minute, PIM-timed grant (just-in-time CI apply). Either way, no pipeline identity holds Tier 0 access between applies.
- PIM activation on every use, with MFA, a justification and a time limit, exists so activation itself is a separate, purpose-specific act by a named person — addresses AC-6(5) directly. Today there is no approval step, because there is only one operator. Approval on activation is planned once there's a second approver (see Role Tier Mapping).
- Restricting Tier 0 PIM eligibility to senior engineers exists because AC-5's separation only works if the approver pool is distinct from, and at least as trusted as, the requester pool.
Every one of those is a mitigation for a named failure mode above, not a default level of caution applied everywhere. That's also why mg-landingzones, and read access at mg-platform, don't get the same treatment — the failure mode that justifies it (self-referential Control-plane compromise) doesn't apply there. Write access at mg-platform does, because it reaches Tier 0 state in rg-terraform-state.
What's safe to automate vs. what needs a human gate¶
| Safe to fully automate (CI, merge = apply) | Requires a manual, out-of-band gate |
|---|---|
| Creating groups and membership for non-privileged access | Any change to a Tier 0 role's PIM eligibility list |
Standing Reader / cost-management-style roles at Tier 1/2 scopes |
Any change to the activation rules (MFA, justification, approval) on a Tier 0 persona — the pipeline that enforces the control cannot be trusted to remove it unsupervised |
| PIM eligibility for Tier 1/2 roles | Any role assignment (Owner/UAA/Reader) at tenant root or mg-intermediate-root |
| Read-only or reporting pipelines | Anything that lets an identity grant itself more privilege (Privileged Role Administrator, or an app with admin-consent rights) |
For the specific directory roles, Graph permissions and RBAC assignments an applying CI identity must never hold, see Prohibited CI Permissions.
Automation may define policy (AC-6 least-privilege scoping). Only a supervised, logged, time-bound human action may exercise Tier 0 privilege (AC-2(6), AC-5).
Who is even eligible to hold Tier 0 PIM — engineer seniority¶
The Role Tier Mapping answers which PIM role a human needs to activate. It doesn't answer who should be allowed to be eligible for it in the first place — that's what AC-5 (Separation of Duties) actually requires: the approver pool must be distinct from, and no less trusted than, the requester pool.
| Engineer level | Tier 0 (human apply, or just-in-time CI apply) | Tier 1/2 (CI, auto-apply) |
|---|---|---|
| Standard engineer | Not PIM-eligible for anything at Tier 0. Can write Tier 0 changes and, under just-in-time CI apply, click apply once a senior has granted access — but can't grant it. |
Full participation — merge rights to the Tier 1/2 project are enough; no personal privileged role needed |
| Senior engineer | PIM-eligible for the persona the change requires, per Entra directory roles and Azure RBAC by management group. Every activation requires MFA, a justification and a time limit; approval is added once there's a second approver. | Same as standard — seniority isn't a factor at Tier 1/2 |
Why the line sits there
Tier 0 tasks aren't harder to write Terraform for — adding one UPN to a PIM eligibility list is trivial at any level. What seniority gates is judgment about blast radius: recognising that a seemingly small change is really about who can eventually reach GA/PRA-adjacent territory. azure-privileged-access-grants' required MR approvers should name senior engineers specifically, or AC-5's separation is nominal only.