Creating and Assigning Custom Azure Policies
Understanding what a policy definition and assignment are conceptually is one thing; writing and deploying one is another. This walks through a small, realistic example — denying (or auditing) storage accounts that don't enforce HTTPS — first through the portal, then through PowerShell and Cloud Shell.
Writing the policy definition
A custom policy definition is a JSON document with a display name, a description, and a policyRule — the condition to check and the effect to apply. This one denies (or audits, depending on a parameter) storage accounts that don't have supportsHttpsTrafficOnly set to true:
{
"properties": {
"displayName": "Deny unencrypted storage accounts",
"description": "This policy denies the creation of storage accounts without HTTPS traffic enabled.",
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"notEquals": true
}
]
},
"then": {
"effect": "[parameters('effectType')]"
}
},
"parameters": {
"effectType": {
"type": "String",
"metadata": {
"displayName": "Effect Type",
"description": "Determine whether to deny or audit non-compliant resources."
},
"defaultValue": "Deny"
}
}
}
}
The policyRule checks two things together (allOf): that the resource type is Microsoft.Storage/storageAccounts, and that supportsHttpsTrafficOnly isn't true. If both hold, the effect defined by the effectType parameter applies — defaulting to Deny, but switchable to Audit at assignment time without editing the definition itself. Parameterising the effect like this is what makes a single definition usable in both "just tell me" (audit) and "actually stop it" (deny) modes.
Assigning the policy through the portal
- Create the policy definition. In the Azure Policy service, go to Definitions and create a new definition from the JSON above.
- Assign the policy. Assign the new definition to a resource group — a production resource group, say, where you actually want HTTPS enforced.
- Configure the parameters. During assignment, set
effectTypetoDenyto block non-compliant storage accounts outright, orAuditto log them without blocking anything.
Common issues
- Policy not applying. Check that the assignment is scoped to the resources you're actually testing against, and that the assignment's parameters are set the way you expect.
- Incorrect property name.
supportsHttpsTrafficOnlyneeds to match the storage account's actual ARM property name exactly — a typo here means the condition silently never matches. - JSON syntax errors. Run the definition through a JSON validator before creating it; malformed JSON fails at creation, not evaluation.
- Propagation delay. Policy assignments aren't instantaneous — allow time for the assignment to propagate and evaluate against existing and new resources, and refresh the compliance view rather than assuming a stale result is final.
- Scope issues. If the policy needs to apply across multiple subscriptions, the assignment needs to be scoped to a management group that contains all of them — a resource-group-level assignment can't reach across subscription boundaries.
Assigning a policy with PowerShell and Cloud Shell
The same assignment can be done from Azure Cloud Shell (a browser-based shell with PowerShell and the Azure CLI preinstalled, no local setup needed) using the Az PowerShell module's policy cmdlets.
Create a resource group for the lab:
New-AzResourceGroup -Name Staging_VMs -Location CentralUS
Store the resource group object for reuse in later commands:
$rg = Get-AzResourceGroup -Name Staging_VMs -Location CentralUS
Retrieve an existing policy definition by its ID (found in the Azure portal's policy definitions section):
$definition = Get-AzPolicyDefinition -Id "/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/policyDefinitions/{policyDefinitionId}"
Assign the retrieved definition to the resource group:
New-AzPolicyAssignment -Scope $rg.ResourceId -PolicyDefinition $definition -Name RGLocationMatch -DisplayName "ResourceGroupMatches resources location"
-Scope takes the resource group's ResourceId, -PolicyDefinition takes the definition object retrieved above, and -Name/-DisplayName give the assignment a human-readable identity.
Common issues
- Cloud Shell storage account creation failure. Cloud Shell prompts to create a storage account on first use; if creation fails, try a different, more unique name.
- Policy definition ID not found. Double-check the ID copied from the portal — a missing character or a mismatched subscription is the usual cause.
- Insufficient permissions. Creating resource groups and assigning policies needs
OwnerorUser Access Administrator(or an equivalent custom role) on the target scope. - Variable not recognised. Usually means an earlier command in the sequence didn't actually run or assign the variable — check the preceding step rather than the one that errored.
- Azure's command surface for policy management does shift over time (cmdlet parameters and behaviour have changed before and will again) — treat the exact syntax above as a snapshot, and check current Microsoft Learn docs before relying on it for anything beyond a lab.