* Add azure-container-registry-cli skill * Address Copilot review: 2025 ACR changes (ABAC, zone redundancy, task network bypass) and secure credential handling * Add ACI (Azure Container Instances) to codespell ignore list * Address second Copilot review wave: DCT deprecation, purge --untagged caveat, soft-delete tiers, Catalog Lister scope, secret quoting --------- Co-authored-by: Aaron Powell <me@aaron-powell.com>
7.5 KiB
Authentication & Security
Table of Contents
- Individual Login
- Microsoft Entra RBAC Roles
- Service Principals
- Managed Identities
- AKS Integration
- Repository-Scoped Tokens
- Admin User
- Content Trust
Individual Login
# Standard login — wires Docker/Podman credentials via your az login identity
az acr login --name {registry}
# Without a Docker daemon: get an Entra access token and pipe it to docker login
LOGIN_SERVER=$(az acr show --name {registry} --query loginServer --output tsv)
az acr login --name {registry} --expose-token --query accessToken --output tsv | \
docker login $LOGIN_SERVER --username 00000000-0000-0000-0000-000000000000 --password-stdin
Notes:
az acr logintokens are valid for 3 hours; re-run on expiry.- Resolve the login server with
az acr show --name {registry} --query loginServer --output tsvrather than hardcoding it: it is usually{registry}.azurecr.io, but sovereign clouds use other suffixes and registries with a domain name label scope get a hash suffix.
Microsoft Entra RBAC Roles
The applicable data-plane roles depend on the registry's role assignment permissions mode — check it first:
az acr show --name {registry} --query roleAssignmentMode --output tsv
# LegacyRegistryPermissions -> use AcrPull/AcrPush/AcrDelete
# AbacRepositoryPermissions -> use Container Registry Repository Reader/Writer/Contributor
Legacy mode (RBAC Registry Permissions):
| Role | Permissions |
|---|---|
AcrPull |
Pull images |
AcrPush |
Pull + push images |
AcrDelete |
Delete images |
AcrImageSigner |
Sign images (content trust) |
Contributor/Owner |
Full control-plane management + push/pull |
ABAC-enabled mode (RBAC Registry + ABAC Repository Permissions): AcrPull/AcrPush/AcrDelete are not honored, and Owner/Contributor/Reader grant control-plane only. Use instead:
| Role | Permissions |
|---|---|
Container Registry Repository Reader |
Read images, tags, metadata (add ABAC conditions to scope to repositories) |
Container Registry Repository Writer |
Read + write/update |
Container Registry Repository Contributor |
Read + write + delete |
Container Registry Repository Catalog Lister |
List repositories — assign only when the identity must enumerate the catalog (e.g., az acr repository list); not needed for pull/push of known repositories |
# Get the registry resource ID
ACR_ID=$(az acr show --name {registry} --query id --output tsv)
# Grant pull access to a user, group, service principal, or managed identity
az role assignment create --assignee {principal-id} --scope $ACR_ID --role AcrPull
# List who has access
az role assignment list --scope $ACR_ID --output table
Service Principals
For CI/CD systems that cannot use OIDC/managed identity:
# Create an SP scoped to pull only
ACR_ID=$(az acr show --name {registry} --query id --output tsv)
az ad sp create-for-rbac --name {sp-name} --scopes $ACR_ID --role AcrPull
# Docker login with the SP — pipe the secret via stdin, never pass it as an argument
# (printf with a quoted variable preserves whitespace/glob characters exactly)
printf '%s' "$SP_PASSWORD" | docker login $LOGIN_SERVER --username {appId} --password-stdin
Prefer federated credentials (OIDC) over SP passwords in GitHub Actions / Azure DevOps when possible.
Managed Identities
For Azure compute (VM, App Service, Container Apps, Functions):
# Assign a system-assigned identity and grant it pull
az vm identity assign --name {vm} --resource-group {rg}
PRINCIPAL_ID=$(az vm show --name {vm} --resource-group {rg} --query identity.principalId --output tsv)
az role assignment create --assignee $PRINCIPAL_ID --scope $ACR_ID --role AcrPull
App Service / Container Apps then pull with --assign-identity + --acr-identity style flags of their own CLIs — no registry password needed.
AKS Integration
# Attach at cluster creation
az aks create --name {cluster} --resource-group {rg} --attach-acr {registry}
# Attach/detach an existing cluster (grants AcrPull to the kubelet identity)
az aks update --name {cluster} --resource-group {rg} --attach-acr {registry}
az aks update --name {cluster} --resource-group {rg} --detach-acr {registry}
# Validate the cluster can reach the registry
az aks check-acr --name {cluster} --resource-group {rg} --acr {registry}.azurecr.io
--attach-acr requires Owner or User Access Administrator on the registry. Cross-subscription attach works by passing the full ACR resource ID.
⚠️ --attach-acr assigns AcrPull, which is not honored on ABAC-enabled registries (roleAssignmentMode = AbacRepositoryPermissions). For those, assign the ABAC roles to the kubelet identity manually:
ACR_ID=$(az acr show --name {registry} --query id --output tsv)
KUBELET_ID=$(az aks show --name {cluster} --resource-group {rg} \
--query identityProfile.kubeletidentity.objectId --output tsv)
az role assignment create --assignee $KUBELET_ID --scope $ACR_ID \
--role "Container Registry Repository Reader"
# "Container Registry Repository Catalog Lister" is NOT needed for pulls —
# only add it if the identity must list repositories
Repository-Scoped Tokens
Available in all service tiers. Fine-grained, non-Entra credentials (e.g., external partners, IoT devices):
# 1. Create a scope map (actions: content/read, content/write, content/delete, metadata/read, metadata/write)
az acr scope-map create --name {scope-map} --registry {registry} \
--repository app content/read metadata/read \
--description "Pull-only access to app"
# 2. Create a token bound to the scope map
az acr token create --name {token} --registry {registry} --scope-map {scope-map}
# 3. Generate/rotate passwords (up to 2, optional expiry)
az acr token credential generate --name {token} --registry {registry} --password1 --expiration-in-days 30
# Login with the token — pipe the password via stdin, never pass it as an argument
printf '%s' "$TOKEN_PWD" | docker login $LOGIN_SERVER --username {token} --password-stdin
# Disable or delete
az acr token update --name {token} --registry {registry} --status disabled
az acr token delete --name {token} --registry {registry} --yes
Admin User
Single account, full push/pull on the whole registry, not auditable per user — keep disabled in production:
az acr update --name {registry} --admin-enabled false # recommended
az acr credential show --name {registry} # view username/passwords (if enabled)
az acr credential renew --name {registry} --password-name password2 # rotate
Legitimate uses: quick local tests, services that only accept username/password and cannot use tokens.
Content Trust (deprecated)
Docker Content Trust (DCT) is being retired: since May 31, 2026 it cannot be enabled on new registries (or on registries that never enabled it), and it will be removed entirely on March 31, 2028. Do not set up DCT — sign images with Notation (Notary Project) and store signatures as OCI artifacts instead; see "Transition from Docker Content Trust to Notary Project" in the ACR docs.
# Registries with legacy DCT only — inspect or disable the existing configuration
az acr config content-trust show --registry {registry}
az acr config content-trust update --registry {registry} --status disabled
Legacy DCT signers needed AcrImageSigner in addition to AcrPush.