Cloud Security Wire
AWS Azure GCP RSS
Azure Hardening Guide medium

Azure Policy as a Security Guardrail: Preventing Misconfigurations Across Subscriptions

Azure Policy is one of the most underused security controls in Azure environments. Correctly deployed at management group scope, it prevents the misconfigurations that cause cloud breaches — public storage access, unencrypted disks, open network security groups, and resources deployed without logging. This guide covers the policy effects that matter, the built-in policies worth assigning today, and how to build custom guardrails for organisation-specific requirements.

By Cloud Security Wire · ·
#azure-policy#management-group#guardrails#misconfiguration#deny-assignment#Defender-for-Cloud#compliance#IaC-security#subscription-security#2026

Most Azure environments have RBAC. Many have Conditional Access. Fewer have Azure Policy configured to actually prevent misconfiguration — which means a developer with Contributor access can deploy a publicly accessible storage account, a VM without disk encryption, or a Key Vault with soft-delete disabled, and nothing stops them.

Azure Policy is the correct control for this. It operates at the resource layer — before or at the point of deployment — and can be applied at management group scope to cover every subscription in the tenancy. This guide covers what actually matters for security, skipping the compliance checkbox content.

Understanding Policy Effects

Azure Policy supports several effects. Only three are relevant for security:

Deny — prevents the resource from being created or updated if it doesn’t comply. This is the correct effect for hard security requirements: public blob access, unencrypted disks, open inbound RDP/SSH from the internet, resources deployed without logging.

DeployIfNotExists — deploys a related resource if it doesn’t exist. Use this for enabling diagnostics settings, deploying the Log Analytics agent, or enabling Defender for Cloud plans on new subscriptions. It’s a reactive correction, not prevention.

Audit — logs non-compliant resources but does nothing to stop them. Useful for discovery, but not a security control by itself. Don’t confuse audit with enforcement.

The instinct to start with Audit is understandable — you want to understand the blast radius before blocking anything. The correct approach: use Audit to assess existing resources, then switch to Deny for all new deployments while remediating existing non-compliance.

Scope: Assign at Management Group Level

Policies assigned at subscription scope only apply to that subscription. Policies assigned at management group scope apply to all subscriptions under that management group, including new subscriptions added later.

Apply security-critical policies at the root management group or the highest management group that encompasses all workload subscriptions:

# Assign a built-in policy at management group scope
az policy assignment create \
  --name "deny-public-blob-access" \
  --policy "7433c107-6db4-4ad1-b57a-a76dce0154a1" \
  --scope "/providers/Microsoft.Management/managementGroups/<mg-id>" \
  --enforcement-mode Default

Priority Built-In Policies to Assign Now

These are the built-in policies that prevent the most common breach vectors. Assign all of these at management group scope with Deny effect.

Storage Accounts

Deny public blob access (Policy ID: 7433c107-6db4-4ad1-b57a-a76dce0154a1) Prevents storage accounts from being created or updated with allowBlobPublicAccess: true. Public storage is one of the most common Azure breach paths.

Require secure transfer (TLS) (Policy ID: 404c3081-a854-4457-ae30-26a93ef643f9) Enforces supportsHttpsTrafficOnly: true. Prevents HTTP access to storage accounts.

Minimum TLS version 1.2 (Policy ID: fe83a0eb-a853-422d-aac2-1bffd182c5d0) Blocks creation of storage accounts permitting TLS 1.0 or 1.1.

Virtual Machines and Disks

Azure Disk Encryption required (Policy ID: 0961003e-5a0a-4549-abde-af6a37f2724d) Requires ADE or EncryptionAtHost on VM OS and data disks. Resources that don’t comply cannot be created.

Deny VMs without managed disks (Policy ID: 06a78e20-9358-41c9-923c-fb736d382a4d) Prevents attachment of unmanaged (page blob) disks that bypass disk encryption controls.

Network Security Groups

Deny inbound RDP from internet (Policy ID: e372f825-a257-4fb8-9175-797a8a8627d4) Blocks NSG rules permitting TCP 3389 from source * or Internet. One of the most common initial access vectors in Azure breach cases.

Deny inbound SSH from internet (Policy ID: 2c89a2e5-7285-40fe-afe5-1a62e89d2c17) Same as above for TCP 22.

Key Vault

Require soft-delete on Key Vault (Policy ID: 1e66c121-a66a-4b1f-9b83-0fd99bf0fc2d) Ensures key vaults cannot be permanently deleted without a recovery window. Required for data recovery after ransomware or accidental deletion.

Require purge protection (Policy ID: a8793640-60f7-487c-b5c3-1d37215905c4) Prevents hard deletion of keys, secrets, and certificates during the soft-delete retention period.

Diagnostic Logging

Deploy diagnostic settings for Activity Log to Log Analytics (DeployIfNotExists) Ensures the Azure Activity Log — which captures all control plane operations — is streamed to a Log Analytics workspace. Without this, management plane actions are invisible to your SIEM.

# Assign the Activity Log to Log Analytics policy initiative
az policy assignment create \
  --name "deploy-diag-activity-log" \
  --policy-set-definition "53d7269a-9d17-4b5e-9ba2-b35c7e6d05c2" \
  --scope "/providers/Microsoft.Management/managementGroups/<mg-id>" \
  --enforcement-mode Default \
  --params '{"logAnalyticsWorkspaceId": {"value": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.OperationalInsights/workspaces/<ws>"}}'

Writing Custom Deny Policies

Built-in policies cover common controls. Organisation-specific requirements need custom policies. The pattern is consistent: define the condition that identifies non-compliance, set the effect to Deny.

Example: Deny storage accounts not in approved regions

{
  "mode": "All",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Storage/storageAccounts"
        },
        {
          "field": "location",
          "notIn": ["uksouth", "ukwest", "northeurope", "westeurope"]
        }
      ]
    },
    "then": {
      "effect": "Deny"
    }
  }
}
# Create and assign custom policy
az policy definition create \
  --name "deny-storage-outside-approved-regions" \
  --display-name "Deny storage accounts outside approved regions" \
  --rules policy-rule.json \
  --mode All \
  --management-group <mg-id>

az policy assignment create \
  --name "deny-storage-regions" \
  --policy "/providers/Microsoft.Management/managementGroups/<mg-id>/providers/Microsoft.Authorization/policyDefinitions/deny-storage-outside-approved-regions" \
  --scope "/providers/Microsoft.Management/managementGroups/<mg-id>"

Example: Require specific tags on all resource groups

{
  "mode": "All",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.Resources/subscriptions/resourceGroups"
        },
        {
          "anyOf": [
            { "field": "tags['Environment']", "exists": "false" },
            { "field": "tags['Owner']", "exists": "false" },
            { "field": "tags['CostCentre']", "exists": "false" }
          ]
        }
      ]
    },
    "then": {
      "effect": "Deny"
    }
  }
}

Policy Initiatives (Definition Sets)

Group related policies into an initiative (policy set) for easier assignment and compliance reporting. The Microsoft Cloud Security Benchmark initiative (1f3afdf9-d0c9-4c3d-847f-89da613e70a8) provides a comprehensive baseline mapped to CIS and NIST. Assign it in Audit mode first to identify your compliance gap before switching relevant policies to Deny.

# Assess compliance against Microsoft Cloud Security Benchmark
az policy assignment create \
  --name "mcsb-audit" \
  --policy-set-definition "1f3afdf9-d0c9-4c3d-847f-89da613e70a8" \
  --scope "/providers/Microsoft.Management/managementGroups/<mg-id>" \
  --enforcement-mode DoNotEnforce

Exemptions and Exception Management

Policy exemptions are necessary for legitimate exceptions (a legacy storage account that cannot be encrypted immediately) but become a security gap if unmanaged. Best practices:

  • Set an expiry date on every exemption (--expires-on)
  • Require JIRA/ServiceNow ticket reference in the description field
  • Review all exemptions quarterly — Azure Policy provides no automatic expiry notification
az policy exemption create \
  --name "legacy-storage-exemption" \
  --policy-assignment "/subscriptions/<sub>/providers/Microsoft.Authorization/policyAssignments/deny-public-blob-access" \
  --scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<sa>" \
  --exemption-category Waiver \
  --expires-on "2026-11-01" \
  --description "Waiver JIRA-4521: Legacy SA undergoing migration, due Q4 2026"

Integration with Defender for Cloud

Defender for Cloud reads Azure Policy compliance data and surfaces non-compliant resources in its Secure Score. If you have Defender for Cloud enabled, assign policies through its Regulatory Compliance blade rather than directly — this keeps your compliance posture visible in a single view and ensures Defender’s recommendations and policy assignments stay aligned.

The quickest win: enable the Microsoft Cloud Security Benchmark as your default policy initiative in Defender for Cloud, assign the critical Deny-effect policies above, and set a target of 80+ Secure Score as a measurable signal of your baseline posture.

← All Analysis Subscribe via RSS