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 smallbuiltin_evaluatortoo.canonical_alz_tree()— the reference ALZ hierarchy you must be able to draw from memory.
Key concepts
| Concept | What to understand |
|---|---|
| MG tree above subscriptions | management groups form a tree above subscriptions; one Tenant Root Group at the top |
| Depth ≤ 6 | Azure caps the hierarchy at 6 levels of MGs under root; depth 7 must be rejected |
| One MG per subscription | a subscription belongs to exactly one management group — vending is placement |
| Inheritance is downward | a policy/RBAC assignment at an MG applies to every descendant scope |
| Effective = ∪ − exclusions | local ∪ inherited − notScopes − exemptions; the whiteboard formula |
notScopes vs exemption | notScopes excludes a scope from one assignment; an exemption suppresses an assignment (time-boxed) |
| Compliance = any-fail | a resource is non-compliant if any policy flags it; audit counts as non-compliant even though it does not block |
Files
| File | Purpose |
|---|---|
lab.py | skeleton with # TODO markers |
solution.py | complete reference; python solution.py runs a worked example |
test_lab.py | the proof — run it red, make it green |
requirements.txt | pytest 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_sevenmatters — 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_assignmentsreturns results most-local-first and why that ordering is deterministic (sort by ancestor depth, then name). - You can articulate the difference between a
notScopesexclusion (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 construct | Real Azure |
|---|---|
parent_map / build_tree | the 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.scope | the scope of a policy/role assignment (an MG id, /subscriptions/{id}, /resourceGroups/{rg}, or a resource id) |
not_scopes | the notScopes array on a policy assignment (and excludedScopes on initiatives) |
exemptions | Microsoft.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_assignmentsand 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_scoreaccept 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
effectin the allowed set, and reject a PR that introduces adenywithout an approval label.