Files
awesome-copilot/skills/azure-container-registry-cli/references/auth-and-security.md
T
Adrien Clerbois 45305f1602 Add azure-container-registry-cli skill (#2450)
* 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>
2026-07-30 11:52:42 +10:00

7.5 KiB

Authentication & Security

Table of Contents


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 login tokens are valid for 3 hours; re-run on expiry.
  • Resolve the login server with az acr show --name {registry} --query loginServer --output tsv rather 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.