Cloud & infrastructure

Azure Policy Tag Enforcement: Require, Inherit and Remediate Tags

Enforce CostCenter and Owner tags with built-in Azure Policy definitions: deny untagged resource groups, inherit tags onto resources, and remediate existing resources with managed identities.

11 min read
On this page

To enforce mandatory tags such as CostCenter and Owner in Azure, assign the built-in policy Require a tag on resource groups (deny effect) once per tag, then assign Inherit a tag from the resource group if missing (modify effect, with a managed identity) so every resource picks the value up from its resource group. Existing resources aren't changed by the modify effect until you run a remediation task, which uses the assignment's managed identity to add the missing tags. Tag your existing resource groups before remediating, because the inherit policy only acts where the resource group already has a value.

Who this is for and what you will have at the end

This guide is for platform and FinOps teams who need every cost to land against a cost centre and an accountable owner, and who already have thousands of resources with inconsistent tags.

At the end you will have:

  • New resource groups blocked unless they carry CostCenter and Owner.
  • New and updated resources tagged automatically from their resource group.
  • Existing resources remediated in bulk with tracked remediation tasks.
  • A view of compliance per assignment, and an optional billing-side safety net in Cost Management.

How the pieces fit

Three facts drive the design:

  1. Tags don't flow down by themselves. Resources don't inherit resource group or subscription tags.
  2. Modify runs before deny. For a create or update request, Azure Policy evaluates disabled, then append and modify, then deny, then audit. A modify policy can add a tag before a deny policy checks for it.
  3. Modify doesn't touch existing resources during scans. It marks them non-compliant. A remediation task fixes them.

The built-in definitions you need:

Built-in definitionDefinition IDEffectMode
Require a tag on resource groups96670d01-0a4d-4649-9c89-2d3abc0a5025denyAll
Inherit a tag from the resource group if missingea3f2387-9b95-492a-a190-fcdc54f7b070modify (add)Indexed
Inherit a tag from the resource groupcd3aa116-8754-49c9-a813-ad46512ece54modify (addOrReplace)Indexed
Require a tag on resources871b6d14-10aa-478d-b590-94f262ecfa99denyIndexed
Add a tag to resources4f9dc7db-30c1-420c-b61a-e1d640128d26modify (add)Indexed

Choose the inherit variant per tag. If missing uses the add operation, so a resource that already has a different CostCenter keeps it; that suits shared resources billed elsewhere. The plain Inherit variant uses addOrReplace and overwrites any value that differs from the resource group's, which suits tags that must never diverge. Both built-in inherit definitions only act when the resource group's tag isn't empty, and both use Indexed mode, so they skip resource groups and resource types that don't support tags.

Prerequisites

  • Rights at the target scope (management group or subscription) to create policy assignments and role assignments, for example Owner.
  • Azure CLI signed in to the right tenant.
  • An agreed tag taxonomy. Tag names are case-insensitive for operations, but tag values are case-sensitive, so Finance and finance are different cost centres. Each resource, resource group and subscription supports up to 50 tags, names up to 512 characters and values up to 256 characters.
  • A list of resource groups created by platform services, which you may need to exclude later.

Step 1: Assign the resource group requirement in audit-only mode

Start with DoNotEnforce. The assignment evaluates compliance but doesn't block requests or write deny entries to the Activity log, so you see the size of the problem first.

SUB="/subscriptions/00000000-0000-0000-0000-000000000000"
 
az policy assignment create \
  --name require-rg-costcenter \
  --display-name "Require CostCenter tag on resource groups" \
  --policy 96670d01-0a4d-4649-9c89-2d3abc0a5025 \
  --scope "$SUB" \
  --params '{ "tagName": { "value": "CostCenter" } }' \
  --enforcement-mode DoNotEnforce
 
az policy assignment create \
  --name require-rg-owner \
  --display-name "Require Owner tag on resource groups" \
  --policy 96670d01-0a4d-4649-9c89-2d3abc0a5025 \
  --scope "$SUB" \
  --params '{ "tagName": { "value": "Owner" } }' \
  --enforcement-mode DoNotEnforce

A new assignment takes about five minutes to apply, then the evaluation cycle runs; on a large scope there is no fixed completion time. To start a scan yourself:

az policy state trigger-scan --no-wait
az policy state summarize -a require-rg-costcenter

If you plan to group several tag policies, Microsoft's tag governance tutorial recommends combining them into an initiative and assigning that once. The individual assignments here keep each step easy to follow.

Step 2: Tag the existing resource groups

The inherit policies have nothing to copy until resource groups carry values. Get the non-compliant list and tag each group with a merge operation, which keeps existing tags:

az policy state list -a require-rg-costcenter \
  --filter "ComplianceState eq 'NonCompliant'" \
  --query "[].resourceId" -o tsv
 
az tag update \
  --resource-id "$SUB/resourcegroups/rg-payments-prod" \
  --operation Merge \
  --tags CostCenter=CC-4100 Owner=payments-team@contoso.com

Use Merge, not Replace; Replace overwrites the whole tag set. Tags are stored as plain text and appear in cost reports, exports and logs, so don't put personal or sensitive data in them; a team mailbox is a better owner value than a person.

Step 3: Assign the inherit policies with a managed identity

Modify assignments need a managed identity, and that identity needs the roles listed in the definition's roleDefinitionIds. The built-in inherit definitions list Contributor. The portal grants the role automatically when it creates a system-assigned identity; from the CLI you pass --role and --identity-scope, otherwise remediation fails for lack of permission.

az policy assignment create \
  --name inherit-costcenter \
  --display-name "Inherit CostCenter from resource group if missing" \
  --policy ea3f2387-9b95-492a-a190-fcdc54f7b070 \
  --scope "$SUB" \
  --params '{ "tagName": { "value": "CostCenter" } }' \
  --mi-system-assigned \
  --identity-scope "$SUB" \
  --role Contributor \
  --location westeurope
 
az policy assignment create \
  --name inherit-owner \
  --display-name "Inherit Owner from resource group if missing" \
  --policy ea3f2387-9b95-492a-a190-fcdc54f7b070 \
  --scope "$SUB" \
  --params '{ "tagName": { "value": "Owner" } }' \
  --mi-system-assigned \
  --identity-scope "$SUB" \
  --role Contributor \
  --location westeurope

The --location sets where the system-assigned identity lives; it can't be global and can't be changed later, but it doesn't affect how the identity works. Each assignment has one identity, which can hold several roles. If you later edit roleDefinitionIds in a custom definition, grant the new roles manually, even in the portal.

From this point, any resource created or updated in a tagged resource group gets the missing tags in the same request.

Writing your own definition instead

If you need behaviour the built-ins don't offer, such as setting an Env tag from a resource group naming pattern, write a custom modify definition. The tag governance tutorial's CostCenter rule shows the shape of the then block:

"then": {
  "effect": "modify",
  "details": {
    "roleDefinitionIds": [
      "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
    ],
    "operations": [
      {
        "operation": "add",
        "field": "tags['CostCenter']",
        "value": "[resourcegroup().tags['CostCenter']]"
      }
    ]
  }
}

Use Indexed mode for rules that target resources and All for rules that target resource groups. conflictEffect defaults to deny; it decides what happens when two modify definitions change the same property.

Step 4: Remediate existing resources

Wait for the inherit assignments to finish evaluating, then create one remediation task per assignment. A task remediates one modify policy at a time.

In the portal:

  1. Open Policy and select Remediation.
  2. On Policies to remediate, select the inherit assignment to open New remediation task.
  3. Optionally narrow the Scope to child resource groups or resources and filter by Locations.
  4. Select Remediate and follow progress on the Remediation tasks tab. For a failed resource, select Related events to see the error.

From the CLI, using the assignment's resource ID:

az policy remediation create \
  --name remediate-costcenter \
  --policy-assignment "$SUB/providers/Microsoft.Authorization/policyAssignments/inherit-costcenter"

From PowerShell you can tune throughput. -ResourceCount defaults to 500 and accepts up to 50,000; -ParallelDeploymentCount accepts 1 to 30, default 10; -FailureThreshold is a percentage, default 100.

Start-AzPolicyRemediation -Name 'remediate-owner' `
  -PolicyAssignmentId '/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/inherit-owner' `
  -ResourceCount 50000 -ParallelDeploymentCount 30

For assignments made at management group scope, create remediation tasks after evaluation has run, from Remediation or from the assignment's compliance page with Create Remediation Task.

Step 5: Switch on enforcement

When the resource group compliance report is clean, move the deny assignments to enforced mode:

az policy assignment update --name require-rg-costcenter --enforcement-mode Default
az policy assignment update --name require-rg-owner --enforcement-mode Default

Assignments also accept a custom non-compliance message. Use it to tell people which tag values are valid and where to get a cost centre code; it is shown in addition to the default deny error.

Should you also require tags on every resource?

Require a tag on resources adds a hard stop at resource level. Because modify runs first, new resources in tagged resource groups pass. The risks are elsewhere: deny also blocks updates to existing resources that are still untagged, and some platform services create their own resource groups and resources. Azure Backup, for example, creates a resource group for VM restore point collections, and a deny tag policy causes UserErrorRequestDisallowedByPolicy on snapshot jobs. If you add resource-level deny, remediate first and exclude such scopes with --not-scopes or exemptions. See Azure VM backup with Recovery Services vaults for that failure in context.

Step 6: Add Cost Management tag inheritance

Cost Management has its own tag inheritance setting that applies billing, subscription and resource group tags to child resource usage records, not to the resources. Because it works on cost data rather than on resources, it catches spend on resources that were missed or can't be tagged. It is available for Enterprise Agreement, Microsoft Customer Agreement and Microsoft Partner Agreement (Azure plan) accounts.

For a subscription: open Cost Management, select the subscription scope, then Settings > Manage subscription. Under Tag inheritance, select Edit and enable Automatically apply subscription and resource group tags to new data. You can also decide whether a resource's own tag or the inherited tag wins when both exist. Usage records update within about 8 to 24 hours and the setting applies to the current month. Group by the tag in Cost Analysis to check results.

Verification

  • Creating a resource group without CostCenter fails with RequestDisallowedByPolicy.
  • A new storage account or VM in a tagged resource group shows both tags without the deployer supplying them.
  • az policy state summarize -a inherit-costcenter reports no non-compliant resources after remediation.
  • Remediation tasks show succeeded deployments, and Deployed Resources on the assignment lists what changed.
  • Cost Analysis grouped by CostCenter shows little or no untagged spend.

Troubleshooting

RequestDisallowedByPolicy ... was disallowed by policy. The message names the policy assignment and definition. Check them with az policy assignment show --name <assignment-name> and az policy definition show --name <definition-name>, then add the tag or request an exemption.

Remediation task fails on every resource. The assignment's managed identity lacks the role from roleDefinitionIds, typically because the assignment was created with the CLI or a template without --role. Grant the role at the assignment scope (in Access control (IAM), search for the last segment of the assignment ID) and rerun the task.

Compliance shows 0 of 0 resources. The scope or mode is wrong. Tag rules that target resources need Indexed mode, rules that target resource groups need All, and the resources must not be excluded or exempt.

Results look stale. New assignments take about five minutes to apply, new or updated resources appear after about 15 minutes, and the full scan runs every 24 hours. Use az policy state trigger-scan or Start-AzPolicyComplianceScan.

Inherited tag never appears on a resource. The resource group has no value for that tag, or the resource type doesn't support tags. Tag the resource group, then remediate again.

Custom definition deployed through an ARM template loses resourceGroup(). Resource Manager evaluated the function at deployment time. Escape it as [[resourceGroup().tags['CostCenter']] so Azure Policy receives the expression.

Checklist

  • Tag names and allowed values agreed, including case.
  • Deny assignments for CostCenter and Owner on resource groups, created in DoNotEnforce.
  • Existing resource groups tagged with az tag update --operation Merge.
  • Inherit assignments with a system-assigned identity and the Contributor role at the right scope.
  • Remediation tasks completed for each inherit assignment.
  • Deny assignments switched to Default with a helpful non-compliance message.
  • Platform-created scopes excluded before adding any resource-level deny.
  • Cost Management tag inheritance enabled for reporting.

For governance in a wider migration programme, see the enterprise Azure cloud migration playbook.

References

Questions people ask

Do Azure resources inherit tags from their resource group?

No. Resources don't inherit resource group or subscription tags on their own. To copy a tag down, assign the built-in policy Inherit a tag from the resource group if missing (or Inherit a tag from the resource group to overwrite), and run a remediation task for resources that already exist.

Why didn't my modify tag policy update existing resources?

The modify effect only changes resources when they are created or updated. During compliance scans it marks existing resources non-compliant instead. You must create a remediation task, which uses the assignment's managed identity to apply the tag operations to those resources.

Which role does the managed identity need for tag remediation?

The role listed in the definition's roleDefinitionIds. The built-in tag modify definitions list Contributor. The portal grants it automatically when it creates the identity; with the CLI, SDKs or templates you grant it yourself, for example with --role and --identity-scope on az policy assignment create.

Is Azure Cost Management tag inheritance the same as Azure Policy inheritance?

No. Cost Management tag inheritance copies billing, subscription and resource group tags onto usage records only, not onto the resources. It helps cost reports, while Azure Policy changes the actual resource tags. The two can be used together.

Azure PolicyAzure Resource ManagerAzure Cost ManagementManaged IdentityAzure CLI
  1. A practical Azure landing zone for small and mid-size companies

    Set up Azure management groups, subscriptions, hub-and-spoke networking, Azure Policy, RBAC and budgets the right way from day one, scaled down from Microsoft's landing zone architecture.

  2. AVD scaling plans: autoscale session hosts and Start VM on Connect

    Configure an Azure Virtual Desktop power management scaling plan, Start VM on Connect and disconnected-session limits so pooled session hosts are deallocated when nobody needs them.

  3. Azure Blueprints Retirement: Move to Template Specs and Deployment Stacks

    Azure Blueprints retires on January 31, 2027. Export your definitions, rebuild them in Bicep, publish template specs and recreate blueprint locks with deployment stack deny settings.