[LZ 3.1] Standing Up the Governance Backbone

The build sheet for the governance backbone: elevate to the root once by hand, create the ten-group tree, place your subscription with the brownfield rule in mind, set the naming and tag standard, and prove inheritance flows.

This is the build sheet for the governance backbone. It takes the exact hierarchy from the doctrine article and stands it up: the management group tree, the placement of your first subscription, and the naming and tagging schema, ending with a test that proves inheritance actually flows. Two things here deserve your full attention. The first is an operation that cannot be scripted and has to be done by hand exactly once. The second is a brownfield rule that, if you ignore it, can break running workloads the instant you move a subscription. Both are called out where they occur.

Prerequisites: a Global Administrator account in the tenant, the Azure CLI signed in (az login), and the subscription ID of any existing subscription you intend to bring under governance. The naming convention used throughout is mg- followed by the lowercase group name, so the tree is mg-northfork, mg-platform, mg-production, and so on.

Step 1: Elevate to manage the root, once

No one, not even a Global Administrator, has access to the tenant root management group by default. Azure’s directory roles and its resource roles are separate authorization systems, so being Global Administrator in Entra grants you nothing over Azure resources until you bridge the gap deliberately. That bridge is a single, deliberate elevation, and you can perform it two ways with no difference in privilege between them. In the portal, as a Global Administrator, under Microsoft Entra ID properties, you set Access management for Azure resources to Yes. From a script, you make one call against the elevateAccess endpoint, authorized by your Global Administrator role rather than by any standing Azure access. The moment you do, you are assigned the User Access Administrator role at root scope, written as /.

Understand precisely what that role gives you, because it is a common trip-up. User Access Administrator lets you assign roles anywhere in the tenant. It does not let you create resources. So the toggle is step one of two: after elevating, you assign yourself a role that can actually build the hierarchy, Owner or Management Group Contributor, at the point in the tree where you are about to work, and only then do you create management groups. This setting applies only to the currently signed-in user, so sign out and back in to refresh your token before continuing. And because access at root scope is the most powerful position in the tenant, you turn the toggle back to No the moment the structural work is done. Elevated root access is a ladder you climb, use, and then kick away, not a standing condition.

ActionWhereWhy
Set Access management for Azure resources to YesEntra ID properties, portal onlyGrants User Access Administrator at root scope; cannot be scripted
Assign yourself Owner at the org group scopeAccess control on the new root of your treeUser Access Administrator can assign but not create; you need build rights
Set the toggle back to No when doneEntra ID propertiesRoot access is least-privilege sensitive; remove it after use
# The same elevation from a script, authorized by your Global Administrator role
az rest --method post \
  --url "/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01"

# ... create the management group tree in Step 2, then step back down ...
az role assignment delete \
  --assignee "[email protected]" \
  --role "User Access Administrator" \
  --scope "/"

Step 2: Create the management group tree

With build rights in place, create the full hierarchy. Each command names the group and points it at its parent, and because a child cannot be created before its parent exists, the order below matters. Top-level groups omit the parent flag, which places them directly under the tenant root. This is the complete tree for Northfork Supply Co.:

az account management-group create --name mg-northfork --display-name "Northfork"
az account management-group create --name mg-platform --display-name "Platform" --parent mg-northfork
az account management-group create --name mg-management --display-name "Management" --parent mg-platform
az account management-group create --name mg-connectivity --display-name "Connectivity" --parent mg-platform
az account management-group create --name mg-identity --display-name "Identity" --parent mg-platform
az account management-group create --name mg-security --display-name "Security" --parent mg-platform
az account management-group create --name mg-landingzones --display-name "Landing Zones" --parent mg-northfork
az account management-group create --name mg-production --display-name "Production" --parent mg-landingzones
az account management-group create --name mg-nonproduction --display-name "Non-Production" --parent mg-landingzones
az account management-group create --name mg-sandbox --display-name "Sandbox" --parent mg-northfork

If you prefer infrastructure as code, the same tree expressed in Bicep uses a tenant-scoped deployment and lets each child reference its parent symbolically, which makes Bicep resolve the ordering for you. The pattern for the first two levels is below, and every remaining node follows it identically by pointing its parent.id at the symbolic name of the group above it:

targetScope = 'tenant'

resource northfork 'Microsoft.Management/managementGroups@2023-04-01' = {
  name: 'mg-northfork'
  properties: { displayName: 'Northfork' }
}

resource platform 'Microsoft.Management/managementGroups@2023-04-01' = {
  name: 'mg-platform'
  properties: {
    displayName: 'Platform'
    details: { parent: { id: northfork.id } }
  }
}

resource landingZones 'Microsoft.Management/managementGroups@2023-04-01' = {
  name: 'mg-landingzones'
  properties: {
    displayName: 'Landing Zones'
    details: { parent: { id: northfork.id } }
  }
}

The Bicep Azure Verified Modules for platform landing zones will generate this and a great deal more, and at larger scale that is the right tool. At this scale the ten commands above are honest, legible, and easy to reason about, which is why I lead with them. Reach for the accelerator when the hand-built tree stops being enough, not before.

Step 3: Place your subscription, and the brownfield rule

Move your existing production subscription under the Production group. In a greenfield tenant this is trivial and safe. In a brownfield tenant it is the single riskiest move in this build sheet, and here is the rule you break at your peril: a subscription inherits every policy assigned above it the instant it is moved, and inheritance takes effect immediately. If a group above carries a policy in deny or deployIfNotExists mode, moving a subscription full of live resources under it can block operations or trigger changes on running workloads without warning. So before the move, confirm that the branch you are moving into carries no enforcing policy, or that your existing resources already comply with it. The safe sequence, which the policy article builds on, is to assign new policies in audit mode first, confirm the brownfield subscription passes clean, and only then promote anything to enforce. Move first into a quiet branch, govern second.

az account management-group subscription add \
  --name mg-production \
  --subscription 00000000-0000-0000-0000-000000000000

Step 4: Set the naming and tagging standard

The naming pattern is resource type, workload, environment, region, instance, so your first platform resource group in the production subscription is created as follows. Automated enforcement of names and tags is the policy article’s job; here you establish the standard by following it from the very first resource and applying the six required tags by hand so the pattern is set before anything scales.

az group create \
  --name rg-platform-prod-wus2-001 \
  --location westus2 \
  --tags Environment=Production Owner=Platform-Team \
         [email protected] CostCenter=IT-100 \
         Workload=Platform ManagedBy=Internal-IT
TagValue in exampleWhy it is required
EnvironmentProductionDeclares the governance profile for every resource
OwnerPlatform-TeamThe accountable team
Contacta named personWho to reach when it is on fire, distinct from the owning team
CostCenterIT-100Attributes spend correctly
WorkloadPlatformTraces the resource to the application it serves
ManagedByInternal-ITInternal, managed provider, or vendor

Validation

Confirm the tree exists and the subscription landed where intended:

az account management-group list --output table
az account management-group show --name mg-production --expand --output json

The first command should list all ten groups. The second should show your subscription nested under Production. If both are true, the structure is correct.

End-to-end test: prove inheritance flows

Structure that exists is not the same as structure that works. The point of the whole backbone is that something assigned high in the tree reaches everything beneath it, so prove exactly that with a harmless audit policy assigned at the top and observed at the bottom. Assign a built-in audit policy at the organization group, then confirm it appears at the subscription as an inherited assignment:

POLICY_ID=$(az policy definition list \
  --query "[?displayName=='Audit usage of custom RBAC roles'].name | [0]" -o tsv)

az policy assignment create \
  --name lz-inherit-test \
  --display-name "Backbone inheritance test" \
  --scope /providers/Microsoft.Management/managementGroups/mg-northfork \
  --policy "$POLICY_ID"

az policy assignment list \
  --disable-scope-strict-match \
  --scope /subscriptions/00000000-0000-0000-0000-000000000000 \
  --query "[?name=='lz-inherit-test'].{name:name, scope:scope}" -o table

The final command should return the assignment, with its scope showing the management group rather than the subscription, which is the proof that the subscription sees a policy it never had assigned directly. Inheritance flows. Now remove the test so it leaves nothing behind:

az policy assignment delete \
  --name lz-inherit-test \
  --scope /providers/Microsoft.Management/managementGroups/mg-northfork

With the tree standing, the subscription placed, the naming and tag standard set, and inheritance proven, the backbone is real rather than theoretical. Every article that follows assigns its policies, its access, and its resources into this structure, and each of those decisions is now cheap because the structure underneath them is correct.


Azure Landing Zones
‹ Previous: [LZ 3] The Governance Backbone
Next: [LZ 4] The Network You Cannot Redo Later