↓ Skip to main content

Reference: Problem Post

Take Bakker
Author
Take Bakker

We had a policy assignment that looked fine in the portal. Compliance showed 98%. The remaining 2% was a handful of resources flagged for missing tags — known, accepted, moving to remediation next sprint. Nothing alarming.

Then someone asked why a storage account deployed three weeks ago was still publicly accessible. It wasn’t in the non-compliant list. The policy was supposed to deny it.

It took a while to figure out why.

What was happening
#

The policy was assigned at the subscription level with a Deny effect. New resources that don’t meet the conditions should fail deployment. That’s the expected behaviour, and it was working — we tested it.

What we hadn’t accounted for: the storage account had been deployed using an exemption that was added quietly six months earlier for a migration project. The exemption was never removed. The policy evaluated it as excluded, compliance dashboard showed green, and the actual configuration of the resource went unchecked.

The exemption didn’t show up on the resource’s compliance view in a way that made it obvious. You have to know to look for it.

Why this matters more than it seems
#

Policy exemptions are a legitimate tool. Long-running migrations, temporary exceptions while a workload is being refactored — there are real use cases. The problem is they’re easy to add and easy to forget.

In practice, I’ve seen environments where exemptions accumulate over time until the compliance dashboard is effectively meaningless. The numbers look good. The actual security posture isn’t what the numbers suggest.

Compliance percentage is a lagging indicator. It tells you how your known, evaluated resources are doing. It doesn’t tell you what’s been explicitly excluded from evaluation.

What we changed
#

A few things came out of this.

Exemption expiry. Azure Policy exemptions support an expiration date. We made it mandatory practice to set one — maximum 90 days. After that they expire automatically and the resource gets re-evaluated. Anything that still needs an exemption gets a deliberate renewal.

Exemption inventory. We added a query to Azure Resource Graph that pulls all active exemptions across subscriptions, with their expiry dates and the person who created them. It runs weekly and the output goes into the governance review.

PolicyResources
| where type == "microsoft.authorization/policyexemptions"
| extend expiresOn = properties.expiresOn
| extend assignedBy = properties.metadata.createdBy
| project subscriptionId, name, expiresOn, assignedBy
| order by expiresOn asc

Tighter access to exemptions. Creating a policy exemption is a privileged operation. We moved it behind a separate role assignment, so it’s not something that can happen without visibility.

The takeaway
#

If you’re relying on Policy compliance numbers to understand your security posture, check what’s in your exemptions. The 98% you’re seeing might be 85% with a quiet 13% sitting in exemptions that nobody is reviewing.

It’s not a bug in Azure Policy. Exemptions are supposed to work this way. But the governance process around them needs to be as deliberate as the policies themselves.