Automation

Azure API Management: validate a shared policy fragment before production rollout

A production runbook for inventorying references, versioning an APIM policy fragment, testing it on a canary API revision, correlating traces and deciding promotion or rollback.

06 Sept 2026 azureapi-managementapimpolicy-fragmentsdevopsautomationobservabilitysecuritycanaryrunbookrollbackproduction

A team needs to harden authentication across several APIs behind Azure API Management. The logic already lives in a shared policy fragment: validate the token, normalize selected headers and pass identity context to the backend. Editing that fragment in place looks cleaner than changing every API. It is also the fastest way to distribute one mistake to every consumer.

XML validation is only part of the problem. A fragment is inserted as-is wherever a policy references it. Its behavior therefore depends on the policy section, statement order, variables already initialized, named values and the gateway executing the policy. This runbook ends with an operational decision: promote a new fragment version, restrict its scope, or restore the previous reference without rewriting the entire policy estate.

Freeze the contract before editing the fragment

Describe what the change must achieve and what it must never alter. The XML diff is only an implementation of that contract.

yaml apim-fragment-change.yml
change:
id: CHG-2941
fragment_current: auth-context-v1
fragment_candidate: auth-context-v2
intent: reject expired tokens and preserve backend context

must_preserve:
- valid-client-request-is-forwarded
- backend-receives-correlation-id
- existing-product-subscriptions-still-work
- error-body-does-not-leak-token-details

must_reject:
- expired-token
- wrong-audience
- missing-required-claim

rollback:
reference: auth-context-v1
owner: api-platform
trigger: unexplained-401-403-5xx-or-latency-regression

Freeze the exact scope too: APIM instance, workspace if applicable, products, APIs, operations and gateways. A passing test on one API says little about another policy with a different ordering of <base />, fragments or variables.

Inventory the actual consumers

The list of teams believed to use the fragment is not an inventory. Read references from deployed policies, then map each reference to its scope and production traffic.

bash 01-export-apim-policy-state.sh
APIM_ID="/subscriptions/<subscription>/resourceGroups/rg-api-prod/providers/Microsoft.ApiManagement/service/apim-prod"
API_VERSION="<supported-management-api-version>"

az rest --method get \
--url "https://management.azure.com/$APIM_ID/policyFragments?api-version=$API_VERSION" \
--output json > policy-fragments-before.json

# Export global, product, API and operation policies from IaC or the
# management API, then locate the exact reference.
rg -n 'include-fragment fragment-id="auth-context-v1"' exported-policies/ \
> auth-context-v1-references.txt

test -s auth-context-v1-references.txt

Retain the fragment content, references, named values and their scopes, each API’s current revision, and the expected IaC commit. Do not assume indirect references: a fragment cannot nest another fragment, and it cannot contain a policy section or <base /> element.

Version instead of editing in place

Updating a shared fragment affects its existing references. For a sensitive change, create a new identifier such as auth-context-v2, keep it immutable during the canary, and update one test policy to reference it. Temporary duplication is a rollout control, not permanent architecture.

text fragment-review-gates.txt
Structure
Valid XML containing one or more policy statements
No inbound, backend, outbound or on-error section
No base element
No nested include-fragment

Dependencies
Named values exist at the required scope
Variables are read after initialization
Secrets never appear in traces or responses
Statements support the target section and gateway

Change
New fragment ID used for the canary
Diff reviewed as behavior, not only XML
v1 reference retained for rollback
No unrelated policy change in the same batch

Execution order is the sharp edge. Moving validation before variable initialization, placing a transformation after backend routing, or changing its position relative to <base /> can alter behavior while leaving the document syntactically valid.

Build a representative canary revision

Create a non-current revision of a representative API and replace only the v1 reference with v2. Keep the same parameters, backend, product and test subscription. An explicit ;rev=<number> URL lets the team call the candidate without moving normal traffic.

The canary needs a happy path, expected denials and a backend non-regression control.

bash 02-probe-fragment-canary.sh
BASE_URL="https://api.example.net/orders"
REVISION="<candidate-revision>"

# Control: current revision.
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer <valid-test-token>" \
-H "x-correlation-id: apim-fragment-current-001" \
"$BASE_URL/health"

# Candidate: explicitly address the new revision.
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer <valid-test-token>" \
-H "x-correlation-id: apim-fragment-v2-001" \
"https://api.example.net/orders;rev=$REVISION/health"

# Expected denial: verify status and a non-sensitive error body.
curl --silent --show-error --output candidate-deny.json --write-out '%{http_code}
' \
-H "Authorization: Bearer <expired-test-token>" \
-H "x-correlation-id: apim-fragment-v2-deny-001" \
"https://api.example.net/orders;rev=$REVISION/health"

Do not reuse a production token captured after expiry. Prepare test identities and tokens whose claims represent the expected scenarios. Check the status, body, headers, backend call and absence of sensitive output.

Read traces as chain evidence

An expected 401 is not sufficient: it may come from the fragment, another policy or the backend. Enable approved diagnostics on the canary and correlate each probe by ID. Adapt the fields to the table produced by the APIM diagnostic mode in use.

kusto 03-correlate-apim-canary.kql
ApiManagementGatewayLogs
| where TimeGenerated > ago(30m)
| where CorrelationId startswith "apim-fragment-v2-"
| project TimeGenerated,
        CorrelationId,
        ApiId,
        OperationId,
        ResponseCode,
        BackendResponseCode,
        TotalTime,
        BackendTime,
        LastErrorReason
| order by TimeGenerated asc

The evidence must connect request, policy decision and backend. A valid token produces one backend call with the expected context. An invalid token produces no backend call. If latency rises, separate gateway time from backend time before blaming the new validation.

Test the blast radius before promotion

The first canary proves the logic, not compatibility with every consumer. Group references by policy shape: global or product scope, include section, <base /> placement, existing variables, gateway type and named-value dependencies. Test one representative from every class.

text fragment-rollout-matrix.txt
Class A: standard API on managed gateway
Happy path, invalid token, missing claim, backend timeout

Class B: product policy with base and quotas
Valid auth, subscription key, quota preserved, stable error

Class C: self-hosted gateway or workspace
Statement support, named-value access, diagnostics available

Cross-cutting controls
No secret in logs or responses
Correlation ID preserved
401, 403 and 5xx rates compared with control
Gateway latency compared under representative load
v1 reference remains deployable

If one class cannot be tested, do not generalize the rollout. Keep v2 limited to proven scopes or remove the fragment’s implicit dependency.

Promote without losing rollback

Promotion changes references in bounded batches toward one frozen candidate. Do not edit v2 while APIs are migrating. A moving target makes results impossible to compare.

yaml fragment-rollout-decision.yml
promote:
when:
- every-policy-class-has-a-passing-canary
- valid-and-denied-paths-are-correlated
- gateway-latency-remains-within-change-budget
- backend-context-is-preserved
action: migrate-references-in-bounded-batches

hold:
when:
- consumer-inventory-is-incomplete
- named-value-scope-is-ambiguous
- one-gateway-class-is-untested
action: keep-v1-current-and-fix-evidence

rollback:
when:
- unexplained-authentication-regression
- backend-call-occurs-after-expected-deny
- sensitive-data-appears-in-output-or-traces
- gateway-error-or-latency-budget-is-breached
action:
- restore-v1-reference-for-last-batch
- redeploy-policy-only
- replay-positive-and-negative-probes
- reconcile-rejected-or-duplicated-requests

Rollback is not an emergency rewrite of v2. Restore the known v1 reference, redeploy only the policies in the affected batch, and replay the same probes. Keep v2 for analysis, then produce a corrected v3 if necessary.

Conclusion

An APIM policy fragment removes duplication but concentrates risk. The change unit is not the XML file alone. It includes the fragment, its references, named values, execution order, gateways and the requests that prove its behavior.

The final decision is straightforward to explain. Promote a frozen version after one canary per policy class, hold when inventory or traces are incomplete, and roll back by restoring the previous reference. Reuse remains an advantage only when its blast radius is observable and reversible.