Skip to content

Parity — v0.1.1

How the emulator’s surface maps to real ARM (as specified in Azure/azure-rest-api-specs and documented at learn.microsoft.com/rest/api/resources), and — the point of this table — whether real work happens or just the API shape.

This emulator exists to make the family’s authorization real: role assignments written over ARM’s wire by real clients, enforced by the sibling data planes. It implements the slice of ARM that serves that goal, and says plainly what it leaves alone.

Meaning
🟢 RealGenuine work over ARM’s real wire: real tokens verified, real resource-ID semantics, real inheritance — an unmodified Microsoft client cannot tell the difference.
🟡 EmulatedFaithful contract + persisted state, but simplified behind it.
🔴 Not implementedAbsent (404 / NoRegisteredProviderFound).
ARM featureEmulatorType
ARM-audience bearer tokens (RS256 / JWKS / issuer / expiry)Signature verified before any claim is read; https://management.azure.com audience, exp/nbf on the emulator’s controllable clock; multiple trusted issuers🟢 Real
401 + WWW-Authenticate challenge with AuthenticationFailedThe real shape; unmodified azidentity walks it🟢 Real
Resource-ID grammar (/subscriptions/{s}/resourceGroups/{g}/providers/{ns}/{type}/{n})Parsed as ARM parses it, including a provider path appended to a scope that itself contains one🟢 Real
api-version required and validated (date-based, -preview)Missing → MissingApiVersionParameter; malformed → InvalidApiVersionParameter🟢 Real
ARM error envelope + x-ms-request-id / x-ms-correlation-request-idOn every response🟢 Real
Cloud discovery (GET /metadata/endpoints, anonymous)The real document, served without a token as ARM does — it is what az cloud register and SDK cloud-discovery fetch first, and it points them at entra-emulator🟢 Real
Behaviour differentiated by api-versionAny valid version behaves identically🔴 Not implemented
ARM featureEmulatorType
Tenants + subscriptions discovery (/tenants, /subscriptions)Served as real ARM does; one seeded subscription, unknown ids SubscriptionNotFound🟡 Emulated
Resource groups CRUD + tags (case-insensitive names, PUT-as-upsert)Real semantics, persisted🟢 Real
Asynchronous group delete (202 + Location polling)Completes synchronously (200/204) — terminal shapes SDK pollers accept🟡 Emulated
Tracked resources of arbitrary providers, Microsoft.Resources/deployments🔴 Not implemented
ARM featureEmulatorType
Built-in role definitions with their real GUIDs and documented data actionsSeeded: the Key Vault data-plane roles plus Owner/Contributor/Reader; get by GUID, list, $filter=roleName eq '…'🟢 Real
Role assignments: PUT / GET / DELETE at any scopeReal CRUD, persisted, ARM’s status codes (201, 200, idempotent 204)🟢 Real
Duplicate (scope, role, principal) → RoleAssignmentExistsEnforced at the storage layer🟢 Real
Assignment to a nonexistent role definition refusedRoleDefinitionDoesNotExist rather than a dangling reference🟢 Real
Scope inheritance on read (an assignment applies to every scope beneath it)Real, on segment boundaries and case-insensitively🟢 Real
$filter=atScope() and principalId eq '…'Honoured, as the CLI sends them🟢 Real
Assignments to a group principal (principalType: Group)Stored and served like any other; a member’s token carries the group in its groups claim (entra-emulator ≥ v0.3.1) and the data plane resolves membership — a user never named in the assignment is authorized through it🟢 Real
Custom role definitions (create/update)Built-ins only🔴 Not implemented
Deny assignments, PIM eligibility🔴 Not implemented
ABAC condition evaluationStored and returned verbatim; not evaluated🟡 Emulated
ARM featureEmulatorType
Vault CRUD (Microsoft.KeyVault/vaults) with tags, SKU, vaultUriReal semantics, persisted; creation requires an existing resource group, as ARM requires🟢 Real
accessPolicies + the add/replace/remove operationReal: add merges by objectId, replace swaps the list, remove drops by objectId — what az keyvault set-policy / delete-policy call🟢 Real
enableRbacAuthorization, enablePurgeProtection, soft-delete settingsStored, returned, and fed to the data plane — RBAC mode makes the vault ignore access policies, as real Key Vault does🟢 Real
Asynchronous vault create (202 + polling)Completes inline with a terminal provisioningState — the shape SDK pollers accept🟡 Emulated
Private endpoints, network ACLs, deleted-vault recovery🔴 Not implemented
FeatureEmulatorType
GET /_family/authorization?scope=… — effective assignments + their dataActionsThe only non-ARM surface here, deliberately: Azure’s internal ARM→data-plane propagation is not public wire, so its shape is ours. Thin by design — it reports assignments and role dataActions verbatim, leaving each data plane to map them onto its own operations🟡 Emulated (by necessity)

Emulator-only (no ARM equivalent — these exist for testing)

Section titled “Emulator-only (no ARM equivalent — these exist for testing)”
FeaturePurpose
Clock control (/_emulator/clock)Freeze/advance/offset — makes token expiry deterministic
Fault injection (/_emulator/faults)Force 429 + Retry-After or 500, to exercise SDK retry paths

Ecosystem conformance: real clients as witnesses

Section titled “Ecosystem conformance: real clients as witnesses”
Real client (pinned)Surface exercisedStatus
armresources (Azure Go SDK)Resource groups: create/get/list/delete, tags, 404s🟢 CI test
armauthorization (Azure Go SDK)Role definitions (list + $filter + get-by-id), role assignments (create/get/list/delete), duplicate conflict, inheritance, atScope()🟢 CI test
armkeyvault (Azure Go SDK)Vault create/get/list/delete, access-policy add and remove🟢 CI test
azidentity (ClientSecretCredential, custom cloud)The ARM-audience token path against an in-process real entra-emulator🟢 CI test
The authorization chain (entra → ARM assignment → Key Vault data plane)A role assignment written over ARM flips the vault from 403 to authorized, revocation flips it back, and an access policy grants it again — three real processes🟢 CI arm-chain (in azure-keyvault-emulator)
az CLI via az cloud registerThe family registered as a cloud: login, group/vault create, role assignment create+delete, set-policy — asserted against the Key Vault data plane🟢 CI az-cli (in azure-keyvault-emulator)
Python / JS / .NET management SDKsPlanned (P2)🔴 Not wired yet

Every 🟢 claim names its witness in witnesses.json, enforced by scripts/check_witnesses.py --strict in CI — the same discipline as the sibling emulators.

Scope boundary: the authorization slice, not all of ARM

Section titled “Scope boundary: the authorization slice, not all of ARM”
Azure featureWhy out of scope
Arbitrary resource providers (compute, network, storage, …)This emulator serves the family’s data planes; providers arrive when a sibling needs one
Template/Bicep deployments (Microsoft.Resources/deployments)A large engine of its own; a later phase if the family needs it
Management groups, multiple subscriptions, cross-tenantDirectory topology, not authorization behaviour
Azure Policy, Activity Log, Resource Graph, locksSeparate services layered on ARM
PIM / just-in-time elevationRequires an approval workflow and directory state no localhost process holds

98.9%, with a CI floor at 98%. Every reachable statement is covered, including the ARM error branches (tables dropped or BEFORE DELETE triggers fired under live handlers), the row-scan failures (SQLite’s dynamic typing lets a text value sit in an INTEGER column), the TLS persistence failures, func main itself (re-executed as a subprocess), and the token paths where a signature verifies but the payload does not decode.

What remains uncovered is only what cannot run unless the standard library fails: crypto/rand erroring (inside NewGUID and certificate generation) and sql.Open rejecting a driver name compiled into the binary. Reaching those would mean adding mutable global seams to production crypto code, so they stay honestly uncovered rather than faked.