Lane A and Lane B Delivery Methodology¶
Every Terraform deployment is delivered through one of two lanes. Lane A is for Tier 0 (control plane): CI plans only, and a PIM-elevated human applies. Lane B is for Tiers 1 and 2 (management and workload planes): full GitOps, where merging to
mainapplies through a scoped OIDC identity. This keeps automation fast everywhere except the few places where a standing pipeline credential would put the whole tenant at risk.
- Access Tiering Model: how Tier 0, 1 and 2 are defined.
- Tier 0 Pipeline Design: why a pipeline must never hold Tier 0 access, and who may apply Tier 0 changes.
- Platform Service Principal Vending: the main Lane A workload, with its attack paths and split-brain anti-patterns.
- Tenant Root Bootstrap: the Day-0 sequence that creates the first Lane A identities and state.
- Terraform State Storage Architecture: state isolation between the lanes.
Why two lanes¶
Pure GitOps, where every merge applies automatically, is the right default. It breaks down at Tier 0. To vend identities, manage directory roles or assign roles at the tenant root, a pipeline would need standing Privileged Role Administrator, Application Administrator or root Owner. Anyone who could merge to the repository, or compromise the runner, would then control the tenant.
So delivery is split by tier:
- Lane A (Tier 0): CI runs checks and
planwith a read-only identity. There is noapplyjob. A senior engineer applies locally, after PIM elevation andpreflight.ps1checks. - Lane B (Tiers 1 and 2): CI runs checks and
planon the MR, thenapplyautomatically on merge, using an OIDC identity scoped to its own management group or subscription.
The detailed case against CI apply at Tier 0 is in Tier 0 Pipeline Design and Platform Service Principal Vending.
The lanes at a glance¶
| Lane A: Tier 0 | Lane B: Tiers 1 and 2 | |
|---|---|---|
| Repositories | azure-tenant-root, azure-service-principals, azure-priv-tier-0 |
subscription-vending, azure-management-groups, azure-platform-identity, azure-monitoring, azure-network, workload repos (gt-*-*) |
| Scope | Tenant root, mg-intermediate-root, Entra directory roles, tenant root state |
mg-platform, mg-landingzones and their subscriptions |
| CI identity | Read-only (sp-pla-tf-*-ro) |
Read-write, bounded to its scope (sp-pla-tf-*-rw, sp-wl-tf-*-rw) |
| CI stages | fmt → tflint → validate → checkov → plan |
fmt → tflint → validate → checkov → plan → apply |
| Who applies | A senior engineer, locally, under az login |
GitLab CI, on merge to main |
| Gate | MR review, then PIM approval, then preflight.ps1 |
MR review and approval |
| Privilege | None standing. PIM activation, time-bound (max 4 hours). | Standing, least-privilege, bounded to the target scope |
| State | Central platform container (sa<tenant>tenantroot) |
Dedicated per-subscription state account in rg-terraform-state |
| Blast radius | The whole tenant | One management group, subscription or resource group |
Choosing a lane¶
Classify every new repository before its first pipeline runs. If any answer points to Lane A, it is Lane A.
flowchart TD
Q1{"Does it manage Entra ID app registrations,<br/>service principals or Graph permissions?"}
Q1 -->|Yes| LANE_A["LANE A<br/>Plan-only CI, human PIM apply"]
Q1 -->|No| Q2{"Does it assign roles at the tenant root<br/>or mg-intermediate-root?"}
Q2 -->|Yes| LANE_A
Q2 -->|No| Q3{"Could a compromised pipeline credential<br/>escalate its own access or create new admin access?"}
Q3 -->|Yes| LANE_A
Q3 -->|No| Q4{"Is the blast radius bounded to one subscription<br/>or a non-identity management group?"}
Q4 -->|Yes| LANE_B["LANE B<br/>Automated GitOps, scoped OIDC"]
Q4 -->|No| LANE_A
| Component | Lane | Why |
|---|---|---|
Directory role management (Privileged Role Administrator, Global Administrator) |
A | Changes the identity control plane directly, and could create a permanent backdoor. |
| Service principal and app registration creation | A | Needs admin consent and credential-creation rights (EAM control plane). |
| Role assignments at the tenant root | A | Inherited by every subscription in the tenant. |
| PIM policy and eligibility | A | Changes the access-control mechanism itself. A pipeline must not govern its own access rules. |
| Tenant root state storage | A | Holds state with sensitive root metadata. |
Management group hierarchy (azure-management-groups) |
B | Creates and moves groups below the root, with Management Group Contributor and no directory roles. |
Subscription vending (subscription-vending) |
B | Creates landing zones through billing APIs. It cannot change the identity architecture. |
Platform monitoring (azure-monitoring) |
B | Bounded to mg-management, with no directory permissions. |
Hub networking (azure-network) |
B | Bounded to mg-connectivity. It cannot escalate to identity admin. |
Workload resources (gt-*-*) |
B | Bounded to individual workload subscriptions. |
Lane A: Tier 0¶
sequenceDiagram
autonumber
actor Engineer as Senior Engineer
actor Reviewer as Peer Senior Engineer
participant GitLab as GitLab CI
participant PIM as Entra ID PIM
participant Azure as Azure / Entra / State
Engineer->>GitLab: Open MR
GitLab->>Azure: Checks + terraform plan (read-only OIDC)
Reviewer->>GitLab: Review plan, approve and merge
Note over GitLab,Azure: CI stops here. There is no apply job.
Engineer->>PIM: Request activation, with justification
Reviewer->>PIM: Approve (separation of duties)
Engineer->>Azure: preflight.ps1, then terraform apply
Note over Azure: Audit logged in Entra ID, ARM and storage
The plan-only pipeline¶
- CI signs in with a keyless OIDC identity (
sp-pla-tf-*-ro) that has read-only access only:Directory Readersin Entra ID,Readeratmg-intermediate-root, andStorage Blob Data Readeron the state container. - The pipeline YAML has no
applyjob. Even if an attacker takes over GitLab or forces a merge, there is no job and no credential that can change live infrastructure.
Preflight checks (preflight.ps1)¶
Before any local apply, the operator runs pwsh -File ./preflight.ps1. It checks that the operator has everything the apply needs, so an apply never fails halfway:
- Backend: reads the storage account, container and resource group from
versions.tf. - Session: the
az loginsession is valid and signed in to the right tenant. - Data plane:
Storage Blob Data Contributoronsa<tenant>tenantroot. - Directory: an active
Global Administrator, orApplication AdministratorplusPrivileged Role Administrator. - Management plane:
Role Based Access Control Administrator(orOwner) atmg-intermediate-root.
Fail closed
If any check fails, the script stops with the missing role or mismatch and how to fix it. Never bypass or disable preflight checks.
Operator runbook¶
Use this for azure-tenant-root, azure-service-principals and azure-priv-tier-0.
- Review and merge. Checks pass, the
planfromsp-pla-tf-*-rois reviewed in the MR, and an independent senior engineer approves the merge. - Elevate. In the Entra admin center, go to Identity Governance → Privileged Identity Management and activate these roles for no more than 2 hours, with the change ticket and a justification. Your peer reviewer approves the requests.
- Entra ID:
Global Administrator, orApplication AdministratorplusPrivileged Role Administrator - Azure:
Role Based Access Control Administrator(orOwner) onmg-intermediate-root - Storage:
Storage Blob Data Contributoron the management subscription's state storage account
- Entra ID:
-
Check. On your managed admin workstation:
-
Apply.
-
Close out. Confirm state was written, deactivate your PIM roles (or let them expire), and attach the apply summary to the change ticket.
Lane B: Tiers 1 and 2¶
Workflow¶
- Open an MR. CI runs
fmt,tflint,validateandcheckov, thenplanwith the repository's scoped OIDC identity, and posts the plan to the MR. - A peer reviews and approves. Merge to
main. - The
mainpipeline runsterraform applywith the same scoped identity.
Identity and state¶
- No secrets. Each repository has a federated credential tied to its project path (
project_path:<group>/<repo>) and to either themainbranch (apply) or merge request events (plan). - Bounded scope. The identity only has rights on its own subscription or management group, for example
ContributorplusUser Access Administratoron one workload subscription. It has no access to parent management groups and no Entra directory roles. - Isolated state. Each subscription created by
subscription-vendinggets its own state storage account inrg-terraform-state. The pipeline identity hasStorage Blob Data Contributoron its own container only, and no access tosa<tenant>tenantroot.
Break-glass¶
In an emergency, such as a pipeline outage, lockout or security incident:
- Use an emergency cloud-only account (
bg-admin-*) from the break-glass vault, signed in with its FIDO2 key. - Signing in with it raises a Priority-1 alert to the Security Operations Center.
- Follow Lane A mechanics: local execution, plan review and manual apply. Record everything for later review (NIST AU-6).