Back to Blog
Azure
4 min read

Custom Azure Policies for Compliance Enforcement

AzurePolicyComplianceGovernanceSecurity

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

EffectWhen to Use
DenyBlock non-compliant resources
AuditLog non-compliance without blocking
ModifyFix resources during create/update
DeployIfNotExistsAdd missing configuration
AuditIfNotExistsAudit 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.

Need help with your Azure environment?

Get in touch for a free consultation.

Get in Touch