Azure Policy is powerful for enforcing standards across your environment. Built-in policies cover many scenarios, but custom policies let you enforce your specific requirements.
Policy Structure
Every policy has:
{
"mode": "All",
"policyRule": {
"if": {
// Condition
},
"then": {
"effect": "deny|audit|modify|deployIfNotExists"
}
},
"parameters": {}
}
Common Policy Effects
| Effect | When to Use |
|---|---|
| Deny | Block non-compliant resources |
| Audit | Log non-compliance without blocking |
| Modify | Fix resources during create/update |
| DeployIfNotExists | Add missing configuration |
| AuditIfNotExists | Audit when related resource missing |
Example: Require Storage Encryption
resource "azurerm_policy_definition" "storage_encryption" {
name = "require-storage-encryption"
policy_type = "Custom"
mode = "All"
display_name = "Require storage account encryption"
policy_rule = jsonencode({
if = {
allOf = [
{
field = "type"
equals = "Microsoft.Storage/storageAccounts"
},
{
field = "Microsoft.Storage/storageAccounts/encryption.services.blob.enabled"
notEquals = true
}
]
}
then = {
effect = "deny"
}
})
}
Example: Enforce Tags
resource "azurerm_policy_definition" "require_tags" {
name = "require-cost-tags"
policy_type = "Custom"
mode = "Indexed"
display_name = "Require cost allocation tags"
metadata = jsonencode({
category = "Tags"
})
parameters = jsonencode({
tagName = {
type = "String"
metadata = {
displayName = "Tag Name"
description = "Name of the tag to require"
}
}
})
policy_rule = jsonencode({
if = {
field = "[concat('tags[', parameters('tagName'), ']')]"
exists = false
}
then = {
effect = "deny"
}
})
}
Example: Inherit Tags from Resource Group
Use Modify to automatically add tags:
resource "azurerm_policy_definition" "inherit_tags" {
name = "inherit-rg-tags"
policy_type = "Custom"
mode = "Indexed"
display_name = "Inherit tags from resource group"
parameters = jsonencode({
tagName = {
type = "String"
metadata = {
displayName = "Tag Name"
description = "Tag to inherit from resource group"
}
}
})
policy_rule = jsonencode({
if = {
allOf = [
{
field = "[concat('tags[', parameters('tagName'), ']')]"
exists = false
},
{
field = "type"
notEquals = "Microsoft.Resources/subscriptions/resourceGroups"
}
]
}
then = {
effect = "modify"
details = {
roleDefinitionIds = [
"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
]
operations = [
{
operation = "addOrReplace"
field = "[concat('tags[', parameters('tagName'), ']')]"
value = "[resourceGroup().tags[parameters('tagName')]]"
}
]
}
}
})
}
Example: Deploy Diagnostic Settings
DeployIfNotExists for automatic configuration:
resource "azurerm_policy_definition" "deploy_diagnostics" {
name = "deploy-keyvault-diagnostics"
policy_type = "Custom"
mode = "All"
display_name = "Deploy diagnostic settings for Key Vault"
parameters = jsonencode({
logAnalyticsWorkspaceId = {
type = "String"
metadata = {
displayName = "Log Analytics Workspace ID"
}
}
})
policy_rule = jsonencode({
if = {
field = "type"
equals = "Microsoft.KeyVault/vaults"
}
then = {
effect = "deployIfNotExists"
details = {
type = "Microsoft.Insights/diagnosticSettings"
name = "setByPolicy"
existenceCondition = {
allOf = [
{
field = "Microsoft.Insights/diagnosticSettings/logs.enabled"
equals = true
},
{
field = "Microsoft.Insights/diagnosticSettings/workspaceId"
equals = "[parameters('logAnalyticsWorkspaceId')]"
}
]
}
roleDefinitionIds = [
"/providers/microsoft.authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa",
"/providers/microsoft.authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293"
]
deployment = {
properties = {
mode = "incremental"
template = {
"$schema" = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"
contentVersion = "1.0.0.0"
parameters = {
resourceName = { type = "string" }
workspaceId = { type = "string" }
}
resources = [
{
type = "Microsoft.KeyVault/vaults/providers/diagnosticSettings"
apiVersion = "2021-05-01-preview"
name = "[concat(parameters('resourceName'), '/Microsoft.Insights/setByPolicy')]"
properties = {
workspaceId = "[parameters('workspaceId')]"
logs = [
{
categoryGroup = "allLogs"
enabled = true
}
]
}
}
]
}
parameters = {
resourceName = { value = "[field('name')]" }
workspaceId = { value = "[parameters('logAnalyticsWorkspaceId')]" }
}
}
}
}
}
})
}
Policy Initiatives
Group related policies into initiatives:
resource "azurerm_policy_set_definition" "security_baseline" {
name = "security-baseline"
policy_type = "Custom"
display_name = "Security Baseline"
policy_definition_reference {
policy_definition_id = azurerm_policy_definition.storage_encryption.id
parameter_values = jsonencode({})
}
policy_definition_reference {
policy_definition_id = azurerm_policy_definition.require_tags.id
parameter_values = jsonencode({
tagName = { value = "CostCenter" }
})
}
}
Assignment and Remediation
resource "azurerm_subscription_policy_assignment" "security" {
name = "security-baseline"
subscription_id = data.azurerm_subscription.current.id
policy_definition_id = azurerm_policy_set_definition.security_baseline.id
identity {
type = "SystemAssigned"
}
location = "uksouth"
}
# Remediation task for existing resources
resource "azurerm_subscription_policy_remediation" "security" {
name = "remediate-security-baseline"
subscription_id = data.azurerm_subscription.current.id
policy_assignment_id = azurerm_subscription_policy_assignment.security.id
}
Testing Policies
Before deploying, test in audit mode:
# Check compliance
Get-AzPolicyState -PolicyAssignmentName "security-baseline" |
Where-Object { $_.ComplianceState -eq "NonCompliant" } |
Select-Object ResourceId, PolicyDefinitionName
Need help implementing governance in Azure? Get in touch - we help organisations build compliant cloud environments.