The compliance report arrives on a Friday afternoon. You scan through it and stop on a finding: a Storage Account with public network access enabled, sitting in your production subscription, deployed three weeks ago. Someone bypassed the documented standard, the ARM deployment succeeded, and nobody noticed until now.
You had the standard documented. You didn’t have it enforced.
That gap—between what your security baseline says and what actually exists in Azure—is configuration drift. It accumulates quietly, one manual deployment at a time, until an audit surfaces it. Azure Policy closes that gap by turning your standards from documentation into enforcement logic that ARM evaluates on every resource creation and modification.
Azure Policy scales flat: assign policies at the Management Group scope and they cover every subscription underneath, today and three years from now. This article covers how to author and deploy policies using Policy as Code patterns in Terraform and Bicep, moving from audit mode into proactive enforcement and automated remediation.
1. The Policy Assignment Hierarchy
Group related controls into Initiatives (also called Policy Sets) rather than managing individual policies. A single “Security Baseline” initiative can bundle 20 policies into one assignment—much easier to manage across a hierarchy than 20 separate assignments you have to keep synchronized.
Policy Assignment Hierarchy
2. Proactive Enforcement with the Deny Effect
The most powerful tool in your governance arsenal is the Deny effect. It evaluates a resource request before it is created. If the resource doesn’t meet the requirement (e.g., it lacks a mandatory tag), ARM rejects the request with a 403 Forbidden.
{
"if": {
"field": "Microsoft.Storage/storageAccounts/publicNetworkAccess",
"ne": "Disabled"
},
"then": {
"effect": "Deny"
}
}
Pro Tip: Always set the mode property in your policy definition. Use Indexed for policies that evaluate tags and locations. Use All for resource-specific properties like network rules or subnets.
3. Self-Healing with DeployIfNotExists (DINE)
Auditing tells you what’s broken; DeployIfNotExists (DINE) fixes it automatically. This effect is used for auxiliary resources like diagnostic settings or private DNS zones. If the primary resource (e.g., a VNet) exists but the dependent resource (e.g., diagnostic settings) is missing, Azure Policy triggers a remediation task to deploy it.
DINE Policy Remediation Workflow
Terraform Implementation: To support DINE, the policy assignment requires a Managed Identity and an RBAC role assignment allowing that identity to deploy resources.
resource "azurerm_management_group_policy_assignment" "logging" {
name = "deploy-diag-settings"
management_group_id = var.platform_mg_id
policy_definition_id = azurerm_policy_definition.dine_logging.id
location = "eastus"
identity {
type = "SystemAssigned"
}
}
resource "azurerm_role_assignment" "remediation_access" {
scope = var.platform_mg_id
role_definition_name = "Contributor"
principal_id = azurerm_management_group_policy_assignment.logging.identity[0].principal_id
}
4. Policy as Code: Bicep and loadJsonContent()
Inline JSON policy rules inside a Bicep file get unreadable fast. The loadJsonContent() function solves this cleanly—keep the policy rule logic in standalone JSON files and let Bicep pull them in at compile time.
resource secureStoragePolicy 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
name: 'deny-public-storage'
properties: {
policyRule: loadJsonContent('policies/storage/deny-public-access.json')
parameters: loadJsonContent('policies/storage/parameters.json')
}
}
5. Managing Exceptions with Exemptions
Some applications can’t meet every policy on day one—legacy dependencies, migration windows, vendor constraints. Exemptions let you grant a scoped, time-bounded waiver for a specific resource or subscription without touching the global standard. Unlike Exclusions (which are permanent and leave no audit trail), Exemptions carry an expiration date and a categorized reason (Waiver or Mitigated). That paper trail is what your compliance reviewer actually wants to see.
Key Takeaways
- Initiatives over Policies: Group your controls to simplify assignment management at scale.
- Deny is Prevention: Use
Denyfor security boundaries (Public IPs, Open RDP) to stop risks before they exist. - DINE is Maintenance: Use
DeployIfNotExistsfor standard platform boilerplate like logging and backup. - Remediate First: Never switch an existing policy to
Denywithout running a remediation task to fix existing resources first.
Next Steps:
- Read Subscription Vending: Automating New Workload Onboarding with IaC to see how these policies are automatically applied to new subscriptions during the vending process.
- Read Cost Governance in the Landing Zone: Tagging Enforcement, Budgets, and FinOps Automation to learn how to use Azure OpenAI to draft complex custom policy definitions in seconds.
