Back to Blog
3 min read

Azure Policy as Code: Governance at Scale

Azure Policy enables you to enforce organizational standards and assess compliance at scale. Managing policies as code brings version control, testing, and automation to your governance strategy.

Policy Structure

A policy definition consists of rules that evaluate resources:

{
  "properties": {
    "displayName": "Require HTTPS for Storage Accounts",
    "description": "Ensures storage accounts only accept HTTPS traffic",
    "mode": "Indexed",
    "metadata": {
      "category": "Storage",
      "version": "1.0.0"
    },
    "parameters": {},
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "type",
            "equals": "Microsoft.Storage/storageAccounts"
          },
          {
            "field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
            "notEquals": true
          }
        ]
      },
      "then": {
        "effect": "deny"
      }
    }
  }
}

Deploying Policies with Bicep

// policies/require-https-storage.bicep
targetScope = 'subscription'

resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'require-https-storage'
  properties: {
    displayName: 'Require HTTPS for Storage Accounts'
    description: 'Storage accounts must only accept HTTPS traffic'
    policyType: 'Custom'
    mode: 'Indexed'
    metadata: {
      category: 'Storage'
      version: '1.0.0'
    }
    parameters: {}
    policyRule: {
      if: {
        allOf: [
          {
            field: 'type'
            equals: 'Microsoft.Storage/storageAccounts'
          }
          {
            field: 'Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly'
            notEquals: true
          }
        ]
      }
      then: {
        effect: 'deny'
      }
    }
  }
}

resource policyAssignment 'Microsoft.Authorization/policyAssignments@2021-06-01' = {
  name: 'require-https-storage-assignment'
  properties: {
    displayName: 'Require HTTPS for Storage'
    policyDefinitionId: policyDefinition.id
    enforcementMode: 'Default'
  }
}

Policy Initiatives (Policy Sets)

Group related policies together:

// policies/security-initiative.bicep
targetScope = 'subscription'

resource initiative 'Microsoft.Authorization/policySetDefinitions@2021-06-01' = {
  name: 'security-baseline'
  properties: {
    displayName: 'Security Baseline'
    description: 'Core security policies for all resources'
    policyType: 'Custom'
    metadata: {
      category: 'Security'
      version: '1.0.0'
    }
    parameters: {
      allowedLocations: {
        type: 'Array'
        metadata: {
          displayName: 'Allowed Locations'
          description: 'The list of allowed locations for resources'
        }
        defaultValue: [
          'australiaeast'
          'australiasoutheast'
        ]
      }
    }
    policyDefinitions: [
      {
        policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e765b5de-1225-4ba3-bd56-1ac6695af988'  // Built-in: Allowed locations
        parameters: {
          listOfAllowedLocations: {
            value: '[parameters(\'allowedLocations\')]'
          }
        }
      }
      {
        policyDefinitionReferenceId: 'requireHttpsStorage'
        policyDefinitionId: policyDefinition.id
      }
      {
        policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9'  // Built-in: Secure transfer for storage
      }
    ]
  }
}

Custom Policy for Tags

{
  "properties": {
    "displayName": "Require specific tags on resources",
    "mode": "Indexed",
    "parameters": {
      "requiredTags": {
        "type": "Array",
        "metadata": {
          "displayName": "Required Tags",
          "description": "List of required tag names"
        },
        "defaultValue": ["environment", "costCenter", "owner"]
      }
    },
    "policyRule": {
      "if": {
        "anyOf": [
          {
            "count": {
              "value": "[parameters('requiredTags')]",
              "name": "tagName",
              "where": {
                "field": "[concat('tags[', current('tagName'), ']')]",
                "exists": false
              }
            },
            "greater": 0
          }
        ]
      },
      "then": {
        "effect": "deny"
      }
    }
  }
}

Testing Policies

# Test policy compliance
$testCases = @(
    @{
        Name = 'Compliant Storage Account'
        Resource = @{
            type = 'Microsoft.Storage/storageAccounts'
            properties = @{
                supportsHttpsTrafficOnly = $true
            }
        }
        Expected = 'Compliant'
    }
    @{
        Name = 'Non-Compliant Storage Account'
        Resource = @{
            type = 'Microsoft.Storage/storageAccounts'
            properties = @{
                supportsHttpsTrafficOnly = $false
            }
        }
        Expected = 'NonCompliant'
    }
)

foreach ($test in $testCases) {
    $result = Test-AzPolicyEvaluation `
        -PolicyDefinition $policyDefinition `
        -Resource $test.Resource

    if ($result.ComplianceState -eq $test.Expected) {
        Write-Host "PASS: $($test.Name)" -ForegroundColor Green
    } else {
        Write-Host "FAIL: $($test.Name)" -ForegroundColor Red
    }
}

CI/CD for Policy Deployment

# .github/workflows/policy-deployment.yml
name: Deploy Policies

on:
  push:
    branches: [main]
    paths:
      - 'policies/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Deploy Policy Definitions
        run: |
          for policy in policies/definitions/*.json; do
            name=$(basename "$policy" .json)
            az policy definition create \
              --name "$name" \
              --rules "$policy" \
              --subscription ${{ secrets.SUBSCRIPTION_ID }}
          done

      - name: Deploy Policy Assignments
        run: |
          az deployment sub create \
            --location australiaeast \
            --template-file policies/assignments.bicep

Azure Policy as Code ensures consistent, auditable governance across your entire Azure estate.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.