Automation

Azure Policy: diagnose a deny before creating a production exemption

A production runbook for qualifying an Azure Policy deployment deny with assignment, initiative, deny effect, compliance, scoped exemption, validation and rollback.

10 Jul 2026 azureazure-policypolicydenyexemptiongovernanceiacrbacobservabilityautomationrunbookrollbackproduction

An Azure deployment denied by Azure Policy often triggers the wrong reflex: disable the assignment, create a broad exemption, bend the template around the rule, or rerun the pipeline with a more powerful identity. The error can look administrative, but it may point to real drift: an unapproved region, a forbidden SKU, missing encryption, absent diagnostics, a required tag that disappeared, or a resource created outside the governed landing zone.

The use case is a Terraform, Bicep, ARM, Azure DevOps or GitHub Actions deployment that fails in production with a deny effect. The runbook goal is to decide whether the change must be fixed, exempted with tight scope, rerun after propagation, or blocked. An exemption is not a shortcut. It is a production decision with scope, expiry, evidence and rollback.

Identify the exact deny

Start by freezing the deployment and the Policy error. RequestDisallowedByPolicy is not enough. You need the assignment, definition, initiative when applicable, effect, target resource and evaluated field.

text policy-deny-incident.txt
Blocked deployment
Environment: production
Tool: Terraform, Bicep, ARM, Azure DevOps or GitHub Actions
Run: deploy-prod-20260710.6
Subscription: sub-prod-core
Resource group: rg-prod-app
Denied resource: Microsoft.Web/sites/orders-api
Operation: create or update
Error: RequestDisallowedByPolicy

Evidence to collect
policyAssignmentId
policyDefinitionId or policySetDefinitionId
policyDefinitionReferenceId when an initiative is involved
effective policy effect: deny, modify, append, audit, deployIfNotExists
evaluated field or alias
assignment parameters
existing or missing exemption
recent policy change

If the team cannot map the denial to a specific rule, it should not create an exemption. It may bypass the wrong guardrail.

Read the assignment and parameters

A policy may be valid but incorrectly parameterized at management group, subscription or resource group level. The incident can come from an obsolete region list, a renamed mandatory tag, an updated initiative or an expired exemption.

bash 01-policy-assignment-context.sh
ASSIGNMENT_ID="/providers/Microsoft.Management/managementGroups/mg-prod/providers/Microsoft.Authorization/policyAssignments/deny-unapproved-web-sku"

az policy assignment show --ids "$ASSIGNMENT_ID" --query "{name:name,scope:scope,notScopes:notScopes,enforcementMode:enforcementMode,definition:policyDefinitionId,parameters:parameters,metadata:metadata}" --output json

az policy exemption list --scope "/subscriptions/<subscription-id>/resourceGroups/rg-prod-app" --query "[].{name:name,assignment:policyAssignmentId,expires:expiresOn,category:exemptionCategory,selectors:resourceSelectors}" --output table

Also check whether enforcementMode is Default or DoNotEnforce. A deployment may pass in test because the rule is effectively audit-only there, then fail in production where the deny is active.

Separate template error from governance signal

The diagnosis should answer one practical question: is the policy blocking a genuinely non-compliant change, or is it blocking an acceptable case that the governance model has not captured yet?

text policy-deny-triage.txt
Template or plan issue
Region is outside the approved list
SKU is not allowed
Diagnostic settings are missing
Public network access is enabled without justification
Mandatory tag is missing or empty
Managed identity is not enabled
Encryption or minimum TLS is not compliant

Rule too broad or obsolete parameter
New SKU is approved but missing from parameters
Region was added to the landing zone but not to policy
Azure Policy alias does not cover the new resource mode cleanly
Initiative was updated without operational notice
Existing exemption expired without an owner

Block the change when
The requested exemption would cover the whole resource group
The proposed fix disables the assignment
Security or business risk is undocumented
Application rollback is not ready

The right fix is often in the template: add diagnostics, tags, identity, an approved SKU or a compliant parameter. Exemption should remain the exception, not the normal deployment path.

Rebuild the decision from logs

Correlate the run with Azure Activity and Policy state. Logs help separate a Policy deny from an RBAC denial, a provider issue or an ARM validation error.

kusto 02-policy-deny-correlation.kql
let StartTime = datetime(2026-07-10T08:00:00Z);
let EndTime = datetime(2026-07-10T09:00:00Z);
AzureActivity
| where TimeGenerated between (StartTime .. EndTime)
| where ActivityStatusValue in ("Failure", "Failed")
| where Properties has "RequestDisallowedByPolicy"
 or Properties has "policyAssignmentId"
| project TimeGenerated,
        OperationNameValue,
        ActivityStatusValue,
        Caller,
        ResourceGroup,
        ResourceProviderValue,
        ResourceId,
        CorrelationId,
        Properties
| order by TimeGenerated asc

Keep the CorrelationId in the incident record. It proves that the exemption or template fix matches the observed denial, not a generic assumption.

Check compliance before redeploying

Before rerunning the pipeline, evaluate the target resource or scope. A blind rerun can fail the same way, or worse, pass after an overly broad exemption without anyone seeing the accepted risk.

bash 03-policy-state-check.sh
SUBSCRIPTION_ID="<subscription-id>"
RESOURCE_GROUP="rg-prod-app"

az policy state list --subscription "$SUBSCRIPTION_ID" --resource-group "$RESOURCE_GROUP" --query "[?complianceState=='NonCompliant'].{resource:resourceId,assignment:policyAssignmentName,definition:policyDefinitionName,reference:policyDefinitionReferenceId,state:complianceState,timestamp:timestamp}" --output table

az policy state summarize --subscription "$SUBSCRIPTION_ID" --resource-group "$RESOURCE_GROUP" --output json

If compliance state has not converged yet, say so. The decision should not depend on a view that is still catching up.

Scope the exemption if it is necessary

A production exemption must be bounded by scope, resource, assignment, duration, owner and exit condition. It must not remove the guardrail for future changes.

yaml policy-exemption-contract.yml
exemption:
reason: deploy hotfix while diagnostic settings policy parameter is corrected
assignment: deny-missing-diagnostic-settings
scope: /subscriptions/sub-prod-core/resourceGroups/rg-prod-app/providers/Microsoft.Web/sites/orders-api
category: Waiver
expires_on: 2026-07-17T18:00:00Z
owner: platform-operations
accepted_risk:
  - diagnostic setting will be added by follow-up change
  - alert coverage remains active through existing Application Insights rules
forbidden:
  - exemption at subscription scope
  - disabling assignment
  - changing policy effect from deny to audit for all resources
exit_criteria:
  - template corrected
  - compliance scan green
  - exemption removed
  - deployment validation attached to incident

If the need is permanent, fix the policy or its parameters. A long-lived exemption without an owner becomes governance debt.

Decide fix, exemption or block

The runbook should end with an explicit decision. The pipeline should not merely turn green. It should turn green for a reason the team can explain later.

text policy-deny-decision.txt
Fix the template
The denial matches a valid requirement
The fix is limited and testable
No policy bypass is required
The plan contains only expected changes

Fix policy or parameters
The rule is legitimate but parameters are obsolete
The governance change is reviewed as code
Impacted scopes are identified
A compliance scan validates the result

Create a scoped exemption
The production need is real and temporary
Accepted risk is documented
Scope, expiry and owner are defined
Exemption exit is planned

Block
The request hides a security or compliance risk
Exemption scope is too broad
Deployment already produced unqualified partial state
Application or infrastructure rollback is missing

This matrix avoids two opposite failures: blocking every release through rigidity, or hollowing out Azure Policy with global exceptions.

Validate after action and roll back the guardrail

After a fix or exemption, rerun with the same artifact or a clearly identified plan. Then validate both the service and governance state.

text post-policy-action-validation.txt
Minimum validation
Deployment uses the approved run and commit
Created or modified resource matches the expected scope
Azure Activity shows no unexpected deny
Unrelated policies remain active
Compliance state is reviewed after propagation
Application probes pass
Exemption has owner, expiry and justification

Rollback
Remove the exemption if validation fails
Revert the template if the fix breaks the service
Restore policy parameters if the initiative was opened too far
Run a new compliance scan
Keep the initial deny, decision and exit evidence in the incident record

Conclusion

An Azure Policy deny is not just a deployment error. It is a decision point between compliance, service continuity and operations.

The runbook should therefore produce a traceable outcome: fix the template, adjust the policy, create a temporary scoped exemption, or block. The real validation is not only a green pipeline. It is a compliant resource, an active guardrail and a rollback path if the exception proves too risky.