azure-keyvault-emulator

v0.7.0 Three defects in rows the ledger already graded green, found by pointing Microsoft's Python SDK at them →

Point a real Azure SDK at a vault on your laptop.

Secrets, keys and certificates, served at Azure Key Vault's own wire protocol from a single Go binary. Real RSA and EC keys doing real signing, encryption and wrapping. Real X.509 issuance. Nothing pretended, and nothing that pretends is graded as though it were.

Authentication is not a pass-through. A tokenless call gets the real401 challenge, and the token the SDK brings back is verified against entra-emulator's JWKS: signature first, then issuer, audience and expiry. DefaultAzureCredential walks the same two steps here that it walks in production, so your tests exercise the credential path and not just the storage path.

Every number on this page is counted from this repository's own parity ledger and witness manifest when the site is built, including the claims that still rest on our own tests alone.

capability claims graded real
proved by a packaged Microsoft SDK in CI
proved by Azure's Go SDK in process
resting on our own tests alone
CI jobs named as witnesses
rows ruled out of scope

The problem this solves

A vault is the first dependency an application acquires and the one it can least easily fake. Three costs follow, and the third is the expensive one.

A vault nobody can give you

Working on secrets needs a vault, a role assignment on it, a principal to hold that role, and someone with rights to create all three. Purge protection and soft-delete retention are decisions made once, per vault, by somebody else. So the code that reads secrets is the code that never gets exercised locally.

One binary, a seeded vault, and a reset in one call

Stand-ins that skip the handshake

Most local Key Vault substitutes accept any token, or serve a token endpoint that agrees with anything. The credential path is then the one part of the system your suite has no opinion about, and it is the part that fails in the deployed environment.

Real RS256 validation against a real issuer's JWKS

Fakes you wrote both ends of

A hand-rolled vault agrees with the client written against it. Ours did too: our own tests parsed nextLink rather than following it, so a pager that no Microsoft SDK could walk stayed green through four releases. Microsoft's Python SDK found it, and two more beside it, in rows already graded real.

docs/release-notes/v0.7.0.md
The handshake, in full.

Nothing in this sequence is shortened for the emulator. The vault does not know how to mint a token, and the issuer does not know what a vault is.

Azure SDK (SecretClient / azsecrets)
   1. unauthenticated probe
      -> azure-keyvault-emulator answers 401 WWW-Authenticate: Bearer
         authorization="{entra authority}", resource="https://vault.azure.net"
   2. SDK acquires a token from the advertised authority
      -> entra-emulator mints aud=https://vault.azure.net
   3. SDK retries with the token
      -> signature checked against entra's JWKS, then iss, aud, exp -> 200

How a claim earns its grade

Real Azure Key Vault is the sole reference, approached from two directions: Microsoft's published REST documentation says what the service does, and Microsoft's own client libraries, pinned and run in CI, witness that this emulator does the same. Every row graded real names the test or CI job that proves it, and CI fails when one goes missing.

Witness tierWhat it meansClaims credited
ci:A CI job driving a packaged Microsoft client from its own package manager, over a network, against the running emulator. The strongest evidence here, because nothing in the loop is ours.
sdk:A Go test in which Azure's own client does the talking:azsecrets, azkeys,azcertificates over the vault's wire. Third-party evidence, but in process rather than a released package over a network, so it ranks below ci:.
go:Our own Go tests: real HTTP, real signed JWTs, real authorization, but our client on both ends of our own implementation. It proves the emulator agrees with itself.

Each claim is credited once, by its strongest witness, so the tiers above add up to the whole ledger. Counting citations instead would flatter every row, because one claim often carries four or five witnesses.

Microsoft's own SDKs witnessed

Python, JavaScript and .NET suites run the same surface on three operating systems each: challenge authentication, the secret lifecycle through soft-delete and purge, RSA cryptography with a tamper negative, on-demand rotation, a key_ops refusal, and a self-signed certificate operation polled to completion.

ci:python-sdk · ci:js-sdk · ci:dotnet-sdk

The Go SDK as a borrowed oracle witnessed

The Go suite reconstructs an *rsa.PublicKey from the returned JWK and verifies an SDK-produced signature outsidethe SDK, with a tampered-signature negative alongside. A round trip that only checks itself would pass on two broken halves.

sdk:TestAzkeysRSALifecycleAndCrypto

A vault at its real hostname witnessed

One job reaches the vault at https://contoso.vault.azure.netwith the SDK's challenge-resource check left on, which the localhost suites have to disable, and verifies the TLS chain rather than skipping it. The wildcard SAN is load-bearing: remove it and the suite fails by name.

ci:host-routed

The rest of the family witnessed

A chain job stores a service principal's secret in the vault, reads it back with a managed-identity token that carries no credential, uses that secret to authenticate to entra-emulator, and calls fabric-emulator with the result. Three real processes, and a wrong secret breaks it exactly where Azure would.

ci:chain · ci:arm-chain · ci:az-cli

What it does

Grades and evidence lines below are the ledger's own, not a summary written for this page. The full table, with the reasoning for each row, is the parity map.

Secrets real

Set, get, list, list versions and get by version, with a new 32-hex version minted on every write. enabled is enforced;nbf and exp behave as real Key Vault does. Backup produces an opaque sealed blob restorable only by the same instance, which is the honest analogue of the same-geography rule.

ci:python-sdk · ci:js-sdk · ci:dotnet-sdk

Keys, doing real cryptography real

RSA 2048/3072/4096 and EC P-256/P-384/P-521 generated withcrypto/rsa and crypto/ecdsa. Sign and verify across RS, PS and ES families, with Azure's raw r‖sencoding. Encrypt, decrypt, wrap, unwrap, JWK import, rotation on the emulator clock, and key_ops enforced with a403.

sdk:TestAzkeysECSignVerify · ci:python-sdk

Certificates real

Self-signed issuance through x509.CreateCertificate, PKCS#12 and PEM import, a real PKCS#10 CSR for a named issuer, and a merge that verifies the signed leaf's public key against the pending key before accepting it. The linked key and secret materialise under the same name, as they do in Azure.

sdk:TestAzcertificatesSelfSignedLifecycle · sdk:TestAzcertificatesMerge

Challenge authentication real

The 401 carries Key Vault's own AKV10000code and points at entra-emulator's authority. Signatures are checked before any claim is read; issuer, audience and validity follow, on a clock you can move. Several trusted issuers are allowed at once, each validating against its own JWKS.

ci:host-routed · ci:chain

Soft delete and recovery real

Delete, list deleted, recover and purge as an enforced state machine, with retention validated between 7 and 90 days and name reuse refused with 409 while an object is still deleted. Retention expiry is genuinely clock-driven rather than announced.

sdk:TestAzureSDKChallengeFlowAndSecretLifecycle · ci:js-sdk

Authorization, governed by ARM real

By default the sibling arm-emulator governs:az role assignment create andaz keyvault set-policy write over ARM's real wire and this data plane enforces the result, no-assignment-means-no-access included. A group grant authorises any caller carrying that group.

ci:arm-chain · ci:az-cli

Vault addressing and TLS real

The Host header selects the vault, object identifiers are always rendered canonically whatever the listen address, and the self-signed material covers *.vault.azure.net with a stable fingerprint across restarts. Paging is capped at 25 with anextLink the SDK pagers can actually follow.

ci:host-routed · go:TestVaultNameResolution

Secure Key Release emulated

exportable is enforced and a non-exportable key refuses release, the release policy round-trips, and the JWS is genuinely signed. There is no attestation, because there is no enclave to attest. That is why the row is amber and not green.

docs/parity.md, Keys section

Issuance by a real CA bring your own engine

The emulator generates the key and a real CSR and merges whatever chain your CA signs. Attach a real CA and the X.509 is real; attach nothing and this row is a contract only. Live DigiCert and GlobalSign integrations are out of scope.

go:TestCertMergeFlow · sdk:TestAzcertificatesMerge

Testing controls no Azure equivalent

A controllable clock at /_emulator/clock makes token expiry and retention windows deterministic, fault injection forces429 with Retry-After or 500 for the next few requests, and a read-only portal shows vault state in a browser. These exist for tests and have no Key Vault counterpart.

http://localhost:8444/_emulator/portal/

Honest status

The counts below come from the same manifests as the flattering ones at the top of the page, so they cannot be the half that goes stale.

Nothing here has ever been run against a live Azure Key Vault.

There is no differential test against a real vault, and no credentials for one. The reference is Microsoft's published REST documentation plus Microsoft's own client libraries, pinned and executed in CI. Those libraries are strong evidence about the wire, and no evidence at all about behaviour they never exercise. Read every grade on this page as "agrees with the documentation and with Microsoft's clients", not as "checked against Azure".

claims are still witnessed by our own tests alone.

Named, because a claim proved only by the code that implements it proves that the emulator agrees with itself: . Everything else in the ledger is carried by a Microsoft client.

rows are not graded real, and say so.

Secure Key Release is emulated, since no enclave exists to attest. Assignment through the /_emulator control surface is emulated, because it is real enforcement over a wire that is not ARM's. Certificate issuance by a real CA is contract-only until you attach a CA.

Out of scope, deliberately

The line is drawn around the vault. Everything below is the infrastructure surrounding it, which no process on a laptop can honestly provide. There are no 501 stubs in this emulator: an unimplemented feature has no route, so a client sees a404.

Azure featureStatusWhy
ARM control planenot implementedCreating and deleting vaults is a different plane. This serves the data plane a vault client talks to; the sibling arm-emulator serves the other one.
Managed HSMnot implementedA distinct service and a hardware trust boundary. Returning software material under an HSM name would tell a caller their keys are hardware-backed when they are not.
Private endpoints, firewall, network ACLsnot implementedNetwork topology, not vault behaviour.
Customer-managed-key encryption of the vault itselfnot implementedInfrastructure level.
Real CA issuance (DigiCert, GlobalSign)not implementedNeeds a real CA. The bring-your-own-engine CSR and merge path above is the substitute.
Diagnostic logs, Event Grid, metricsnot implementedAn Azure Monitor surface, not a vault surface.
Intentionally insecure, and not a security boundary.

Publicly known seeded principals and secrets, self-signed TLS, an open/_emulator control surface that can move the clock and rewrite permissions. It exists so a laptop can serve a vault that behaves closely enough to develop against. Run it on localhost. Never point real secrets at it.

Get it

A distroless image with a built-in healthcheck, or a static binary for six platform pairs across Linux, macOS and Windows. Pure Go, no cgo, no runtime dependency.

brew install calvinchengx/tap/azure-keyvault-emulator      # macOS and Linux
winget install calvinchengx.azure-keyvault-emulator        # Windows
go install github.com/calvinchengx/azure-keyvault-emulator/cmd/azure-keyvault-emulator@latest

docker run -p 8444:8444 ghcr.io/calvinchengx/azure-keyvault-emulator:latest

The vault on its own has no issuer to trust, so the useful unit is the pair. The repository'sdocker-compose.yml wires the challenge to entra-emulator's authority and points token validation at its JWKS, with no configuration to write. Every install method is ininstallation, and the prerequisites per platform are in platform setup.

Run it

Three verbs, the same on Linux, macOS and Windows.

make doctor   # toolchain and docker context check, run this first
make up       # entra-emulator :8443 and keyvault-emulator :8444
make status   # is the pair usable? containers, endpoints, the 401 challenge

Then point a real client at it. Only the vault URL and the authority change; the seeded application values below are public development credentials from entra-emulator's deterministic seed, so a fixture written today still resolves next year.

import os
from azure.identity import DefaultAzureCredential, DefaultAzureCredentialOptions
from azure.keyvault.secrets import SecretClient

os.environ["AZURE_TENANT_ID"]      = "6f89cf12-978b-4d23-ac18-9ef0c127cf87"
os.environ["AZURE_CLIENT_ID"]      = "00d88624-f0d7-46f6-a641-6232c2608928"
os.environ["AZURE_CLIENT_SECRET"]  = "daemon-app-secret"
os.environ["AZURE_AUTHORITY_HOST"] = "https://localhost:8443"

cred = DefaultAzureCredential(
    DefaultAzureCredentialOptions(disable_instance_discovery=True))

client = SecretClient(
    vault_url="https://localhost:8444",
    credential=cred,
    # localhost is not *.vault.azure.net, so the challenge-resource check is
    # relaxed here. Addressing the vault by its real hostname does not need it.
    disable_challenge_resource_verification=True,
    connection_verify=False,   # self-signed certificate, local only
)

client.set_secret("db-password", "hunter2")
print(client.get_secret("db-password").value)   # -> hunter2

The Go, JavaScript and .NET clients work the same way. The handshake is described in detail underauthentication, the family wiring under family integration, and how all of this is tested under testing.

Documentation