Lab 01 — Landing-Zone Resolver

Phase: 05 — Azure Landing Zones & Management-Group Scale | Difficulty: ⭐⭐⭐☆☆ | Time: 3–5 hours

Phase 04 answered "given an assignment, does this request pass?" This lab answers the question that comes first: "which assignments even apply here?" The answer is a tree walk — a subscription inherits every policy and role assignment from its management-group chain up to the Tenant Root Group, minus the explicit carve-outs. You build that resolver, the management-group tree it walks, the subscription-vending placement that puts a new subscription under the right node, and the compliance scorer that grades the whole estate. This is the engine behind an Azure Landing Zone.

What you build

  • build_tree(parent_map) — validate a management-group hierarchy (one root, no dangling parents, no cycles, depth ≤ 6 levels of MGs under root) — the structural invariants ARM enforces.
  • ancestors(mg_id, parent_map) — the scope chain leaf→root; the order you reason in during an incident ("start at the resource, walk up to find who can touch it").
  • Assignment + effective_assignments(scope, assignments, hierarchy, exemptions) — the governance law: local ∪ inherited − notScopes − exemptions, returned in a deterministic order (most-local first, then name).
  • place_subscription(sub_id, target_mg, parent_map) — subscription vending: attach a subscription under an MG so it inherits that MG's baseline; a subscription belongs to exactly one MG.
  • compliance_score(resources, initiative, evaluator) — score an estate against an initiative (policy set); a resource is non-compliant if any policy returns a failing effect. Returns {total, compliant, non_compliant, by_policy, score_pct}. Ships a small builtin_evaluator too.
  • canonical_alz_tree() — the reference ALZ hierarchy you must be able to draw from memory.

Key concepts

ConceptWhat to understand
MG tree above subscriptionsmanagement groups form a tree above subscriptions; one Tenant Root Group at the top
Depth ≤ 6Azure caps the hierarchy at 6 levels of MGs under root; depth 7 must be rejected
One MG per subscriptiona subscription belongs to exactly one management group — vending is placement
Inheritance is downwarda policy/RBAC assignment at an MG applies to every descendant scope
Effective = ∪ − exclusionslocal ∪ inherited − notScopes − exemptions; the whiteboard formula
notScopes vs exemptionnotScopes excludes a scope from one assignment; an exemption suppresses an assignment (time-boxed)
Compliance = any-faila resource is non-compliant if any policy flags it; audit counts as non-compliant even though it does not block

Files

FilePurpose
lab.pyskeleton with # TODO markers
solution.pycomplete reference; python solution.py runs a worked example
test_lab.pythe proof — run it red, make it green
requirements.txtpytest only (pure stdlib otherwise)

Run

pip install -r requirements.txt
pytest test_lab.py -v                      # against your lab.py
LAB_MODULE=solution pytest test_lab.py -v  # against the reference
python solution.py                         # the worked example

Success criteria

  • All tests pass against your implementation.
  • You can explain why test_build_tree_rejects_depth_seven matters — Azure caps MG depth at 6 levels under root, and the off-by-one (6 allowed, 7 rejected) is exactly the kind of limit a principal carries.
  • You can explain why effective_assignments returns results most-local-first and why that ordering is deterministic (sort by ancestor depth, then name).
  • You can articulate the difference between a notScopes exclusion (a scope is carved out of one assignment) and an exemption (an assignment is suppressed entirely, usually time-boxed) — interviewers probe this.
  • You can draw the canonical ALZ management-group hierarchy from memory and say what each node is for.

How this maps to real Azure

Lab constructReal Azure
parent_map / build_treethe management-group hierarchy under your Tenant Root Group; az account management-group create --parent …
ancestors(...)the scope chain ARM walks to compute effective access (MG → … → Tenant Root)
Assignment.scopethe scope of a policy/role assignment (an MG id, /subscriptions/{id}, /resourceGroups/{rg}, or a resource id)
not_scopesthe notScopes array on a policy assignment (and excludedScopes on initiatives)
exemptionsMicrosoft.Authorization/policyExemptions — documented, often time-boxed carve-outs
effective_assignments(...)what az policy state / the Defender for Cloud / "Policy → Compliance" blade compute when they show effective assignments at a scope
place_subscription(...)az account management-group subscription add — the placement step of subscription vending
initiative / compliance_score(...)a policy initiative (policy set) and the estate-wide compliance percentage on the Policy compliance dashboard

What the miniature omits (and where it lives): the real policy condition language (field, count, anyOf, aliases) is Phase 04's job — here the evaluator is a callback so the resolver stays about scope, not condition. Real inheritance also interacts with deny assignments (P04) and RBAC Actions/NotActions wildcard matching (P04); this lab assumes those are resolved and focuses on which assignments are in scope. Management groups also cache for up to a few minutes after a move (eventual consistency), which the deterministic lab does not model.

The az one-liners this stands in for:

az account management-group create --name platform --parent root
az account management-group subscription add --name corp --subscription <sub-id>
az policy assignment create --name require-tags --scope <mg-id> \
    --policy-set-definition <initiative-id> --not-scopes <sandbox-mg-id>
az policy state summarize --management-group <mg-id>   # the compliance score

Extensions

  • Add deny assignments to effective_assignments and make a deny at any ancestor win over an allow (mirrors P04's "deny always wins") — then test that an MG-level deny masks a subscription-level allow.
  • Add effective_diff(scope_a, scope_b, ...) returning the assignments that differ between two scopes — the "why is Corp locked down but Online isn't?" query.
  • Make compliance_score accept a per-scope estate (resources tagged with their scope) and roll the score up the MG tree, so each MG shows its own and its descendants' score — the real Policy compliance dashboard.
  • Add policy-as-code validation: parse an initiative from JSON, assert every policy has a declared effect in the allowed set, and reject a PR that introduces a deny without an approval label.