Workload Identity for Azure Container Registry
Workload Identity connects a private Azure Container Registry (ACR) to AV-hosted container scanning without handing AlertaVuln a password, token, or service-principal secret to store. Instead, AlertaVuln proves who it is with a short-lived token it signs itself, your Azure tenant checks that token against a trust relationship you configure, and Azure hands back a scoped ACR pull token that expires in minutes. There is nothing long-lived on either side to leak, rotate, or accidentally commit.
This guide walks through the trust setup in Azure and the matching connection in AlertaVuln.
Why Workload Identity beats a stored credential
Section titled “Why Workload Identity beats a stored credential”With a Basic or Token connection, AlertaVuln stores an encrypted secret and sends it to your registry on every pull. With Workload Identity:
- No secret is stored. AlertaVuln holds a private signing key it uses to mint its own short-lived tokens - never a copy of anything issued by your tenant.
- Nothing to rotate on your side. There is no password or PAT that expires, gets leaked in a log, or needs a reminder to renew. The trust relationship is configuration, not a secret.
- Tokens are scoped and short-lived. Each scan mints a fresh token that is valid for minutes and, with the default pull scope, can only read the one repository being scanned.
- You control the trust, and you can revoke it instantly. Deleting the
federated credential or the
AcrPullrole assignment in Azure cuts AlertaVuln off immediately - no key to rotate or connection to update on the AlertaVuln side.
Before you start
Section titled “Before you start”You will need:
- An Azure account with permission to create an app registration (or a
user-assigned managed identity) and to assign roles on the registry
(
OwnerorUser Access Administratoron the registry’s scope). - The Azure CLI
installed and signed in (
az login). - Organisation-admin access in AlertaVuln to manage registry connections.
The examples below use az and an Entra app registration. A user-assigned
managed identity works the same way if you prefer one - swap
az ad app create / az ad app federated-credential create for
az identity create / az identity federated-credential create; the trust
values (issuer, subject, audience) and the AcrPull role assignment are
identical either way.
Set up the trust
Section titled “Set up the trust”-
Create the identity AlertaVuln will use.
Terminal window az ad app create --display-name "AlertaVuln ACR access" --query appId --output tsvThis prints the Application (client) ID - copy it, you will paste it into AlertaVuln in a later step. Then create the matching service principal, which an app registration needs before it can receive an Azure role assignment:
Terminal window az ad sp create --id <client-id>Also note your Azure tenant ID:
Terminal window az account show --query tenantId --output tsvThis reports the tenant your CLI session is signed into. The Tenant ID you enter in AlertaVuln must be the tenant that actually hosts the identity and the registry, which matters if you manage multiple tenants and your CLI session happens to be signed into a different one.
-
Grant it
AcrPullon your registry.Terminal window ACR_ID=$(az acr show --name <registry-name> --resource-group <resource-group> --query id --output tsv)az role assignment create \--assignee <client-id> \--role AcrPull \--scope "$ACR_ID"AcrPullis a registry-level Azure RBAC role - Azure has no built-in role scoped to a single repository, so this grant always covers the whole registry regardless of which pull scope you choose in AlertaVuln (see the pull-scope choice below). -
Create the registry connection in AlertaVuln.
In the web app, open Settings -> Registry Connections and select Add registry:
Field Value Registry Your ACR login server, for example myregistry.azurecr.ioAuthentication Workload Identity Cloud provider Azure Tenant ID The tenant ID from step 1 Client ID The Application (client) ID from step 1 Pull scope Per-repository (least privilege) (default) or Registry-wide Select Add registry. AlertaVuln creates the connection and shows a trust panel on it with two values you need for the next step: an Issuer URL and a Subject identifier. Both are safe to copy into Azure - neither is a secret.
-
Add the federated identity credential in Azure, using the exact Issuer URL and Subject identifier from AlertaVuln’s trust panel:
federated-credential.json {"name": "alertavuln-acr-pull","issuer": "https://oidc.alertavuln.com","subject": "org:<your-org-id>:conn:<your-connection-id>","audience": ["api://AzureADTokenExchange"]}Terminal window az ad app federated-credential create \--id <client-id> \--parameters @federated-credential.jsonCopy
issuerandsubjectfrom the trust panel rather than retyping them -issuerishttps://oidc.alertavuln.comfor the hosted AlertaVuln service, andsubjectis unique to this one connection (org:{organisationId}:conn:{connectionId}), so a mistyped character breaks the trust silently until a scan runs.audiencemust be exactlyapi://AzureADTokenExchange- that is fixed by Azure, not something you choose.
That’s it - the next scan that needs this registry mints a token through the trust you just set up. There is nothing further to configure or renew.
The pull-scope choice
Section titled “The pull-scope choice”Pull scope controls how narrowly the token AlertaVuln requests from ACR at
scan time is scoped. It does not change the Azure role assignment - that is
always AcrPull on the whole registry, since Azure has no narrower built-in
role.
| Pull scope | Token requested at scan time | Best for |
|---|---|---|
| Per-repository (default) | Scoped to the single repository being scanned | Most setups - a token minted for one scan cannot read any other repository, even though the underlying role grant covers the whole registry |
| Registry-wide | Scoped to the whole registry | Registries where scans target many repositories and you would rather manage one broad grant than think about scope per scan |
There is no way to edit a connection’s pull scope after it is created. To change it, delete the connection and create a new one with the scope you want. The new connection gets a new Subject identifier, so you must also update the federated identity credential in Azure to match it before scans against that registry will work again - the old federated credential (pointed at the deleted connection’s subject) will no longer be honored.
How it works at scan time
Section titled “How it works at scan time”AlertaVuln never touches your registry credentials directly. For each scan it:
- Mints a short-lived JSON Web Token signed with its own key, identifying the
connection (
org:{organisationId}:conn:{connectionId}). - Exchanges that token with Microsoft Entra for an Azure AD access token, using the federated credential you configured.
- Exchanges the Azure AD token with your registry for a scoped ACR pull token, following the pull-scope choice above.
Every step’s token is short-lived. The ACR pull token is held in memory and may be reused for other scans against the same connection until it is close to expiry, rather than being re-minted for every single scan. When a scan runs, that token is written to a transient, scan-scoped Docker config file so the scanner can authenticate the pull; that file is removed again once the scan finishes. Nothing from any of these steps outlives its own expiry or is kept around longer than the scan that used it.
Troubleshooting
Section titled “Troubleshooting”| The scan’s error message | Likely cause | Fix |
|---|---|---|
Registry authentication fails at the AAD exchange step (aad-token), typically HTTP 401 |
The Subject identifier in your federated credential doesn’t exactly match the connection’s, or the Tenant ID / Client ID entered in AlertaVuln doesn’t match the identity you created | Re-copy the Subject identifier and Tenant ID from AlertaVuln’s trust panel into the federated credential and connection, without retyping either by hand |
| Same failure, right after you first add the federated credential | The federated credential’s audience isn’t exactly api://AzureADTokenExchange (a common typo is a trailing slash or a different casing) |
Delete and recreate the federated credential with audience set to exactly api://AzureADTokenExchange |
Registry authentication fails at the ACR exchange or token step (acr-exchange / acr-token), commonly HTTP 401 or HTTP 403 |
The AAD trust succeeded, but the identity has no AcrPull role (or it was granted at the wrong scope) |
Grant AcrPull to the identity’s client ID, scoped to the registry’s resource ID (step 2 above) |
A similar 401/403 appears immediately after granting AcrPull, then clears on a later scan with no other changes |
Azure role assignments can take a few minutes to propagate | Wait a few minutes and re-run the scan; nothing else needs changing |
| “the connection’s registry host is not an azurecr.io host” | The Registry field on the connection isn’t your ACR login server | Edit the connection’s Registry value to the exact *.azurecr.io login server (not a custom domain or a repository path) |
| “a per-repository connection requires a target repository” | Rare - a per-repository connection was used for a scan that has no specific image reference to scope the token to | Scan a specific image reference, or switch the connection to the registry-wide pull scope |
If none of these match, check that the app registration (or managed identity)
still exists and its federated credential and AcrPull role assignment are
both still present - either being deleted breaks the trust the same way as a
misconfiguration.
See also
Section titled “See also”- Container image scanning - what container scanning covers
av image scan- the CLI-driven path, for scans you run yourself with your own registry credentials- Continuous monitoring - keep a scanned image re-checked as new CVEs land