Skip to content

Azure Policy Compliance Runbook

Scope

Applies to both tenants:

  • PRODciriusgroup.com, tenant d477c9f8
  • DDEciriusdde.com, tenant ff1c5d68

Frameworks enforced: HIPAA/HITRUST (§164.308 administrative safeguards, §164.312 technical safeguards), HITRUST CSF, ISO 27001. All subscriptions in both tenants have these initiatives assigned. Resources are Terraform-managed — most remediation paths lead back to a PR, not the portal.


1. Navigating to the Compliance Dashboard

Portal Path

Azure Portal → Policy → Compliance

Both tenants must be checked independently. Switch tenants via account avatar → Switch directory.

For PROD: https://portal.azure.com/#view/Microsoft_Azure_Policy/PolicyMenuBlade/~/Compliance

For DDE: switch tenant first, then navigate to the same path.

Filtering by Initiative

The default view shows all policy assignments. To isolate a specific framework:

  1. On the Compliance page, click Filter (top toolbar)
  2. Set Initiative → search for the initiative name:
    • HIPAA HITRUST 9.2 — the HIPAA/HITRUST combined initiative
    • ISO 27001:2013 — ISO initiative
    • HITRUST/HIPAA may also appear depending on which version was assigned
  3. Set Scope if you want to narrow to a specific subscription or management group

Alternatively, use the search bar at the top of the compliance list and type part of the initiative name.

Reading the Compliance Percentage

The compliance percentage shown for an initiative is: (compliant resources) / (compliant + non-compliant resources) × 100. Unknown and exempt resources are excluded from the denominator.

Key numbers to watch:

  • Overall compliance % — headline number per initiative
  • Non-compliant resources count — actionable items
  • Non-compliant policies count — how many policy definitions are failing, which tells you whether the issue is widespread or isolated

A 95%+ score on HIPAA/HITRUST is the operating target. If you see it drop below 90% investigate before the next monthly review cycle.

Azure CLI — Compliance Summary

bash
# Summarize compliance for all initiatives in PROD (requires subscription ID)
az policy state summarize --subscription <subscription-id> --output table

# Summarize for a specific initiative definition
az policy state summarize \
  --subscription <subscription-id> \
  --filter "policySetDefinitionName eq 'a169a624-5599-4385-a696-c8d643089fab'" \
  --output table

The HIPAA/HITRUST built-in initiative ID is a169a624-5599-4385-a696-c8d643089fab. ISO 27001 built-in is 89c6cddc-1c73-4ac1-b19c-54d1a15a42f9. Confirm the active assignment IDs via az policy assignment list.


2. Understanding the Dashboard

Compliance States

StateMeaning
CompliantResource satisfies the policy condition
Non-compliantResource violates the policy condition — requires action or exemption
ExemptResource is excluded from evaluation via an exemption resource — counts as neither compliant nor non-compliant
UnknownPolicy couldn't evaluate the resource — usually missing read permissions, unsupported resource type, or a DeployIfNotExists effect that hasn't completed yet
ConflictTwo policies are assigning conflicting configurations

Non-compliant is the only state that requires triage. Unknown warrants investigation if it appears on a resource type that should be evaluable — it sometimes masks a real gap.

How Scope Works

Azure Policy evaluates at the assigned scope and inherits downward:

Management Group (if configured)
  └── Subscription (PROD / DDE each have separate subscriptions)
        └── Resource Group
              └── Resource

At Cirius, initiatives are assigned at the subscription level in both tenants. This means every resource group and resource within a subscription is in scope unless an exemption is applied. There is no management group layer currently.

To confirm the scope of a specific assignment:

Azure Policy → Assignments → click the assignment → Basics tab → Scope field

If a new subscription is created, it is NOT automatically covered — you must create a new policy assignment. See Section 7.


3. Drilling Into a Non-Compliant Resource

Portal Walk-Through

  1. Azure Policy → Compliance
  2. Click the initiative name (e.g., HIPAA HITRUST 9.2)
  3. You see the list of policy definitions within that initiative. Each row shows its own compliance percentage and non-compliant resource count
  4. Click a non-compliant policy definition (e.g., Audit VMs that do not use managed disks)
  5. The Resource compliance tab lists every non-compliant resource with columns: Resource name, Resource group, Resource type, Subscription, Compliance state
  6. Click a specific resource row → View resource compliance → you see the specific policy effect, condition that failed, and the resource's current property values

The Reason for non-compliance section shows the exact condition that evaluated to false. This is the most important field — it tells you whether the fix is a config change, a tag, a diagnostic setting, or a property that requires a resource rebuild.

Azure CLI — Non-Compliant Resources for a Policy

bash
# List non-compliant resources for a specific policy definition
az policy state list \
  --subscription <subscription-id> \
  --filter "complianceState eq 'NonCompliant' and policyDefinitionName eq '<policy-def-name>'" \
  --query "[].{Resource:resourceId, Policy:policyDefinitionName, Reason:complianceReasonCode}" \
  --output table

# Get all non-compliant states across a subscription
az policy state list \
  --subscription <subscription-id> \
  --filter "complianceState eq 'NonCompliant'" \
  --query "[].{Resource:resourceId, Policy:policyDefinitionName, Effect:policyDefinitionAction}" \
  --output table

4. Triage Workflow

For every non-compliant resource, work through these checks in order before deciding on a remediation path.

Step 1 — Is This a Real Gap?

Check the policy definition's effect:

  • Audit — finding only, no enforcement. Non-compliance means the resource exists but doesn't meet the condition. It's a gap.
  • Deny — the policy should have blocked this resource from being created. If it's showing non-compliant, the resource predates the policy or the assignment scope changed.
  • DeployIfNotExists / Modify — means the policy can auto-remediate if you trigger a remediation task. Non-compliance here is often just lag — the policy engine hasn't run yet or the remediation task hasn't been triggered.

For Audit effect findings: confirm whether the resource property that's failing maps to a real HIPAA control. If the policy is checking for a tag and the resource doesn't have it, that's a tagging gap. If it's checking for encryption at rest and the resource is unencrypted, that's a HIPAA §164.312(a)(2)(iv) gap. The policy definition name and description tell you which control it covers.

Step 2 — Is the Resource Terraform-Managed?

Check Terraform state before doing anything else.

bash
# In the relevant repo (azure-infra or azure-dde-infra), run locally (read-only)
terraform state list | grep <resource-name>

# Get full details for a specific resource
terraform state show <terraform-resource-address>

If the resource is in state, the fix goes in Terraform. Do not remediate in the portal and leave Terraform out of sync — that creates drift that will either fail on the next plan or get overwritten.

If the resource is NOT in state, check whether it was recently added to Azure outside of Terraform (someone created it manually or it was provisioned by a service). If so, it needs to be imported before it can be remediated. See the import workflow in the Terraform Standards section of CLAUDE.md.

Step 3 — Is It Decommission-Bound?

Check the June 2026 decommission list: runbooks/june-2026-decommission-checklist.md.

Resources scheduled for deletion before June 2026 do not need HIPAA remediation. The correct approach is:

  1. Confirm the resource is on the decommission list
  2. Apply an exemption with category Waiver and an expiry date of 2026-07-01
  3. Add a comment in the exemption: Decommission-bound per june-2026-decommission-checklist.md
  4. Document the exemption in this runbook's exemption log (Section 6)

Do not waste engineering time hardening resources that are scheduled for deletion.

Step 4 — Does a Compensating Control Exist?

Some HIPAA requirements can be satisfied by a compensating control rather than the exact technical implementation the policy checks. Document these as exemptions with category Mitigated.

Common examples at Cirius:

  • Palo Alto marketplace images cannot use encryption_at_host — the compensating control is CMK disk encryption. This is a known documented exception.
  • Log Analytics workspace diagnostic settings that reference themselves are circular — the compensating control is the workspace itself retaining logs.
  • A resource that the policy flags for missing a specific tag when the tag is applied at the resource group level via inheritance.

If a compensating control exists, create a Mitigated exemption (Section 6) rather than trying to make the policy pass artificially.

Triage Decision Tree

Non-compliant resource identified

  ├─ Is the effect DeployIfNotExists/Modify?
  │     └─ Yes → Trigger remediation task first. Check again in 24h before going further.

  ├─ Is the resource decommission-bound (June 2026)?
  │     └─ Yes → Waiver exemption. Done.

  ├─ Is the resource Terraform-managed?
  │     ├─ Yes → Fix in Terraform. Create PR. Reference the policy name in the PR description.
  │     └─ No → Import first, then fix in a separate PR.

  └─ Does a compensating control exist?
        ├─ Yes → Mitigated exemption. Document the compensating control.
        └─ No → This is a real gap. Fix in Terraform or escalate to Rory.

Rory is the sole decision-maker on remediation approach. Any path that involves an exemption or a compensating control claim needs his sign-off.


5. Remediation Paths

Path A — Terraform (Preferred)

All compliance gaps that can be addressed via infrastructure configuration go through Terraform.

  1. Identify the failing property from the policy's non-compliance reason
  2. Find the resource in the appropriate repo (azure-infra or azure-dde-infra)
  3. If the resource uses a module, check whether the module already supports the property — if so, set it in the module call. If not, the module needs to be updated first (modules-first principle).
  4. Make the change, create a PR
  5. PR description must include:
    • Policy definition name
    • HIPAA/ISO control reference (e.g., §164.312(a)(2)(iv))
    • What property is being set and what value
  6. CI runs terraform plan — confirm the plan shows only the expected change
  7. Rory reviews and merges — CI applies

Never terraform apply locally. Never modify the resource in the portal and leave Terraform behind.

Path B — Azure Policy Remediation Task

Used when the policy effect is DeployIfNotExists or Modify and there are many existing resources to remediate at scale. This is appropriate for things like deploying diagnostic settings to all existing VMs, or enabling a property across all storage accounts.

A remediation task tells Azure Policy to trigger the deployment effect on all currently non-compliant resources.

Portal:

Azure Policy → Remediation → + New remediation task
  → Select the policy assignment
  → Select scope (subscription or resource group)
  → Set remediation task name
  → Create

CLI:

bash
az policy remediation create \
  --name "remediate-diag-settings-$(date +%Y%m%d)" \
  --policy-assignment <assignment-id> \
  --resource-discovery-mode ReEvaluateCompliance \
  --subscription <subscription-id>

# Monitor status
az policy remediation show \
  --name "remediate-diag-settings-<date>" \
  --subscription <subscription-id>

After triggering a remediation task, it can take 24–48 hours for the compliance score to update. Do not create a Terraform PR for the same resources while a remediation task is running — you'll end up with conflicting configurations.

Any resources modified by a remediation task that are also Terraform-managed need to be reconciled: run terraform plan after the remediation completes to see the drift, then update the Terraform config to match.

Path C — Exemption

Used when the resource genuinely cannot or should not comply with a specific policy. See Section 6 for the full exemption procedure.


6. Creating an Exemption

When Exemptions Are Appropriate

  • Resource is decommission-bound (June 2026 target)
  • A compensating control exists that satisfies the underlying HIPAA requirement
  • The resource type is technically incapable of satisfying the policy (documented platform limitation)
  • The policy is a false positive for this resource's specific configuration

Exemptions are not appropriate for resources that should comply but haven't been fixed yet. Those are unresolved gaps and should be tracked in the remediation backlog, not hidden behind exemptions.

Exemption Categories

CategoryUse When
WaiverThe requirement does not apply to this resource, or the resource will be decommissioned before the requirement matters. Example: decommission-bound VM, resource type with no PHI exposure.
MitigatedA compensating control exists that addresses the underlying risk. Example: Palo Alto VM with CMK as compensating control for encryption_at_host, resource group-level tag inherited by resource.

Required Fields for Every Exemption

  • Exemption name — format: exempt-<resource-short-name>-<policy-short-name>-<YYYYMM>
  • Display name — human-readable description of why this is exempt
  • Exemption category — Waiver or Mitigated
  • Policy assignment — which initiative assignment this exemption is against
  • Policy definition(s) — if the exemption is for specific policies within an initiative, list them. If left blank, the exemption applies to all policies in the initiative for this resource.
  • Expiry date — required. Maximum 1 year. Decommission-bound resources: set to 2026-07-01. Mitigated exemptions: set to current date + 12 months.
  • Description — must include: what compensating control exists OR why the requirement doesn't apply, link to the relevant CLAUDE.md or runbook section if applicable, and who approved it (Rory)

Creating an Exemption — Portal

Azure Policy → Compliance → [click initiative] → [click policy definition] → [click resource]
  → Create exemption (button at top)
  → Fill all required fields
  → Review + create

Alternatively from the Assignments blade:

Azure Policy → Assignments → [click assignment] → Create exemption
  → Set scope to the specific resource (not the whole subscription)

Creating an Exemption — CLI

bash
az policy exemption create \
  --name "exempt-paloalto-enchost-202506" \
  --display-name "Palo Alto VM - encryption_at_host not supported - CMK compensating control" \
  --exemption-category Mitigated \
  --policy-assignment <assignment-id> \
  --scope "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.Compute/virtualMachines/<vm-name>" \
  --expires-on "2026-06-30T00:00:00Z" \
  --description "Palo Alto marketplace images do not support encryption_at_host. Compensating control: CMK disk encryption applied via keyvault-cmk module. Approved by Rory." \
  --output json

Exemption Log

Record every exemption created. Add a row to this table when you create one.

DateResourcePolicyCategoryExpiryCompensating Control / ReasonApproved By

Review this table monthly. Expired exemptions that are still relevant must be renewed with a fresh review. Expired exemptions that are no longer relevant should be deleted.


7. Policy Initiative Assignments

Checking Which Initiatives Are Assigned Where

Portal:

Azure Policy → Assignments

Filter by scope to see assignments at subscription level. Each tenant must be checked separately (switch tenants).

CLI — List All Assignments in a Subscription:

bash
# PROD tenant — replace with actual subscription IDs
az policy assignment list \
  --subscription <subscription-id> \
  --query "[].{Name:name, DisplayName:displayName, Scope:scope, PolicySetId:policyDefinitionId}" \
  --output table

# Run for each subscription in PROD and DDE

You should see assignments for:

  • HIPAA HITRUST 9.2 (or equivalent)
  • ISO 27001:2013
  • HITRUST CSF

If any of these are missing from a subscription, that subscription is out of compliance scope. See below.

Adding a New Subscription to Compliance Scope

When a new Azure subscription is created in either tenant, it has no policy assignments by default. Before provisioning any resources into a new subscription:

  1. Confirm the subscription is registered in the correct tenant
  2. Create policy assignments for all three initiatives:
bash
# HIPAA/HITRUST — built-in initiative
az policy assignment create \
  --name "hipaa-hitrust-<subscription-short-name>" \
  --display-name "HIPAA HITRUST 9.2 — <subscription-short-name>" \
  --policy-set-definition "a169a624-5599-4385-a696-c8d643089fab" \
  --scope "/subscriptions/<new-sub-id>" \
  --enforcement-mode Default

# ISO 27001 — built-in initiative
az policy assignment create \
  --name "iso27001-<subscription-short-name>" \
  --display-name "ISO 27001:2013 — <subscription-short-name>" \
  --policy-set-definition "89c6cddc-1c73-4ac1-b19c-54d1a15a42f9" \
  --scope "/subscriptions/<new-sub-id>" \
  --enforcement-mode Default
  1. Add the new subscription to the Terraform backend configuration and CI/CD pipeline before any resources are created
  2. Create a PR in azure-infra or azure-dde-infra that includes the policy assignment as a Terraform resource — do not rely on the CLI-created assignment alone

Verifying Coverage

bash
# Quick check — list all subscriptions in the tenant and cross-reference against assignments
az account list --query "[].{Name:name, Id:id, TenantId:tenantId}" --output table

# For each subscription ID, run:
az policy assignment list \
  --subscription <sub-id> \
  --query "[?contains(policyDefinitionId, 'policySetDefinitions')].displayName" \
  --output table

Any subscription that returns no initiative assignments is uncovered.


8. KQL Queries

Run all queries against cirius-logging-law-central (workspace ID 5d76d1f2) in the logging subscription of the PROD tenant. Use the portal: Log Analytics workspaces → cirius-logging-law-central → Logs, or via CLI:

bash
az monitor log-analytics query \
  --workspace 5d76d1f2 \
  --analytics-query "<paste query here>" \
  --output table

Non-Compliant Resources by Policy

kql
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.POLICYINSIGHTS"
| where OperationName == "Microsoft.PolicyInsights/policyEvents/queryResults/action"
| where complianceState_s == "NonCompliant"
| summarize Count = count() by policyDefinitionName_s, resourceId_s
| sort by Count desc

For richer data, use the Policy Insights table directly if your LAW has it connected. Otherwise query via the Azure CLI REST API:

bash
# All non-compliant policy states across a subscription, last evaluated
az policy state list \
  --subscription <subscription-id> \
  --filter "complianceState eq 'NonCompliant'" \
  --select "resourceId,policyDefinitionName,policySetDefinitionName,complianceState,timestamp" \
  --query "sort_by([],&timestamp)" \
  --output table

Compliance Trend Over Time

Azure Policy stores state snapshots. To see how compliance has changed:

kql
// If PolicyInsights data is flowing to LAW
AzureActivity
| where OperationNameValue startswith "MICROSOFT.POLICYINSIGHTS"
| where TimeGenerated > ago(90d)
| summarize Events = count() by bin(TimeGenerated, 1d), ActivityStatusValue
| render timechart

For direct compliance trend data, the Azure Policy Compliance History API is more reliable than LAW:

bash
# Get compliance state change history for a resource
az policy state list \
  --resource <resource-id> \
  --apply "groupby((timestamp,complianceState),aggregate($count as Count))" \
  --output table

Resources That Became Non-Compliant Since Last Week

bash
# Pull non-compliant states with timestamps, filter to last 7 days
az policy state list \
  --subscription <subscription-id> \
  --filter "complianceState eq 'NonCompliant'" \
  --select "resourceId,policyDefinitionName,timestamp,policySetDefinitionName" \
  --query "[?timestamp >= '$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)']" \
  --output table

Note: timestamp reflects when the policy engine last evaluated the resource, not when it became non-compliant. New entries since last week indicate either newly created non-compliant resources or resources whose compliance state changed.

Diagnostic Settings Coverage (HIPAA §164.312(b) — Audit Logs)

This is one of the most common HIPAA policy failures. Check which resources are missing diagnostic settings:

kql
// Resources with no diagnostic settings — pulls from Activity Log
AzureActivity
| where TimeGenerated > ago(7d)
| where OperationNameValue == "MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/WRITE"
| summarize LastDiagWrite = max(TimeGenerated) by Resource
| join kind=rightanti (
    AzureActivity
    | where TimeGenerated > ago(30d)
    | summarize by Resource
) on Resource
| project Resource

More directly, use the Policy compliance state for the specific diagnostic settings policy:

bash
az policy state list \
  --subscription <subscription-id> \
  --filter "complianceState eq 'NonCompliant' and policyDefinitionName eq 'Deploy Diagnostic Settings for Key Vault to Log Analytics workspace'" \
  --query "[].resourceId" \
  --output tsv

Activity Log — Policy Exemption Changes

Track when exemptions are created or deleted (audit trail):

kql
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue contains "POLICYEXEMPTIONS"
| project TimeGenerated, Caller, OperationNameValue, ResourceId, ActivityStatusValue
| sort by TimeGenerated desc

9. Monthly Compliance Review Procedure

Run on the first Monday of each month, as part of the broader monthly security review. This section covers the Azure Policy compliance component specifically.

Pre-Review Setup

Pull the current compliance state before the review meeting:

bash
# For each subscription in PROD and DDE — collect these numbers
az policy state summarize \
  --subscription <subscription-id> \
  --output json | jq '{
    nonCompliantPolicies: .results.nonCompliantPolicies,
    nonCompliantResources: .results.nonCompliantResources,
    policyDetails: [.policyDetails[] | {name: .policyDefinitionId, nonCompliant: .results.nonCompliantResources}]
  }'

Capture the numbers for both tenants before the review.

What to Look At

1. Overall compliance percentage per initiative

  • Navigate to Azure Policy → Compliance for each tenant
  • Record the % for HIPAA/HITRUST, ISO 27001, and HITRUST CSF
  • Compare to prior month (keep a running log — see table below)

2. New non-compliant resources since last month

  • Run the "became non-compliant since last week" query but extend to 30 days
  • Any resource that was compliant last month and is now non-compliant requires explanation

3. Exemption expiry review

  • Check the exemption log in Section 6 of this runbook
  • Any exemption expiring in the next 30 days needs renewal or deletion
  • Deleted exemptions expose the resource to non-compliance — have a plan before deleting

4. Remediation task status

  • Azure Policy → Remediation → check for any stuck or failed remediation tasks
  • Failed tasks should be investigated — they may indicate a permissions problem or a resource type the policy can't handle

5. New policy definitions added to initiatives

  • Microsoft occasionally adds new policy definitions to built-in initiatives
  • Check whether the non-compliant count is higher than expected — new definitions can surface gaps that weren't visible before

Action Thresholds

MetricThresholdAction
HIPAA/HITRUST complianceBelow 90%Immediate investigation, triage all non-compliant resources within 48h
HIPAA/HITRUST compliance90–95%Investigate all new findings this month, remediation in current sprint
HIPAA/HITRUST complianceAbove 95%Normal operations, track open items
New non-compliant resources (month-over-month)>10 newDeep dive required — check for Terraform drift, new resources created outside IaC
Exemptions expiringAny in next 30 daysRenew or plan deletion before expiry

Compliance Score Log

Update this table each month after completing the review.

MonthPROD HIPAA/HITRUSTPROD ISO 27001DDE HIPAA/HITRUSTDDE ISO 27001Non-Compliant Resources (PROD)Non-Compliant Resources (DDE)Notes

Reporting to Kevin and Greg

Kevin and Greg are T1 Domain Admins and escalation path. They consume compliance reports but are not the decision-makers on remediation approach — that stays with Rory.

What to send:

  • Overall compliance percentage for each initiative in each tenant
  • Count of non-compliant resources, grouped by whether they are: actively being remediated, exempt with justification, or new findings requiring triage
  • Any findings that represent a gap in HIPAA §164.308 or §164.312 controls that are not yet addressed
  • Projected remediation completion dates for any open items

Format: Plain email or SharePoint document (coordinate with Adriana for the SharePoint upload if it needs to go into the compliance folder). No raw JSON or KQL output in the report — translate to plain language.

Frequency: Monthly, aligned to the monthly security review. If compliance drops below 90% at any point, send an interim notification within 24 hours — do not wait for the monthly cycle.

Escalation trigger: If a HIPAA §164.312 technical safeguard is non-compliant and has no remediation path or exemption in place, that is a reportable gap. Document it, set a remediation deadline, and ensure Kevin and Greg are aware it is being tracked.

Internal use only — Cirius Group