https://learn.microsoft.com/en-us/azure/governance/policy/
Azure Policy is a governance service that enables you to create, assign, and manage policies that control or audit your Azure resources. It acts as a guardrail system that ensures your cloud environment stays compliant with organizational standards, regulatory requirements, and best practices.
When you use Azure Policy, the system:
Azure Policy ensures governance at scale by enforcing rules across your entire Azure environment, from individual resources to organization-wide deployments.
Maintain adherence to corporate standards, industry regulations (HIPAA, PCI-DSS, SOC 2), and security requirements without manual oversight.
Stop resources from being deployed or modified in ways that violate organizational policies, catching issues before they become problems.
Enforce policies that prevent expensive resource types, oversized VMs, or deployments in high-cost regions, directly impacting your cloud spending.
Automatically ensure security best practices like encryption, network restrictions, and proper authentication across all resources.
Track which resources comply with policies and which don't, providing evidence for audits and compliance reviews.
Automatically fix non-compliant resources without manual intervention, reducing operational overhead and human error.
Azure Policy evaluates resources based on rules you define, then takes action based on the policy effect:
Policy Evaluation Flow:
Resource Creation/Modification Request
↓
Azure Policy Evaluation
↓
├─ Compliant → Allow operation
└─ Non-Compliant → Apply Effect
├─ Deny: Block the operation
├─ Audit: Log but allow
├─ DeployIfNotExists: Remediate automatically
├─ Modify: Change configuration
└─ Disabled: No action
Example - VM Size Restriction:
Policy Rule:
"Only allow Standard_D2s_v3 or smaller VMs in production"
User attempts to create Standard_D16s_v3 VM
↓
Azure Policy evaluates
↓
VM size violates policy
↓
Effect: Deny
↓
VM creation blocked with error message
Important: Policies are evaluated in real-time during resource operations and periodically (every 24 hours) for existing resources.
A policy definition is a JSON document that describes the conditions to evaluate and the action to take.
Structure:
{
"properties": {
"displayName": "Allowed virtual machine size SKUs",
"description": "This policy restricts VM sizes to approved SKUs",
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Compute/virtualMachines"
},
{
"not": {
"field": "Microsoft.Compute/virtualMachines/sku.name",
"in": [
"Standard_B2s",
"Standard_D2s_v3",
"Standard_D4s_v3"
]
}
}
]
},
"then": {
"effect": "deny"
}
}
}
}
Azure Policy supports multiple effects that determine what happens when a policy rule matches:
1. Deny
Example: Prevent VMs without encryption
2. Audit
Example: Log when storage accounts don't use HTTPS
3. AuditIfNotExists
Example: Audit VMs without backup enabled
4. DeployIfNotExists
Example: Automatically enable diagnostic settings
5. Modify
Example: Automatically add cost center tags
6. Disabled
Azure Policy can be assigned at multiple levels, with inheritance flowing downward:
Management Group (Organization-wide)
├─ Subscription 1
│ ├─ Resource Group A
│ │ ├─ Resource 1
│ │ └─ Resource 2
│ └─ Resource Group B
│ └─ Resource 3
└─ Subscription 2
└─ Resource Group C
└─ Resource 4
Inheritance Rules:
Example:
Management Group: "Require tags on all resources"
↓ (inherited)
Subscription: "Allowed regions: East US, West US"
↓ (inherited)
Resource Group: "Require encryption on storage accounts"
↓ (all three policies apply)
Resources: Must comply with all three policies
Azure provides hundreds of built-in policy definitions across all service categories.
Compute:
Storage:
Networking:
Security:
Tags:
Cost Management:
An initiative (also called a policy set) is a collection of policy definitions grouped together to achieve a specific compliance goal.
Single initiatives can:
This initiative contains 100+ policy definitions including:
SQL Security Policies:
VM Security Policies:
Network Security Policies:
Storage Security Policies:
1. PCI DSS 3.2.1
2. ISO 27001:2013
3. Azure Security Benchmark
4. HIPAA/HITRUST
Assignment Components:
Policy Assignment
├─ Policy Definition (what to enforce)
├─ Scope (where to enforce)
├─ Parameters (customization)
├─ Enforcement Mode (enabled/disabled)
├─ Remediation (fix existing resources)
└─ Exclusions (specific resources to skip)
1. Via Azure Portal:
Azure Policy → Definitions → Select Policy → Assign
├─ Basics: Name, description, scope
├─ Parameters: Configure policy parameters
├─ Remediation: Create remediation tasks
├─ Non-compliance messages: Custom messages
└─ Review + Create
2. Via Azure CLI:
# Assign a policy to a resource group
az policy assignment create \
--name 'audit-vm-managed-disks' \
--display-name 'Audit VMs without managed disks' \
--scope '/subscriptions/{subscription-id}/resourceGroups/{rg-name}' \
--policy '/providers/Microsoft.Authorization/policyDefinitions/{policy-id}'
# Assign an initiative
az policy set-definition assignment create \
--name 'pci-dss-3.2.1' \
--display-name 'PCI DSS 3.2.1 Compliance' \
--policy-set-definition '/providers/Microsoft.Authorization/policySetDefinitions/496eeda9-8f2f-4d5e-8dfd-204f0a92ed41' \
--scope '/subscriptions/{subscription-id}'
3. Via Azure PowerShell:
# Assign policy to subscription
New-AzPolicyAssignment `
-Name 'require-tag-on-rg' `
-DisplayName 'Require CostCenter tag on resource groups' `
-Scope '/subscriptions/{subscription-id}' `
-PolicyDefinition $definition `
-PolicyParameter @{'tagName'='CostCenter'}
# Assign initiative with remediation
New-AzPolicyAssignment `
-Name 'enable-monitoring' `
-PolicySetDefinition $initiative `
-Scope $scope `
-Location 'eastus' `
-IdentityType 'SystemAssigned'
4. Via ARM Template:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.Authorization/policyAssignments",
"apiVersion": "2021-06-01",
"name": "deny-public-ip",
"properties": {
"displayName": "Deny creation of public IPs",
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/83a86a26-fd1f-447c-b59d-e51f44264114",
"scope": "[subscription().id]",
"enforcementMode": "Default"
}
}
]
}
Default (Enabled):
DoNotEnforce (Disabled):
Example:
# Deploy policy in DoNotEnforce mode for testing
az policy assignment create \
--name 'test-vm-policy' \
--policy '/providers/Microsoft.Authorization/policyDefinitions/{id}' \
--scope $scope \
--enforcement-mode 'DoNotEnforce'
# After testing, enable enforcement
az policy assignment update \
--name 'test-vm-policy' \
--enforcement-mode 'Default'
Azure Policy can automatically fix non-compliant resources using remediation tasks.
For Existing Resources:
Non-compliant resource detected
↓
Remediation task created
↓
Managed identity granted permissions
↓
DeployIfNotExists or Modify policy executes
↓
Resource becomes compliant
Policy: "Deploy diagnostic settings for Azure SQL to Log Analytics"
Before Remediation:
After Remediation Task:
# Create remediation task
az policy remediation create \
--name 'remediate-sql-diagnostics' \
--policy-assignment '/subscriptions/{sub-id}/providers/Microsoft.Authorization/policyAssignments/sql-diagnostics' \
--resource-group 'production-databases'
# Check remediation progress
az policy remediation show \
--name 'remediate-sql-diagnostics' \
--resource-group 'production-databases'
Result:
Policies that modify resources require a managed identity:
{
"identity": {
"type": "SystemAssigned"
},
"location": "eastus",
"properties": {
"displayName": "Configure diagnostic settings",
"policyDefinitionId": "{policy-id}",
"parameters": {
"logAnalytics": {
"value": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.OperationalInsights/workspaces/{workspace}"
}
}
}
}
Required Steps:
Parameters make policies reusable by allowing customization at assignment time.
Policy Definition with Parameters:
{
"properties": {
"displayName": "Allowed locations",
"parameters": {
"allowedLocations": {
"type": "Array",
"metadata": {
"description": "The list of allowed locations for resources",
"displayName": "Allowed locations",
"strongType": "location"
}
}
},
"policyRule": {
"if": {
"not": {
"field": "location",
"in": "[parameters('allowedLocations')]"
}
},
"then": {
"effect": "deny"
}
}
}
}
PowerShell:
$locations = @('eastus', 'westus')
$params = @{'allowedLocations' = $locations}
New-AzPolicyAssignment `
-Name 'restrict-locations' `
-PolicyDefinition $definition `
-Scope $scope `
-PolicyParameter $params
Azure CLI:
az policy assignment create \
--name 'restrict-locations' \
--policy '{policy-id}' \
--params '{
"allowedLocations": {
"value": ["eastus", "westus"]
}
}'
Azure Portal:
Azure Policy → Compliance
├─ Overall Compliance Score: 78%
├─ Non-compliant Resources: 145
├─ Non-compliant Policies: 12
└─ Filter by:
├─ Subscription
├─ Resource Group
├─ Resource Type
└─ Policy Assignment
Compliance Details:
Azure Resource Graph:
// Find all non-compliant resources
PolicyResources
| where type == "microsoft.policyinsights/policystates"
| where properties.complianceState == "NonCompliant"
| project
resourceId = properties.resourceId,
policyName = properties.policyDefinitionName,
reason = properties.complianceReasonCode
// Count non-compliant resources by type
PolicyResources
| where type == "microsoft.policyinsights/policystates"
| where properties.complianceState == "NonCompliant"
| summarize count() by resourceType = tostring(properties.resourceType)
| order by count_ desc
Azure CLI:
# Get compliance summary
az policy state summarize \
--resource-group 'production'
# List non-compliant resources
az policy state list \
--filter "ComplianceState eq 'NonCompliant'" \
--query "[].{Resource:resourceId, Policy:policyDefinitionName}"
Generate Compliance Report:
# Export compliance data
Get-AzPolicyState `
-Filter "ComplianceState eq 'NonCompliant'" |
Export-Csv -Path 'non-compliant-resources.csv'
# Get compliance by policy
Get-AzPolicyStateSummary -Top 10 |
Select-Object PolicyAssignmentName,
@{N='NonCompliant';E={$_.Results.NonCompliantResources}},
@{N='Compliant';E={$_.Results.CompliantResources}}
Requirement: All resources must have CostCenter and Environment tags
Solution:
{
"if": {
"anyOf": [
{
"field": "tags['CostCenter']",
"exists": "false"
},
{
"field": "tags['Environment']",
"exists": "false"
}
]
},
"then": {
"effect": "deny"
}
}
Alternative with Auto-remediation:
{
"if": {
"field": "tags['CostCenter']",
"exists": "false"
},
"then": {
"effect": "modify",
"details": {
"roleDefinitionIds": [
"/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
],
"operations": [
{
"operation": "add",
"field": "tags['CostCenter']",
"value": "[parameters('costCenterValue')]"
}
]
}
}
}
Requirement: Limit VMs to cost-effective SKUs
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Compute/virtualMachines"
},
{
"not": {
"field": "Microsoft.Compute/virtualMachines/sku.name",
"in": [
"Standard_B2s",
"Standard_B2ms",
"Standard_D2s_v3",
"Standard_D4s_v3"
]
}
}
]
},
"then": {
"effect": "deny"
}
}
Requirement: All storage accounts must use encryption
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"not": {
"field": "Microsoft.Storage/storageAccounts/encryption.services.blob.enabled",
"equals": "true"
}
}
]
},
"then": {
"effect": "deny"
}
}
Requirement: Resources only in approved regions
{
"if": {
"not": {
"field": "location",
"in": "[parameters('allowedLocations')]"
}
},
"then": {
"effect": "deny"
}
}
Assignment:
az policy assignment create \
--name 'restrict-to-us-regions' \
--policy '{policy-id}' \
--params '{
"allowedLocations": {
"value": ["eastus", "eastus2", "westus", "westus2", "centralus"]
}
}'
Requirement: All SQL servers must have auditing enabled
{
"if": {
"field": "type",
"equals": "Microsoft.Sql/servers"
},
"then": {
"effect": "DeployIfNotExists",
"details": {
"type": "Microsoft.Sql/servers/auditingSettings",
"name": "default",
"roleDefinitionIds": [
"/providers/microsoft.authorization/roleDefinitions/056cd41c-7e88-42e1-933e-88ba6a50c9c3"
],
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"serverName": {
"type": "string"
},
"storageAccountName": {
"type": "string"
}
},
"resources": [
{
"type": "Microsoft.Sql/servers/auditingSettings",
"apiVersion": "2021-02-01-preview",
"name": "[concat(parameters('serverName'), '/default')]",
"properties": {
"state": "Enabled",
"storageEndpoint": "[concat('https://', parameters('storageAccountName'), '.blob.core.windows.net')]",
"retentionDays": 90,
"auditActionsAndGroups": [
"SUCCESSFUL_DATABASE_AUTHENTICATION_GROUP",
"FAILED_DATABASE_AUTHENTICATION_GROUP",
"BATCH_COMPLETED_GROUP"
]
}
}
]
}
}
}
}
}
}
Sometimes specific resources need to be excluded from policies.
Valid Exemption Reasons:
Example:
# Create exemption for a specific VM
az policy exemption create \
--name 'legacy-vm-exemption' \
--display-name 'Legacy VM Exemption' \
--policy-assignment '/subscriptions/{sub}/providers/Microsoft.Authorization/policyAssignments/require-encryption' \
--exemption-category 'Waiver' \
--expires-on '2026-12-31' \
--description 'Legacy VM scheduled for decommission in 2026' \
--scope '/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{vm}'
Best Practices:
Azure Policy integrates with CI/CD pipelines for policy-as-code.
Azure Pipeline Task:
- task: AzureCLI@2
displayName: 'Validate ARM Template Against Policies'
inputs:
azureSubscription: 'Production'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Export policies
az policy assignment list --output json > policies.json
# Validate template
az deployment group what-if \
--resource-group $(ResourceGroup) \
--template-file $(TemplateFile) \
--parameters $(ParametersFile)
# Check for policy violations
if [ $? -ne 0 ]; then
echo "Template would violate policies"
exit 1
fi
- task: AzurePowerShell@5
displayName: 'Check Policy Compliance'
inputs:
azureSubscription: 'Production'
ScriptType: 'InlineScript'
Inline: |
Start-Sleep -Seconds 60 # Wait for policy evaluation
$nonCompliant = Get-AzPolicyState `
-ResourceGroupName $(ResourceGroup) `
-Filter "ComplianceState eq 'NonCompliant'"
if ($nonCompliant.Count -gt 0) {
Write-Error "Deployment resulted in non-compliant resources"
$nonCompliant | Format-Table
exit 1
}
Phase 1: Audit (30 days)
└─ Identify non-compliant resources
└─ Understand impact
Phase 2: Test Enforcement (30 days)
└─ Use DoNotEnforce mode
└─ Validate with teams
Phase 3: Full Enforcement
└─ Enable Default mode
└─ Monitor compliance
❌ Don't: Assign 50 individual policies
✅ Do: Create initiative with 50 policies, assign once
Before creating custom policies:
1. Search built-in definitions
2. Check community samples
3. Modify existing policies if needed
4. Create custom only if necessary
Management Group (Broad policies)
├─ All resources must have tags
├─ All resources must use approved regions
└─ All resources must enable diagnostics
Subscription (Environment-specific)
├─ Production: High security, no public IPs
└─ Development: Relaxed, allow public IPs
Resource Group (Workload-specific)
└─ Database RG: Encryption required, backups enabled
# Test in non-production first
az policy assignment create \
--name 'test-policy' \
--scope '/subscriptions/{dev-sub-id}' \
--policy '{policy-id}' \
--enforcement-mode 'DoNotEnforce'
# Monitor for 1-2 weeks
az policy state list \
--filter "PolicyAssignmentName eq 'test-policy'"
# Enable enforcement after validation
az policy assignment update \
--name 'test-policy' \
--enforcement-mode 'Default'
Policy Documentation Template:
├─ Purpose: Why this policy exists
├─ Scope: Where it applies
├─ Effect: What happens on violation
├─ Exceptions: Who can get exemptions
├─ Remediation: How to fix violations
└─ Contact: Policy owner
Weekly:
└─ Review new non-compliant resources
Monthly:
└─ Review overall compliance trends
└─ Update exemptions
Quarterly:
└─ Review and update policy definitions
└─ Audit exemptions
└─ Train teams on new policies
Annually:
└─ Complete policy portfolio review
└─ Align with regulatory changes
└─ Optimize policy assignments
Symptoms: Resources created but not evaluated
Solutions:
# Check policy assignment
az policy assignment show --name '{policy-name}'
# Verify scope is correct
# Check enforcement mode (should be "Default")
# Manually trigger evaluation
az policy state trigger-scan \
--resource-group '{resource-group}'
Symptoms: Remediation task shows failures
Solutions:
# Check managed identity permissions
Get-AzRoleAssignment -ObjectId '{identity-object-id}'
# Grant required roles
New-AzRoleAssignment `
-ObjectId '{identity-object-id}' `
-RoleDefinitionName 'Contributor' `
-Scope '{scope}'
# Retry remediation
Start-AzPolicyRemediation -Name '{task-name}'
Symptoms: Valid resources being denied
Solutions:
# Create temporary exemption
az policy exemption create \
--name 'temp-exemption' \
--policy-assignment '{assignment}' \
--exemption-category 'Waiver' \
--expires-on '{date}' \
--scope '{resource-scope}'
Repository Structure:
policies/
├─ definitions/
│ ├─ compute/
│ │ ├─ allowed-vm-sizes.json
│ │ └─ require-managed-disks.json
│ ├─ storage/
│ │ └─ require-encryption.json
│ └─ networking/
│ └─ deny-public-ip.json
├─ initiatives/
│ ├─ security-baseline.json
│ └─ cost-optimization.json
└─ assignments/
├─ production.json
└─ development.json
trigger:
paths:
include:
- policies/*
pool:
vmImage: 'ubuntu-latest'
steps:
- task: AzureCLI@2
displayName: 'Deploy Policy Definitions'
inputs:
azureSubscription: 'Management'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Deploy definitions
for file in policies/definitions/**/*.json; do
az policy definition create \
--name $(basename $file .json) \
--rules $file \
--mode Indexed
done
# Deploy initiatives
for file in policies/initiatives/*.json; do
az policy set-definition create \
--name $(basename $file .json) \
--definitions $file
done
# Create assignments
for file in policies/assignments/*.json; do
az policy assignment create \
--name $(basename $file .json) \
--policy-set-definition $(cat $file | jq -r '.policySetDefinitionId') \
--scope $(cat $file | jq -r '.scope')
done
# List all policy definitions
az policy definition list
# Show specific policy
az policy definition show --name '{policy-name}'
# Create custom policy
az policy definition create \
--name '{policy-name}' \
--rules '{policy-rules.json}' \
--params '{policy-params.json}'
# List policy assignments
az policy assignment list
# Check compliance
az policy state list --filter "ComplianceState eq 'NonCompliant'"
# Trigger policy scan
az policy state trigger-scan --resource-group '{rg-name}'
# Create remediation task
az policy remediation create \
--name '{task-name}' \
--policy-assignment '{assignment-id}'
# Delete policy assignment
az policy assignment delete --name '{assignment-name}'
# Get policy definitions
Get-AzPolicyDefinition
# Create policy assignment
New-AzPolicyAssignment -Name '{name}' -PolicyDefinition $def -Scope '{scope}'
# Get compliance state
Get-AzPolicyState -Filter "ComplianceState eq 'NonCompliant'"
# Start remediation
Start-AzPolicyRemediation -Name '{task-name}' -PolicyAssignmentId '{id}'
# Get compliance summary
Get-AzPolicyStateSummary -ManagementGroupName '{mg-name}'
# Create policy exemption
New-AzPolicyExemption -Name '{name}' -PolicyAssignment $assignment -ExemptionCategory 'Waiver'
| Effect | Description | Use Case |
|---|---|---|
| Deny | Blocks resource creation/update | Prevent non-compliant resources |
| Audit | Logs non-compliance, allows operation | Visibility without blocking |
| AuditIfNotExists | Audits if related resource missing | Check for required configurations |
| DeployIfNotExists | Auto-deploys missing resources | Automatic remediation |
| Modify | Changes resource properties | Auto-tagging, configuration fixes |
| Disabled | Policy not evaluated | Testing, temporary deactivation |
Azure Policy is an essential governance tool that ensures your Azure environment remains compliant, secure, and cost-effective. By defining policies at appropriate scopes, leveraging built-in definitions, and implementing automatic remediation, you can maintain control over thousands of resources without manual oversight.
Key Takeaways:
Remember: Azure Policy is not about blocking developers—it's about providing guardrails that enable teams to move fast while staying compliant and secure.