
Advanced workshop · 90–120 minutes · Terraform + PowerShell 7 + Azure CLI
An Azure landing zone is more than a folder tree. It is the place where subscription ownership, inherited policy, access control, observability, and automation become one repeatable platform contract.
In this deep-dive Workshopz lab, you will build a small but realistic platform landing zone beneath an existing Azure management-group parent. You will onboard two existing subscriptions safely, observe deliberately noncompliant resources in audit-only mode, promote the same guardrails to enforcement, prove two policy denials, validate inherited RBAC, centralize the workload subscription’s Activity Log, check Terraform drift, and restore the original tenant state.
Cost and safety warning: The default lab does not create a subscription. It temporarily creates a Log Analytics workspace, identities, resource groups, a diagnostic setting, and a Standard LRS storage account, so a small Azure charge is possible. Run the verified cleanup when finished. Real subscription creation is an optional, explicitly disabled path because it can create a billable subscription that Terraform cleanup cannot delete.
Architecture
Tenant Root
└── Workshopz ALZ
├── Platform
│ └── Management
│ └── Existing management subscription
├── Landing Zones
│ ├── Corp
│ └── Online
│ └── Existing workload subscription
└── Sandbox
The management subscription holds a central Log Analytics workspace. The workload subscription sends its subscription Activity Log there. A user-assigned identity receives Reader at the Workshopz root, and another receives Contributor at Online, so both permissions can be inspected as inherited at the workload subscription without Microsoft Graph permissions.
What you will prove
The read-only preflight refuses unsafe starting conditions and records both original subscription parents.
Terraform creates the isolated hierarchy and safely onboards both existing subscriptions.
Pre-existing workload resource groups are excluded dynamically from the workshop policy assignment.
DoNotEnforce permits deliberate violations but Azure Policy reports them.
Default enforcement denies an untagged North Europe resource group and a tagged East US resource group.
A tagged North Europe resource group and secured storage account succeed.
Reader and Contributor are inherited at the workload subscription.
Successful and denied operations reach the management workspace.
Terraform reports no drift and cleanup restores the protected baseline.
Before you begin
You need two existing subscriptions in one tenant. The signed-in principal needs Owner on both and User Access Administrator at the tenant-root management group. Install PowerShell 7, Azure CLI, and Terraform 1.15.8.
On Windows, extract the workshop to a short location such as C:\awz-lab; deeply nested Terraform modules can exceed the legacy path-length limit.
az login
az account set --subscription "<management-subscription-id>"
az account show --output table
terraform version
$PSVersionTable.PSVersion
$managementSubscriptionId = "<management-subscription-id>"
$workloadSubscriptionId = "<workload-subscription-id>"
$parentManagementGroupId = "<tenant-id-or-existing-management-group-name>"
Keep these identifiers only in shell memory. The scripts place runtime state, baselines, plans, and raw evidence under the ignored work/ directory.
Stage 1 — Read-only preflight and baseline capture
pwsh ./Start-AzureLandingZoneWorkshop.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-ParentManagementGroupId $parentManagementGroupId `
-Stage Preflight
Preflight performs zero Azure mutations. It validates the signed-in tenant and roles, checks for existing awz-* names, refuses unexpected North Europe resources, verifies the requested built-in policy-version endpoints, discovers the current parent of each subscription, and snapshots the protected baseline.
It also enumerates every pre-existing workload resource group and injects its resource ID into existing_resource_group_not_scopes. The workshop never adopts, retags, or otherwise changes those groups.
Stage 2 — Temporary tenant-root bootstrap
pwsh ./Start-AzureLandingZoneWorkshop.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-ParentManagementGroupId $parentManagementGroupId `
-Stage Bootstrap
The script checks whether Management Group Contributor is already effective. If it is missing, the lab creates one temporary assignment at the tenant root and records its exact resource ID. Cleanup deletes only that recorded assignment.
Stage 3 — Build the platform and onboard subscriptions
pwsh ./Start-AzureLandingZoneWorkshop.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-ParentManagementGroupId $parentManagementGroupId `
-Stage Platform
The deployment uses Azure/avm-ptn-alz/azurerm 0.21.0 for the custom hierarchy and Azure/avm-ptn-alz-sub-vending/azure 0.3.0 for existing-subscription association. AzureRM 4.79.0, AzAPI 2.11.0, and Azure ALZ 0.21.0 are pinned, and provider checksums are committed in .terraform.lock.hcl.
The first apply uses enforcement_mode = "DoNotEnforce". It creates the central workspace, the two self-contained identities, inherited role assignments, and the workload Activity Log diagnostic setting.
The versioned guardrail initiative
Online receives the custom AzureWorkshopz-Guardrails-v1 initiative:
Allowed locations, requested built-in version 1.1.0: North Europe and West Europe.
Require a tag and its value on resource groups, version 1.0.0: ManagedBy=LandingZoneVending.
Storage accounts should restrict network access, version 1.1.1: Audit.
Allowed locations for resource groups, version 1.1.0: the companion control that governs the location of the resource-group container.
There is an important implementation detail here. Azure initiative references accept a patch wildcard such as 1.1.*, not an exact patch such as 1.1.0. The read-only preflight verifies that each requested exact version endpoint exists, while the initiative constrains Azure to the requested minor line. The extra resource-group policy is required because the general Allowed locations policy deliberately excludes resource groups themselves.
Stages 4–5 — Deliberate failure in audit-only mode
pwsh ./Start-AzureLandingZoneWorkshop.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-ParentManagementGroupId $parentManagementGroupId `
-Stage Audit `
-PolicyWaitSeconds 1200
The audit stage creates an untagged North Europe resource group, an East US resource group, and an intentionally open storage account. They are deliberately wrong. Because enforcement is disabled, Azure permits them.
The script triggers policy evaluation at subscription and resource-group scope, waits for noncompliant states, records sanitized counts, and deletes the audit resources before promotion. In the tested run, Azure Policy reported three noncompliant state records while both pre-existing workload resource groups remained excluded.
This audit-first step matters in production. It reveals scope, parameter, exclusion, and policy-effect mistakes before a deny assignment can block delivery teams.
Stage 6 — Promote to enforcement and prove both denials
pwsh ./Start-AzureLandingZoneWorkshop.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-ParentManagementGroupId $parentManagementGroupId `
-Stage Enforce `
-PolicyWaitSeconds 1200
Terraform changes only the assignment’s enforcement_mode to Default and creates the compliant secured storage account. The test waits for policy propagation and then performs two negative deployments:
untagged North Europe resource group → RequestDisallowedByPolicy
tagged East US resource group → RequestDisallowedByPolicy
The positive control is equally important. rg-awz-online-ne uses North Europe and ManagedBy=LandingZoneVending; the storage account disables public network access and shared-key authentication and requires TLS 1.2.
Stage 7 — Validate inherited RBAC and centralized logging
pwsh ./Get-AzureLandingZoneEvidence.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-LogWaitSeconds 1200
The evidence collector proves that:
the management subscription is associated with Management;
the workload subscription is associated with Online;
Reader assigned at Workshopz ALZ is visible at the workload subscription;
Contributor assigned at Online is visible at the workload subscription;
send-to-awz-management points the workload Activity Log to the central workspace;
the workspace contains successful operations, failed operations, and policy-denied operations from workshop resource groups.
The public evidence contains aggregate counts only. Tenant IDs, subscription IDs, object IDs, emails, raw event payloads, and local paths are never published.
Stage 8 — Prove zero Terraform drift
The Validate stage wraps the evidence collection and a detailed-exit-code plan:
pwsh ./Start-AzureLandingZoneWorkshop.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-ParentManagementGroupId $parentManagementGroupId `
-Stage Validate `
-PolicyWaitSeconds 1200
Terraform uses exit code 0 for no changes, 2 for a non-empty plan, and 1 for an error. The tested run returned 0 after all validation operations.
Stage 9 — Ordered cleanup and baseline proof
pwsh ./Remove-AzureLandingZoneWorkshop.ps1 `
-ManagementSubscriptionId $managementSubscriptionId `
-WorkloadSubscriptionId $workloadSubscriptionId `
-ConfirmCleanup
Cleanup destroys the workshop resources in dependency order, restores both recorded subscription parents, removes the temporary root assignment only if the lab created it, and compares current resources, groups, roles, and workload diagnostic settings with the preflight baseline.
Success means zero remaining Workshopz management groups and resource groups, no workshop policy or role assignments, no central workspace, no workshop diagnostic setting, both original parents restored, and all protected pre-existing state unchanged.
Optional real subscription creation — disabled by design
stages/03-real-vending.tfvars.example demonstrates the AVM subscription-alias path for an eligible EA, MCA, or MPA billing scope. It is not part of the default lab.
Terraform refuses the path unless all three gates are present:
enable_real_subscription_creation = true
billing_scope = "/providers/Microsoft.Billing/..."
acknowledge_billable_subscription = true
This creates a real, potentially billable subscription. terraform destroy cannot cancel or delete it. Production vending therefore needs a separate, approved subscription decommissioning and billing process.
Troubleshooting matrix
| Symptom | Cause | Resolution | |---|---|---| | Preflight stops on existing names | The isolated namespace is already in use | Clean a known prior lab or choose clean subscriptions. Never bypass the collision check. | | Preflight stops on North Europe resources | The workload subscription is not empty in the lab region | Use a clean workload subscription or deliberately adapt the safety model and baseline before running. | | terraform init fails on Windows paths | Module extraction exceeded the legacy path limit | Extract the ZIP to C:\awz-lab and initialize there. | | Subscription association is denied | Management-group RBAC is missing or still propagating | Confirm root permissions, wait several minutes, and rerun the same idempotent stage. | | Denial tests unexpectedly succeed | Assignment propagation is incomplete | Trigger a policy scan and rerun Enforce with a larger wait value. | | Compliance is Unknown | Azure Policy evaluation is asynchronous | Wait and refresh Policy > Compliance after the scan completes. | | Log query returns no rows | Diagnostic setting or ingestion has not propagated | Confirm the diagnostic setting, perform an operation in rg-awz-*, and rerun Validate after 10–20 minutes. | | Cleanup baseline comparison fails | An unrelated change occurred during the lab | Compare the private snapshots and restore only the identified external change. Do not weaken the cleanup assertion. |
Production guidance
For production, store Terraform state in a private, locked, recoverable remote backend; use workload identity federation; separate platform and workload duties with PIM; promote policy through canary scopes; govern exemptions with owners and expiry; integrate vending with IPAM, connectivity, DNS, budgets, Defender, CMDB, and decommissioning; and extend central monitoring with retention, archive, alerts, and Sentinel.
The key design principle is simple: subscription vending is a product interface. A subscription becomes usable only when hierarchy, policy, access, logging, network, cost, and lifecycle controls arrive together.
Comments