Skip to content

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&lt;tenant&gt;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)<br/>• Management Group Contributor @ 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["azure-management-groups<br/>(Deploys MG hierarchy)"]
        SP_VEND["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:

  1. 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.
  2. Owner on the Management Subscription:
    Required to create the central resource group, storage account, and CanNotDelete management lock.
  3. Validate Session:
    Authenticate using Azure CLI:
    az login --tenant <tenant-id>
    az account set --subscription <management-subscription-id>
    

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: CanNotDelete lock 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-root so 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 repo project_path:<org>/azure-tenant-root.
  • azurerm_role_assignment: Reader at Tenant Root Group (/).
  • azurerm_role_assignment: Storage Blob Data Contributor on the tenant-root container.
  • azuread_directory_role_assignment: Directory Readers in 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 machine identity that manages the management group hierarchy in the downstream azure-management-groups repository.
  • Resources Created:
  • azurerm_storage_container.management_groups: Container management-groups in sa<tenant>tenantroot.
  • azuread_application & service_principal: sp-pla-tf-management-groups-rw.
  • azuread_application_federated_identity_credential: OIDC trust to azure-management-groups.
  • azurerm_role_assignment: Management Group Contributor at Tenant Root Group (/).
  • azurerm_role_assignment: Storage Blob Data Contributor on the management-groups container.

Scope Bounding

sp-pla-tf-management-groups-rw holds Management Group Contributor to organize groups, but deliberately holds no User Access Administrator or Owner at root. Role assignments at root remain strictly gated to human operators.


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 azure-service-principals repository.
  • Resources Created:
  • azurerm_storage_container.service_principals: Container service-principals in sa<tenant>tenantroot.
  • azuread_application & service_principal: sp-pla-tf-service-principals-ro.
  • azuread_application_federated_identity_credential: OIDC trust to azure-service-principals.
  • azurerm_role_assignment: Reader scoped to mg-intermediate-root.
  • azuread_directory_role_assignment: Directory Readers in Microsoft Entra ID.
  • azurerm_role_assignment: Storage Blob Data Contributor on service-principals container (for CI planning).

Downstream Platform Hand-Off

With Steps 001 through 004 complete, the foundational trust anchor is locked:

  1. azure-management-groups runs to build out mg-intermediate-root (mg-grinntec), mg-platform, mg-landingzones, and mg-sandboxes.
  2. azure-service-principals activates under Lane A to vend all remaining platform and tooling machine identities (e.g., AzGovViz, EPAC, Monitoring SP, Conditional Access SP).
  3. subscription-vending activates under Lane B to vend workload subscriptions and application-level service principals.
  4. The human operator de-elevates: Root User Access Administrator access is revoked, returning the tenant to day-to-day zero standing elevation.