Background: what is "Invoke"?
Azure Policy is adding External Evaluation (colloquially "Invoke"): an enforcement policy that factors in the result of an external call when deciding to allow or deny an ARM operation. Today the only endpoint is an Azure Resource Graph (ARG) query; more are planned.
A normal policy can only inspect the request body. An External Evaluation policy can also query other state in the subscription via ARG, so it can enforce rules a plain policy cannot. Rule semantics: "Deny unless the request carries a token proving the external check ran and returned an acceptable result."
Typical use cases — what ARG actually checks
The ARG query runs at request time, scoped to the single subscription of the operation, and must return 0 or 1 rows projecting one claim the rule compares against. Patterns:
- Reference / over-sharing checks — canonical example: deny attaching an NSG to a subnet if that NSG is already attached to a different subnet. ARG counts the other subnets referencing the NSG; the rule denies if count > 0.
- Existence checks — deny (or require) based on whether a related resource exists, e.g. deny creating a resource unless a prerequisite resource is already present.
- Reference-by-others checks — deny deleting/changing a resource that other resources still depend on.
- Cross-resource invariants — e.g. deny a tag change on an NSG if it would violate an immutable-tag rule, decided by querying the resource's current state.
Not a fit: cross-subscription / cross-tenant / management-group checks (query is pinned to one subscription), or anything needing more than a single projected value.
How enforcement works (why ARM rejects the request)
- An External Evaluation policy is assigned to a scope. Its definition holds an ARG query + the claim it projects, plus
missingTokenAction (typically deny).
- A user issues a normal ARM op (e.g.
PUT a subnet referencing an NSG).
- The op carries no policy token, so ARM rejects it with
403 RequestDisallowedByPolicy — meaning "this op is guarded; prove the external check passes first." The 403 body carries machine-readable instructions.
- The client calls the subscription-level
acquirePolicyToken API, which runs the ARG queries of the applicable policies and signs the results into one token.
- The client retries the original op with the token in a header.
- ARM validates the token and proceeds (or the policy denies if the result is rejected).
The flow is reactive (triggered by the 403) and needs client-side orchestration, not just API exposure.
The Ask to the SDKs
Stage 1 (exposing acquirePolicyToken in the SDKs) is done. The ask is Stage 2: the end-to-end orchestration — detect the 403 → acquire token → retry — transparently, ideally hidden from the user, matching clients that already do this:
- Azure Portal and ARM Template / Deployments — auto intercept, acquire, retry.
- Azure CLI — global
--acquire-policy-token flag since v2.85.0.
- PowerShell — similar flag-based support.
CLI/PS add the behavior behind an optional switch initially, with a plan to remove the switch and make it default once the flow has been in use for a while.
Deliverables: a cross-language standardized design, plus samples and docs, aligned with each language owner / the architecture board.
REST contract (key wire details)
API versions
| Operation |
API version |
| Policy definition / assignment CRUD |
2025-03-01 |
acquirePolicyToken |
2025-11-01 |
1. Initial 403 — 403 RequestDisallowedByPolicy; actionable payload under error.additionalInfo[*].info.evaluationDetails.missingPolicyTokenDetails (shouldDeny, endpointKind, endpointDetails, endpointResultLifespan, isChangeReferenceRequired). Clients consume this directly to drive acquire + retry.
2. acquirePolicyToken request — subscription-level call describing the operation (URI, method, request body). The token is bound to the body via a contentHash.
3. acquirePolicyToken response
result — Succeeded (per-policy outcomes under results[*]) vs. Failed (acquisition itself failed — don't retry).
token — present when minted; always "PoP "-prefixed (pass verbatim).
results[*].policyAction — verdict: all Allow/Audit → token issued, retry; any Deny → no token, don't retry; Error → see policyEvaluationDetails.reason.
4. Retry
- Header
x-ms-policy-external-evaluations (lowercase, dashes; a typo = no header).
- Value =
token verbatim, incl. PoP prefix.
- Body must match the acquire request body byte-for-byte (
contentHash binding).
Current gap in the SDKs
- ✅ Stage 1 — API exposed.
- ❌ Stage 2 — E2E orchestration not implemented or documented; no clear owner.
Key challenges:
- Cross-library dependency —
acquirePolicyToken lives in the policy/authorization library, but guarded operations live across many resource libraries; the orchestration must work without coupling each service library.
- Byte-for-byte payload binding — the body sent to
acquirePolicyToken (operation.content) and the retried body must match contentHash exactly. This is handled naturally by the pipeline policy, which already has the final serialized bytes (re-serializing the model would risk drift).
Current direction & considerations <-- This is one proposal
- Where: an HTTP pipeline policy that sees the request body + response, performs acquire + retry, shared across libraries. (Agreed as the primary direction.)
- Detection: only signal is the
403 RequestDisallowedByPolicy + missingPolicyTokenDetails; no proactive detection.
- Errors: surface which policy blocked and what's required, not a raw 403; on
Failed/Deny, don't retry — fail clearly.
- Enablement: default-enabled in the SDK with an optional override (opt-in has no long-term value — it just lets policies break apps). CLI/PS start behind a switch and plan to make it default.
Open questions & current proposals
Each item lists the question and the current proposed direction (✅), or flags it as an open gap (❓) where no concrete answer exists yet.
- Enablement model — auto-enable vs. opt-in/opt-out. ✅ Default-enabled in the SDK, with an optional override. An opt-in approach has no long-term value — not opting in effectively asks for policies to break your app. (CLI/PS add the behavior behind a switch initially and plan to remove the switch once the flow has been in use for a while; SDKs should aim for default-on.)
One consideration for opt-in (instead of enable by default) is the Diagnosable guideline, and it is easier for customer to "add" a pipeline policy, than to "remove" one -- and this may depend on how many subscription enables this External Evaluation Policy.
- Error surfacing — original 403 vs. enriched error. ❓ In the case that
acquirePolicyToken itself fails (4xx), Keep the original 403 and surface its enriched policy details rather than replacing it with a new error type. In the case that acquirePolicyToken completes (200), but the Evalutation didn't pass, the 403 error need to wrap this information. We need more discussion on error/exception structure, as they are vital for customer to figure out that went wrong on 403 and no token, and the details of implementation can be language dependent.
- Retry semantics — how much custom retry logic is needed. ✅ Drive acquire + retry from the pipeline policy and reuse standard ARM retry behavior; avoid a bespoke retry model. In ambiguous internal-failure cases it may be best to just return the first response. ❓
200 + result=Failed + retryAfter and any multi-step retry chains beyond 403 → acquire → retry are not yet worked out.
- Cross-library integration —
acquirePolicyToken ships in one library while the guarded resource ops live in others. ✅ Solve it in a shared HTTP pipeline policy so the logic isn't coupled to individual service libraries. ❓ The concrete packaging (shared dependency vs. interface contract) is still open.
- Payload access / rebinding — the token request needs the full JSON payload, and the retry must resend it byte-for-byte to match
contentHash. ✅ Largely solved by the pipeline policy: it sits at the bottom of the pipeline and already holds the final serialized request body, so it can feed those exact bytes to acquirePolicyToken as operation.content and replay the identical bytes on retry — no re-serialization, no drift. (The user-facing models never need to expose the raw JSON.)
- Endpoint / version construction — which endpoint and API-version the pipeline uses for
acquirePolicyToken. ✅ Reuse the client's own endpoint (the same ARM host as the guarded PUT), so Public/Gov/other clouds are handled automatically. Use a default API-version with the ability for the user to override it.
- RBAC — what the caller needs to acquire a token. ✅ Subscription-level read on the same subscription as the original request, plus permission to perform the guarded operation; no additional restriction. Surface permission failures clearly.
- Cross-language alignment — architecture-board sign-off so behavior is consistent across languages. ❓ Pending.
Status
| Item |
Status |
| Stage 1 — API exposure |
✅ Done |
| Stage 2 — E2E SDK support |
❌ Not started / no owner |
| Design |
🟡 Active discussion, open questions |
| Cross-language alignment |
🔄 Needed (architecture board) |
Next steps
- Define a reference design for the pipeline policy (detect 403 → acquire → retry with token + original body).
- Settle payload rebinding and cross-library integration.
- Align with language architects / architecture board.
- Produce E2E scenarios, samples, and docs.
References
Background: what is "Invoke"?
Azure Policy is adding External Evaluation (colloquially "Invoke"): an enforcement policy that factors in the result of an external call when deciding to allow or deny an ARM operation. Today the only endpoint is an Azure Resource Graph (ARG) query; more are planned.
A normal policy can only inspect the request body. An External Evaluation policy can also query other state in the subscription via ARG, so it can enforce rules a plain policy cannot. Rule semantics: "Deny unless the request carries a token proving the external check ran and returned an acceptable result."
Typical use cases — what ARG actually checks
The ARG query runs at request time, scoped to the single subscription of the operation, and must return 0 or 1 rows projecting one claim the rule compares against. Patterns:
Not a fit: cross-subscription / cross-tenant / management-group checks (query is pinned to one subscription), or anything needing more than a single projected value.
How enforcement works (why ARM rejects the request)
missingTokenAction(typicallydeny).PUTa subnet referencing an NSG).403 RequestDisallowedByPolicy— meaning "this op is guarded; prove the external check passes first." The 403 body carries machine-readable instructions.acquirePolicyTokenAPI, which runs the ARG queries of the applicable policies and signs the results into one token.The flow is reactive (triggered by the 403) and needs client-side orchestration, not just API exposure.
The Ask to the SDKs
Stage 1 (exposing
acquirePolicyTokenin the SDKs) is done. The ask is Stage 2: the end-to-end orchestration — detect the 403 → acquire token → retry — transparently, ideally hidden from the user, matching clients that already do this:--acquire-policy-tokenflag since v2.85.0.Deliverables: a cross-language standardized design, plus samples and docs, aligned with each language owner / the architecture board.
REST contract (key wire details)
API versions
2025-03-01acquirePolicyToken2025-11-011. Initial 403 —
403 RequestDisallowedByPolicy; actionable payload undererror.additionalInfo[*].info.evaluationDetails.missingPolicyTokenDetails(shouldDeny,endpointKind,endpointDetails,endpointResultLifespan,isChangeReferenceRequired). Clients consume this directly to drive acquire + retry.2.
acquirePolicyTokenrequest — subscription-level call describing the operation (URI, method, request body). The token is bound to the body via acontentHash.3.
acquirePolicyTokenresponseresult—Succeeded(per-policy outcomes underresults[*]) vs.Failed(acquisition itself failed — don't retry).token— present when minted; always"PoP "-prefixed (pass verbatim).results[*].policyAction— verdict: allAllow/Audit→ token issued, retry; anyDeny→ no token, don't retry;Error→ seepolicyEvaluationDetails.reason.4. Retry
x-ms-policy-external-evaluations(lowercase, dashes; a typo = no header).tokenverbatim, incl.PoPprefix.contentHashbinding).Current gap in the SDKs
Key challenges:
acquirePolicyTokenlives in the policy/authorization library, but guarded operations live across many resource libraries; the orchestration must work without coupling each service library.acquirePolicyToken(operation.content) and the retried body must matchcontentHashexactly. This is handled naturally by the pipeline policy, which already has the final serialized bytes (re-serializing the model would risk drift).Current direction & considerations <-- This is one proposal
403 RequestDisallowedByPolicy+missingPolicyTokenDetails; no proactive detection.Failed/Deny, don't retry — fail clearly.Open questions & current proposals
Each item lists the question and the current proposed direction (✅), or flags it as an open gap (❓) where no concrete answer exists yet.
One consideration for
opt-in(instead of enable by default) is the Diagnosable guideline, and it is easier for customer to "add" a pipeline policy, than to "remove" one -- and this may depend on how many subscription enables this External Evaluation Policy.acquirePolicyTokenitself fails (4xx), Keep the original 403 and surface its enriched policy details rather than replacing it with a new error type. In the case thatacquirePolicyTokencompletes (200), but the Evalutation didn't pass, the 403 error need to wrap this information. We need more discussion on error/exception structure, as they are vital for customer to figure out that went wrong on 403 and no token, and the details of implementation can be language dependent.200 + result=Failed + retryAfterand any multi-step retry chains beyond 403 → acquire → retry are not yet worked out.acquirePolicyTokenships in one library while the guarded resource ops live in others. ✅ Solve it in a shared HTTP pipeline policy so the logic isn't coupled to individual service libraries. ❓ The concrete packaging (shared dependency vs. interface contract) is still open.contentHash. ✅ Largely solved by the pipeline policy: it sits at the bottom of the pipeline and already holds the final serialized request body, so it can feed those exact bytes toacquirePolicyTokenasoperation.contentand replay the identical bytes on retry — no re-serialization, no drift. (The user-facing models never need to expose the raw JSON.)acquirePolicyToken. ✅ Reuse the client's own endpoint (the same ARM host as the guarded PUT), so Public/Gov/other clouds are handled automatically. Use a default API-version with the ability for the user to override it.Status
Next steps
References
acquirePolicyTokenREST API referenceinvoke-policy-rest-flow.md.