Skip to content

Azure Policy and Governance

First PublishedByAtif Alam

Azure Policy enforces organizational standards and compliance at scale — automatically preventing or auditing resources that don’t meet your rules. Combined with management groups, resource locks, and tagging, it forms Azure’s governance framework.

Without governance, cloud environments drift:

  • Developers create VMs without encryption.
  • Resources are created in unapproved regions.
  • Tags are missing, making cost attribution impossible.
  • Public endpoints are exposed accidentally.

Governance automates enforcement so you don’t rely on people remembering the rules.

Azure Policy evaluates resources against rules and either prevents non-compliant resources from being created or reports on existing non-compliance.

Resource creation/update
Azure Policy evaluates rules
├── Compliant ──► Allowed
└── Non-compliant
├── Deny ──► Blocked (resource not created)
├── Audit ──► Allowed but flagged in compliance dashboard
├── Modify ──► Automatically fix (e.g. add missing tag)
└── DeployIfNotExists ──► Deploy a resource to remediate

A policy definition is a JSON rule:

{
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{ "field": "type", "equals": "Microsoft.Compute/virtualMachines" },
{ "field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.managedDisk", "exists": "false" }
]
},
"then": {
"effect": "deny"
}
}
}

This denies creating VMs without managed disks.

EffectWhat It DoesUse Case
DenyBlock resource creation/updateHard enforcement (must comply)
AuditAllow but flag as non-compliantSoft enforcement (report only)
ModifyAuto-modify resource propertiesAdd missing tags, enable encryption
DeployIfNotExistsDeploy a related resource if missingAuto-enable diagnostics, install agents
AppendAdd fields to a resourceAdd required tags on creation
DisabledPolicy exists but doesn’t evaluateTemporarily disable a policy

Azure provides 700+ built-in policies:

PolicyEffectWhat It Enforces
Allowed locationsDenyResources can only be created in approved regions
Require tag on resourcesDenyEvery resource must have specific tags
Storage accounts should use private linkAuditFlag storage accounts without private endpoints
VMs should use managed disksDenyBlock VMs with unmanaged disks
SQL databases should have TDE enabledAuditFlag SQL DBs without encryption
Kubernetes clusters should not allow privileged containersDenyBlock privileged pods in AKS
Terminal window
# Assign a built-in policy: "Allowed locations"
az policy assignment create \
--name "allowed-locations" \
--display-name "Restrict to US and EU regions" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c" \
--params '{"listOfAllowedLocations": {"value": ["eastus", "eastus2", "westeurope", "northeurope"]}}' \
--scope "/subscriptions/<subscription-id>"
# Assign to a resource group (narrower scope)
az policy assignment create \
--name "require-env-tag" \
--display-name "Require Environment tag" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/871b6d14-10aa-478d-b466-ef6698cc4b60" \
--params '{"tagName": {"value": "Environment"}}' \
--scope "/subscriptions/<sub>/resourceGroups/myapp-prod-rg"

An initiative groups multiple policies together:

Terminal window
# Assign a built-in initiative: "Azure Security Benchmark"
az policy assignment create \
--name "security-benchmark" \
--display-name "Azure Security Benchmark" \
--policy-set-definition "/providers/Microsoft.Authorization/policySetDefinitions/1f3afdf9-d0c9-4c3d-847f-89da613e70a8" \
--scope "/subscriptions/<subscription-id>"

Common built-in initiatives:

  • Azure Security Benchmark — Comprehensive security controls.
  • CIS Microsoft Azure Foundations — CIS benchmark compliance.
  • ISO 27001 — Information security standard.
  • NIST SP 800-53 — US federal security controls.
{
"mode": "Indexed",
"policyRule": {
"if": {
"anyOf": [
{ "field": "tags['Environment']", "exists": "false" },
{ "field": "tags['Team']", "exists": "false" },
{ "field": "tags['CostCenter']", "exists": "false" }
]
},
"then": {
"effect": "deny"
}
}
}

For policies with Modify or DeployIfNotExists effects, remediation tasks fix existing non-compliant resources:

Terminal window
# Create a remediation task
az policy remediation create \
--name "add-missing-tags" \
--policy-assignment "require-env-tag" \
--resource-group myapp-rg

The Azure portal shows compliance status across all assignments:

Overall Compliance: 87%
Policy Compliant Non-compliant
Allowed locations 145 0
Require Environment tag 120 25
VMs must use managed disks 98 3
Storage must use private endpoint 45 12

Management groups organize subscriptions into a hierarchy for applying policies and RBAC at scale:

Root Management Group
├── Production MG
│ ├── Subscription: prod-app
│ └── Subscription: prod-data
├── Development MG
│ ├── Subscription: dev-app
│ └── Subscription: sandbox
└── Platform MG
└── Subscription: shared-services

Policies and RBAC assigned to a management group inherit down to all child subscriptions and resources.

Terminal window
# Create a management group
az account management-group create --name "Production" --display-name "Production"
# Move a subscription under it
az account management-group subscription add \
--name "Production" \
--subscription <subscription-id>
# Assign a policy to the management group
az policy assignment create \
--name "prod-allowed-locations" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/e56962a6-..." \
--scope "/providers/Microsoft.Management/managementGroups/Production" \
--params '{"listOfAllowedLocations": {"value": ["eastus", "westeurope"]}}'

Resource locks prevent accidental deletion or modification — even by Owners:

Lock LevelPrevents
CanNotDeleteDeletion (modification allowed)
ReadOnlyAll changes (read-only, no writes or deletes)
Terminal window
# Lock a production resource group
az lock create \
--name "protect-prod" \
--resource-group myapp-prod-rg \
--lock-type CanNotDelete \
--notes "Prevent accidental deletion of production resources"
# Lock a specific resource
az lock create \
--name "protect-db" \
--resource-group myapp-prod-rg \
--resource-type Microsoft.Sql/servers \
--resource-name my-sql-server \
--lock-type CanNotDelete

To delete a locked resource, you must remove the lock first — this is intentionally a deliberate action.

Tags are critical for cost management, governance, and automation:

TagPurposeExample Values
EnvironmentWhich environmentproduction, staging, development
TeamOwning teamplatform, backend, data
CostCenterBudget categoryengineering, marketing
ApplicationApplication nameweb-portal, api-service
ManagedByHow it was createdterraform, bicep, manual
  1. Azure Policy — Deny resources without required tags.
  2. Modify policy — Auto-add tags (e.g. inherit resource group tags).
  3. Terraformdefault_tags in the Azure provider.

Auto-inherit a tag from the resource group:

{
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{ "field": "tags['Environment']", "exists": "false" },
{ "value": "[resourceGroup().tags['Environment']]", "notEquals": "" }
]
},
"then": {
"effect": "modify",
"details": {
"roleDefinitionIds": ["/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"],
"operations": [{
"operation": "addOrReplace",
"field": "tags['Environment']",
"value": "[resourceGroup().tags['Environment']]"
}]
}
}
}
}

Blueprints package policies, RBAC assignments, ARM templates, and resource groups into a deployable definition — useful for setting up new subscriptions consistently.

Blueprint: "Production Subscription"
├── Policy: Allowed locations (US + EU)
├── Policy: Require tags
├── RBAC: Reader for audit team
├── Resource Group: networking-rg
│ └── ARM template: VNet + NSGs
└── Resource Group: monitoring-rg
└── ARM template: Log Analytics workspace

Note: Microsoft is deprecating Blueprints in favor of deployment stacks and template specs combined with policy. For new setups, use Azure Policy + Bicep/Terraform.

PracticeHow
Start with Audit, then DenyDeploy policies in Audit mode first; review compliance; switch to Deny
Use management groupsApply policies at the MG level for consistent enforcement across subscriptions
Enforce tags via policyDon’t rely on humans — automate tag requirements
Lock production resourcesCanNotDelete locks on databases, storage, and critical infrastructure
Use initiativesGroup related policies (e.g. “Security Baseline”) for easier management
Remediate existing resourcesRun remediation tasks for Modify/DeployIfNotExists policies
Review compliance regularlyDashboard → fix non-compliant resources → tighten policies
  • Azure Policy enforces rules on resource creation and configuration — Deny (block), Audit (report), Modify (auto-fix).
  • Initiatives group related policies (security baselines, compliance standards) for easier assignment.
  • Management groups organize subscriptions into a hierarchy for policy and RBAC inheritance.
  • Resource locks prevent accidental deletion of critical resources.
  • Tags are enforced via policy — require Environment, Team, and CostCenter on every resource.
  • Start with Audit mode, review compliance, then switch to Deny once you’re confident.