Where Armite differs from Azure
Every deliberate difference, grouped by what you touch. Tags: simplification (modelled, less faithfully), deferred (not modelled yet), environment (a consequence of running locally), accepted limitation (unverified against a live subscription), invention (something Azure has no equivalent for).
Environment
- Hostnames and port.
management.localhost:8443,login.localhost:8443and{vault}.vault.localhost:8443instead of Microsoft's hosts on 443. Every client needs endpoint configuration;*.vault.localhostresolves under systemd-resolved but needs a hosts entry per vault on WSL2, macOS and Windows. environment - Private CA. Clients trust
~/.armite/ca.pemexplicitly. environment - Instance discovery must be disabled in every client; the authority is not in Entra's instance metadata and MSAL would otherwise ask
login.microsoftonline.com(AADSTS50049). environment - IMDS is plain HTTP on
127.0.0.1:8081, reached throughAZURE_POD_IDENTITY_AUTHORITY_HOST, not the link-local169.254.169.254. environment - One process, fixed world. One tenant, one subscription, two principals. State persists across restarts in a local, encrypted journal (see persistence.md), not in a database: one server per state directory. simplification
Identity
- Grants: client credentials and IMDS only; no interactive, device-code or authorization-code flows, so
az loginworks only with--service-principalor--identity. deferred - One static RSA signing key with a stable
kid; no rotation. simplification - Minimal claims:
oid,tid,aud,iss,exp,appid; no groups, roles orxms_*claims. simplification - IMDS tokens are v2-shaped (real IMDS issues v1); the envelope keeps IMDS's string-typed
expires_in. simplification - No app registrations or consent: any configured client with the matching secret gets a token for any audience it asks for. simplification
- No revocation or continuous access evaluation. deferred
- ARM accepts two audiences:
https://management.azure.com(az, SDKs) and its own originhttps://management.localhost:8443(Terraform's go-azure-sdk derives the audience from the endpoint). Real ARM acceptsmanagement.azure.comandmanagement.core.windows.net. accepted limitation
RBAC
- Built-in roles are a snapshot of the real 934 definitions (captured 2026-08-30); definition IDs are tenant-level. simplification
- A principal is one object ID: no group expansion, no distinction between user, service principal and managed identity. simplification
- Not evaluated: ABAC conditions, deny assignments, PIM eligible assignments, classic administrators. deferred
- Custom role definitions cannot be created; the built-in set is returned at every scope. deferred
- Assignment principals must be one of the configured identities; anything else is
PrincipalNotFound. Assignment names are not validated as GUIDs. simplification $filtersupport is the subset az uses:roleName eq,type eq,atScopeAndBelow(),atScope(),principalId eq,assignedTo(), combinable withand. simplification- No Microsoft Graph:
az ad …fails, andaz role assignment list -o tablefails because the table wants principal names; use-o jsonor--query. deferred
ARM
api-versionis required but any well-formed value is accepted; real ARM enforces per-provider version sets. simplification- Operations complete synchronously; SDK and Terraform pollers accept that. The one long-running operation, purging a deleted vault, is reported as already finished. simplification
- Locations are stored verbatim with no region catalogue; real ARM canonicalises them. simplification
GET /subscriptionsreturns the one subscription to every authenticated principal. simplification- The resource envelope carries
id,name,type,location,tagsandproperties; nosystemData,etag,sku,identity,plan,zonesormanagedBy. simplification - Resource group PATCH replaces the tags when the body carries them and ignores everything else (az updates with GET and PUT; Terraform PATCHes a tag change). Group delete cascades synchronously. simplification
- The provider catalogue lists Armite's namespaces as "Registered" with the types and api-versions it serves; registration is not modelled and locations are nominal. simplification
- The generic resource listing supports
$filter=resourceType eq '…'andname eq '…';$expandand$topare ignored. simplification - The endpoints document carries what az and Terraform read; real ARM's has about thirty keys.
microsoftGraphResourceIdpoints at the ARM host so az can build its Graph client. simplification - Not modelled at all: Azure Policy, activity log, resource locks, quotas and throttling, ETags. deferred
- Error codes are faithful in shape everywhere; exact codes and messages are guaranteed only where verified against live client traffic. accepted limitation
- Responses carry
x-ms-request-idand echox-ms-client-request-idon every plane; nox-ms-correlation-request-idorx-ms-routing-request-id, and a proxied blob response carries Armite's id, not Azurite's. simplification
Key Vault
- Vaults are deleted at once: no vault soft delete, no purge protection, names reusable immediately.
deletedVaultsis always empty and a purge succeeds instantly. simplification - Access policies and
networkAclsare stored and echoed, never enforced; authorization is RBAC only. Create vaults with--enable-rbac-authorization(the access-policy path needs Graph). simplification - Secrets: versions, soft delete, recover and purge are served; the retention window never expires;
nbfandexpare stored, not enforced (enabledis). Listings are one page. simplification - Not served: keys, certificates, secret backup and restore, managed HSMs,
checkNameAvailability, private endpoints. deferred - An unknown vault hostname answers 404
VaultNotFound; in Azure the name would simply not resolve. invention - Secret values are held in process memory in plain text; on disk they are encrypted with a key kept next to the CA key. accepted limitation
- The vault URI carries the port (
https://kv-demo.vault.localhost:8443/) because az builds it from the DNS suffix by concatenation. environment
Storage
- Azurite holds the blobs and validates key-based auth. Bearer requests are verified and RBAC-checked by Armite, then re-signed with the account key and forwarded; Shared Key and key-signed SAS requests pass through and Azurite validates them. simplification
- Creating, deleting or re-keying an account restarts Azurite (about two seconds, other accounts answer 502 meanwhile): Azurite reads its accounts at start-up only. environment
- Bring-your-own Azurite serves only the accounts listed in
azurite.accounts; another name is refused with 400StorageAccountNotConfigured. invention - The blob host is
blob.core.localhost, notblob.localhost: the Python SDK, hence az, finds the account name only in a host containing.blob.core.. environment PUT storageAccountsanswers 200, never 201 or 202: the account is ready synchronously and the SDK pollers accept only 200 and 202. simplification- Only the blob endpoint is advertised; the queue and table hosts answer the service-properties probe Terraform makes and refuse all else (
FeatureNotSupported);fileServices,queueServicesandtableServiceson the management plane return defaults and echo PUTs. deferred - 403 messages carry the operation, action, scope and caller after Azure's standard first line. invention
- Unknown blob operations are refused (400
UnsupportedOperation) rather than forwarded: batch, immutability policies, legal holds, Data Lake paths; SAS tokens bound to request headers or parameters, or to a stored access policy with a user delegation key. deferred - User delegation keys are derived, not stored: the value is an HMAC of the key's own fields with a server secret, so a SAS can be checked without lookup. simplification
checkNameAvailabilityis authorized as…/action; Azure grants it as…/checknameavailability/read, so Reader could call it there, not here yet. simplification- Deleting an account deletes its containers upstream (best effort), so a re-created name starts empty. simplification
- Azurite's own port (
10000, loopback) stays reachable without Armite in front: anyone on the machine with an account key bypasses RBAC there, as the key would anywhere. accepted limitation - Blob data is not encrypted at rest (
state/azurite), unlike Armite's own state. accepted limitation - Listings keep Azurite's
ServiceEndpointvalue, not the account's URL; no client we ran reads it. deferred
Client caveats (not Armite's)
az keyvault update --set tags.x=yis broken in az 2.89.1 against any cloud; use the explicit flags.- The azurerm provider waits about two minutes after creating a public vault, polling its hostname; that is the provider, not Armite.