Tenant Root Bootstrap¶
Tenant root bootstrap establishes the foundational root of trust for an Azure tenant. It breaks the "chicken-and-egg" bootstrap paradox — where an automated identity pipeline cannot vend itself — by taking an unmanaged tenant through a sequential, human-gated four-step progression to establish secure remote state and seed initial platform identities under Lane A.
The Day-0 Bootstrap Paradox¶
In an unmanaged brownfield or greenfield Azure tenant, access governance does not yet exist:
- There is no remote Terraform backend storage account.
- There is no management group hierarchy beneath Tenant Root (/).
- There are no service principals or Workload Identity Federation (OIDC) trusts.
- There is no automated pipeline.
A classic IaC paradox emerges: to automate identity vending via CI/CD, the identity vending pipeline requires a service principal, remote state backend, and management group permissions that do not yet exist.
An identity vending pipeline cannot vend itself. Day-0 bootstrap must be executed out-of-band by an authorized human operator with transient, elevated privileges, adhering to NIST AC-6 (Least Privilege) and EAM Control-plane isolation.
The 4-Step Bootstrap Progression¶
The azure-tenant-root repository structures the bootstrap into four isolated steps:
flowchart TD
subgraph P0["Phase 0: Human Elevation & Pre-Flight"]
OP["Engineer elevates to:<br/>• User Access Administrator @ '/'<br/>• Owner @ Management Subscription"]
end
subgraph S1["Step 001: State Backend Bootstrap"]
S1_RES["Local terraform apply (throwaway local state)<br/>• rg-terraform-state<br/>• sa<tenant>tenantroot (TLS 1.2+, locked)<br/>• Container: tenant-root"]
end
subgraph S2["Step 002: Seed Tenant Root CI Identity"]
S2_RES["Remote state: tenant-root<br/>• sp-pla-tf-tenant-root-ro (OIDC)<br/>• Reader @ Root MG<br/>• Storage Blob Data Contributor on tenant-root<br/>• Enables CI Plan Review for root"]
end
subgraph S3["Step 003: Seed Management Groups Identity"]
S3_RES["Remote state: tenant-root<br/>• sp-pla-tf-management-groups-rw (OIDC, read-only)<br/>• Reader @ Root MG<br/>• Container: management-groups"]
end
subgraph S4["Step 004: Seed SP Vending Identity"]
S4_RES["Remote state: tenant-root<br/>• sp-pla-tf-service-principals-ro (OIDC)<br/>• Reader @ mg-intermediate-root<br/>• Container: service-principals"]
end
subgraph HANDOFF["Downstream Platform Hand-off"]
MG["t0-azure-management-groups<br/>(MG hierarchy, just-in-time CI apply)"]
SP_VEND["t0-azure-service-principals<br/>(Vends platform SP fleet via Lane A)"]
end
OP --> S1
S1 --> S2
S2 --> S3
S3 --> S4
S3 -.-> MG
S4 -.-> SP_VEND
Phase 0: Human Elevation & Pre-Flight¶
Before touching Terraform, the human operator temporarily elevates privileges:
- User Access Administrator at Root (
/):
In Microsoft Entra ID $\rightarrow$ Properties $\rightarrow$ Toggle Access management for Azure resources to Yes. This allows granting RBAC roles at the Tenant Root Group. - Owner on the Management Subscription:
Required to create the central resource group, storage account, andCanNotDeletemanagement lock. - Validate Session:
Authenticate using Azure CLI:
Step 001: Bootstrap State Storage Backend¶
- Path:
step001-bootstrap-state-storage/ - Execution: Human operator, local
terraform apply, throwaway local.tfstate. - Purpose: Provisions the hardened remote state storage account that will house Terraform state for the entire tenant platform.
- Resources Created:
azurerm_resource_group.state(rg-terraform-state) in the Management Subscription.azurerm_storage_account.state(sa<tenant>tenantroot): Minimum TLS 1.2, HTTPS-only traffic enforced, public blob access disabled, blob versioning enabled, 30-day soft-delete retention policy.azurerm_storage_container.tenant_root(tenant-root): Private blob container.azurerm_management_lock.sa_lock:CanNotDeletelock on the storage account.
Once Step 001 succeeds, the local state file is discarded; all subsequent steps use remote state stored in sa<tenant>tenantroot.
Step 002: Seed Tenant Root Read-Only CI Identity¶
- Path:
step002-seed-tenant-root-identity/ - Execution: Human operator, local apply, remote state in
tenant-root/terraform.tfstate. - Purpose: Seeds the plan-only identity for
azure-tenant-rootso that pull requests in GitLab can run automated CI plans without human intervention. - Resources Created:
azuread_application&service_principal:sp-pla-tf-tenant-root-ro.azuread_application_federated_identity_credential: Keyless OIDC trust to GitLab repoproject_path:<org>/azure-tenant-root.azurerm_role_assignment:Readerat Tenant Root Group (/).azurerm_role_assignment:Storage Blob Data Contributoron thetenant-rootcontainer.azuread_directory_role_assignment:Directory Readersin Microsoft Entra ID.
Step 003: Seed Management Groups Pipeline Identity¶
- Path:
step003-seed-management-groups-identity/ - Execution: Human operator, local apply, remote state in
tenant-root/terraform.tfstate. - Purpose: Seeds the read-only identity that plans the management group hierarchy in the downstream
t0-azure-management-groupsrepository. - Resources Created:
azurerm_storage_container.management_groups: Containermanagement-groupsinsa<tenant>tenantroot.azuread_application&service_principal:sp-pla-tf-management-groups-rw(read-only, despite the name).- Flexible federated credential
gitlab-any-branch: OIDC trust to any branch oft0-azure-management-groups, by project ID. azurerm_role_assignment:Readerat the Tenant Root Group.azurerm_role_assignment:Storage Blob Data Readeron themanagement-groupscontainer (CI plans with-lock=false).
Scope Bounding
sp-pla-tf-management-groups-rw only plans. Changes are applied by a separate main-only identity, sp-pla-tf-t0-azure-management-groups-rw (vended by t0-azure-service-principals), which holds no standing roles: a PIM-activated senior gives it Management Group Contributor for 30 minutes per apply. See Tier 0 Just-in-Time CI Apply.
Step 004: Seed Service Principal Vending Identity¶
- Path:
step004-seed-sp-vending-identity/ - Execution: Human operator, local apply, remote state in
tenant-root/terraform.tfstate. - Purpose: Provisions the remote state container and plan-only CI identity for the downstream
t0-azure-service-principalsrepository. - Resources Created:
azurerm_storage_container.service_principals: Containerservice-principalsinsa<tenant>tenantroot.azuread_application&service_principal:sp-pla-tf-service-principals-ro.azuread_application_federated_identity_credential: OIDC trust to thet0-azure-service-principalsproject by ID (project_id:<id>), so a rename needs no change.azurerm_role_assignment:Readerscoped tomg-intermediate-rootand to the tenant root.azuread_directory_role_assignment:Directory Readersin Microsoft Entra ID.azurerm_role_assignment:Storage Blob Data Readeronservice-principalscontainer (CI plans with-lock=false).
Downstream Platform Hand-Off¶
With Steps 001 through 004 complete, the foundational trust anchor is locked:
t0-azure-management-groupsruns (Lane A, just-in-time CI apply) to build outmg-intermediate-root,mg-platform,mg-landingzones, andmg-sandboxes.t0-azure-service-principalsactivates under Lane A to vend all remaining platform and tooling machine identities (e.g., AzGovViz, EPAC, Monitoring SP, Conditional Access SP).subscription-vendingactivates under Lane B to vend workload subscriptions and application-level service principals.- The human operator de-elevates: Root User Access Administrator access is revoked, returning the tenant to day-to-day zero standing elevation.