Control plane (Entra ID + ARM)

The Entra ID (AAD) and Resource Manager (ARM) emulators let the Azure CLI and the Azure SDKs treat localaz as a custom Azure cloud: register it once, az login with a service principal, and data-plane commands (such as az monitor log-analytics query or az servicebus) are routed to localaz.

Endpoints

ServiceURLProtocol
Entra ID (AAD)https://127.0.0.1:10006HTTP / REST (HTTPS)
Resource Manager (ARM)https://127.0.0.1:10007HTTP / REST (HTTPS)

State is in-memory and transient: a single fixed subscription and tenant, plus resource groups and generic resources created at runtime.

Configuration

FlagEnvironment variableDefaultDescription
-aad-addrLOCALAZ_AAD_ADDR:10006Entra ID listen address
-arm-addrLOCALAZ_ARM_ADDR:10007Resource Manager listen address
-arm-cloud-nameLOCALAZ_ARM_CLOUD_NAMElocalazCloud name in the ARM metadata document
-advertise-hostLOCALAZ_ADVERTISE_HOST127.0.0.1Host clients use to reach the control plane
-tls-certLOCALAZ_TLS_CERT(unset)PEM certificate for the control plane
-tls-keyLOCALAZ_TLS_KEY(unset)PEM private key for the control plane
-tls-autoLOCALAZ_TLS_AUTO(off)Generate a self-signed certificate at startup

TLS is required. MSAL and azure-core refuse to send bearer tokens over plain HTTP. Either supply -tls-cert/-tls-key or use -tls-auto, which writes <data>/tls/localaz.crt and localaz.key. When TLS is enabled, the Monitor, AAD and ARM services serve HTTPS.

Supported operations

Entra ID (AAD)

OperationREST surface
OpenID configurationGET /{tenant}/.well-known/openid-configuration
JWKS (signing keys)GET /{tenant}/discovery/keys
TokenPOST /{tenant}/oauth2/token (and /v2.0/token)

Tokens are minted as hand-rolled RS256 JWTs and verify against the published JWKS. Client id, secret and assertions are accepted but not validated.

Resource Manager (ARM)

OperationREST surface
Cloud metadataGET /metadata/endpoints
List tenants / subscriptionsGET /tenants, GET /subscriptions
Resource groupsGET/PUT/DELETE /subscriptions/{id}/resourcegroups/{name}
Generic resourcesGET/PUT/DELETE .../providers/{ns}/{type}/{name}
Name availabilityPOST .../providers/{ns}/checkNameAvailability
List keysPOST .../providers/{ns}/.../authorizationRules/{rule}/listKeys

A generic resource-provider surface stores any .../providers/{ns}/{type}/{name} body and echoes it back with a terminal provisioningState=Succeeded. This is exercised end to end by az servicebus (Microsoft.ServiceBus).

Not yet implemented: token signature/scope validation and RBAC, device-code/interactive logins, long-running-operation polling, and subscription/tenant management.

Register localaz as a cloud

Run localaz with -tls-auto, then trust the generated certificate and register the cloud. The registered cloud name must equal -arm-cloud-name.

export REQUESTS_CA_BUNDLE=<data>/tls/localaz.crt
export SSL_CERT_FILE=<data>/tls/localaz.crt
export ARM_CLOUD_METADATA_URL=https://127.0.0.1:10007/metadata/endpoints

az cloud register -n localaz \
  --endpoint-active-directory https://127.0.0.1:10006/ \
  --endpoint-resource-manager https://127.0.0.1:10007/ \
  --endpoint-management https://127.0.0.1:10007/ \
  --endpoint-active-directory-resource-id https://127.0.0.1:10007/ \
  --suffix-storage-endpoint 127.0.0.1 --skip-endpoint-discovery
az cloud set -n localaz

# Sign in with the ADFS authority so MSAL skips public instance discovery.
az login --service-principal -u <app-id> -p <any-secret> --tenant adfs

The emulator does not validate the client id, secret or tokens, so the exact credential values are not sensitive in this context. Once signed in, az account show, az group create/list/delete, az servicebus ... and az monitor log-analytics query all route to localaz.

Example: Go SDK

cred, _ := azidentity.NewClientSecretCredential("adfs", "<app-id>", "<secret>",
    &azidentity.ClientSecretCredentialOptions{
        ClientOptions: azcore.ClientOptions{Cloud: localCloud},
    })

client, _ := armresources.NewResourceGroupsClient("<subscription-id>", cred,
    &arm.ClientOptions{ClientOptions: azcore.ClientOptions{Cloud: localCloud}})
client.CreateOrUpdate(ctx, "rg1", armresources.ResourceGroup{Location: to.Ptr("local")}, nil)

where localCloud is a cloud.Configuration pointing its ActiveDirectoryAuthorityHost at https://127.0.0.1:10006/ and the ARM endpoint at https://127.0.0.1:10007/.