Azure landing-zone hierarchy and policy lab
Compiles a bounded Azure landing-zone Bicep example without deploying cloud resources.
Cloud Security 2 min read
Implementation: Partially tested
Implementation
labs/azure-landing-zone/main.bicep
1targetScope = 'tenant'
2
3@description('Stable management group identifier for the organization landing-zone root.')
4param rootManagementGroupId string
5
6@description('Display name for the organization landing-zone root.')
7param rootDisplayName string
8
9@description('Azure region required for tenant-scope deployment metadata.')
10param deploymentLocation string = 'canadacentral'
11
12@description('Locations audited by the sample baseline policy.')
13param allowedLocations array = [
14 'canadacentral'
15 'canadaeast'
16]
17
18resource root 'Microsoft.Management/managementGroups@2023-04-01' = {
19 name: rootManagementGroupId
20 properties: {
21 displayName: rootDisplayName
22 }
23}
24
25resource platform 'Microsoft.Management/managementGroups@2023-04-01' = {
26 name: '${rootManagementGroupId}-platform'
27 properties: {
28 displayName: 'Platform'
29 details: {
30 parent: {
31 id: root.id
32 }
33 }
34 }
35}
36
37resource landingZones 'Microsoft.Management/managementGroups@2023-04-01' = {
38 name: '${rootManagementGroupId}-landing-zones'
39 properties: {
40 displayName: 'Landing zones'
41 details: {
42 parent: {
43 id: root.id
44 }
45 }
46 }
47}
48
49resource sandbox 'Microsoft.Management/managementGroups@2023-04-01' = {
50 name: '${rootManagementGroupId}-sandbox'
51 properties: {
52 displayName: 'Sandbox'
53 details: {
54 parent: {
55 id: root.id
56 }
57 }
58 }
59}
60
61module baseline 'modules/policy-baseline.bicep' = {
62 name: 'landing-zone-audit-baseline'
63 scope: managementGroup(rootManagementGroupId)
64 params: {
65 allowedLocations: allowedLocations
66 assignmentLocation: deploymentLocation
67 }
68 dependsOn: [
69 root
70 ]
71}
72
73output managementGroupIds object = {
74 root: root.id
75 platform: platform.id
76 landingZones: landingZones.id
77 sandbox: sandbox.id
78}
1targetScope = 'managementGroup'
2
3param allowedLocations array
4param assignmentLocation string
5
6resource allowedLocationsDefinition 'Microsoft.Authorization/policyDefinitions@2023-04-01' = {
7 name: 'audit-allowed-locations'
8 properties: {
9 displayName: 'Audit resources outside reviewed locations'
10 description: 'Teaching policy: audit, rather than deny, while teams inventory exemptions and validate impact.'
11 policyType: 'Custom'
12 mode: 'Indexed'
13 metadata: {
14 category: 'Landing zone lab'
15 version: '1.0.0'
16 }
17 parameters: {
18 allowedLocations: {
19 type: 'Array'
20 metadata: {
21 displayName: 'Allowed locations'
22 }
23 }
24 }
25 policyRule: {
26 if: {
27 allOf: [
28 {
29 field: 'location'
30 exists: 'true'
31 }
32 {
33 field: 'location'
34 notIn: '[parameters(\'allowedLocations\')]'
35 }
36 ]
37 }
38 then: {
39 effect: 'audit'
40 }
41 }
42 }
43}
44
45resource allowedLocationsAssignment 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
46 name: 'audit-allowed-locations'
47 location: assignmentLocation
48 properties: {
49 displayName: 'Audit resources outside reviewed locations'
50 description: 'Lab assignment begins in audit mode. Promote only after impact analysis, exemption ownership, and rollback validation.'
51 policyDefinitionId: allowedLocationsDefinition.id
52 enforcementMode: 'Default'
53 parameters: {
54 allowedLocations: {
55 value: allowedLocations
56 }
57 }
58 }
59}
60
61output policyDefinitionId string = allowedLocationsDefinition.id
62output policyAssignmentId string = allowedLocationsAssignment.id
Run it
npm run verify:bicep
This lab compiles a small tenant-scope Bicep design for a management-group hierarchy and an audit-first custom policy assignment. It demonstrates deployment shape, federated CI identity, and a reviewable what-if boundary; it does not deploy during repository validation.
Architecture decision
The sample separates platform, landing-zone, and sandbox subscriptions beneath an organization root. The policy begins with audit, because changing a broad management-group policy to deny before impact analysis can interrupt workloads. Production design still needs connectivity, identity, security, decommissioned, and workload-archetype management groups based on the organization's operating model.
Prerequisites and compile
- Azure CLI 2.83.0 or later with Bicep support.
- No Azure login is needed to compile.
az bicep build --file labs/azure-landing-zone/main.bicep
Expected result: Bicep returns a compiled ARM template without diagnostics. The repository code validator performs this compilation when Azure CLI is available and reports an explicit limitation otherwise.
What-if, not deployment
After replacing every angle-bracket placeholder, an authorized reviewer may inspect:
az login --tenant <tenant-id> --allow-no-subscriptions
az deployment tenant what-if
--name landing-zone-review
--location canadacentral
--template-file labs/azure-landing-zone/main.bicep
--parameters '@labs/azure-landing-zone/parameters.example.json'
The included workflow uses GitHub OIDC and a protected environment. Configure the federated credential with an exact repository/environment subject, constrain the service principal to the required tenant/management-group operations, and require a human review of the what-if artifact. Do not add a client secret.
Deployment and rollback considerations
Management-group and policy changes have a wide blast radius. Roll out new policy in audit mode, inventory noncompliance, assign named exemption owners with expiry, pilot a narrow management group, then move to deny only with tested break-glass and rollback procedures. Removing a policy assignment does not undo resource mutations performed by deployIfNotExists or modify; this sample deliberately uses audit.
Negative cases to review
- placeholder or duplicate management-group IDs;
- an OIDC subject broader than the protected environment;
- policy enforcement before exemptions and regional dependencies are inventoried;
- a hierarchy move that changes inherited role and policy scope;
- subscription vending without ownership, budget, network, logging, and lifecycle metadata;
- a tenant-level deployment identity with permanent credentials or excessive roles.
Cleanup and limitations
Compilation creates main.json; remove that generated file after inspection. This lab does not execute what-if in CI because it has no authorized tenant, and it does not deploy, move subscriptions, create role assignments, configure networking, or validate tenant-specific Azure Policy aliases. What-if itself is predictive output, not proof of runtime correctness.