Cross-registry authentication in an ACR task using an Azure-managed identity

In an ACR task, you can enable a managed identity for Azure resources. The task can use the identity to access other Azure resources, without needing to provide or manage credentials.

In this article, you learn how to enable a managed identity in a task to pull an image from a registry different from the one used to run the task.

To create the Azure resources, this article requires that you run the Azure CLI version 2.0.68 or later. Run az --version to find the version. If you need to install or upgrade, see Install Azure CLI.

Scenario overview

The example task pulls a base image from another Azure container registry to build and push an application image. To pull the base image, you configure the task with a managed identity and assign appropriate permissions to it.

This example shows steps using either a user-assigned or system-assigned managed identity. Your choice of identity depends on your organization's needs.

In a real-world scenario, an organization might maintain a set of base images used by all development teams to build their applications. These base images are stored in a corporate registry, with each development team having only pull rights.

Prerequisites

For this article, you need two Azure container registries:

  • You use the first registry to create and execute ACR tasks. In this article, this registry is named myregistry.
  • The second registry hosts a base image used for the task to build an image. In this article, the second registry is named mybaseregistry.

Replace with your own registry names in later steps.

If you don't already have the needed Azure container registries, see Quickstart: Create a private container registry using the Azure CLI. You don't need to push images to the registry yet.

Prepare base registry

For demonstration purposes, as a one-time operation, run [az acr import][az-acr-import] to import a public Node.js image from Docker Hub to your base registry. In practice, another team or process in the organization might maintain images in the base registry.

az acr import --name mybaseregistry \
  --source docker.io/library/node:15-alpine \
  --image baseimages/node:15-alpine 

Define task steps in YAML file

The steps for this example multi-step task are defined in a YAML file. Create a file named helloworldtask.yaml in your local working directory and paste the following contents. Update the value of REGISTRY_NAME in the build step with the server name of your base registry.

version: v1.1.0
steps:
# Replace mybaseregistry with the name of your registry containing the base image
  - build: -t $Registry/hello-world:$ID  https://github.com/Azure-Samples/acr-build-helloworld-node.git#main -f Dockerfile-app --build-arg REGISTRY_NAME=mybaseregistry.azurecr.io
  - push: ["$Registry/hello-world:$ID"]

The build step uses the Dockerfile-app file in the Azure-Samples/acr-build-helloworld-node repo to build an image. The --build-arg references the base registry to pull the base image. When successfully built, the image is pushed to the registry used to run the task.

Option 1: Create task with user-assigned identity

The steps in this section create a task and enable a user-assigned identity. If you want to enable a system-assigned identity instead, see Option 2: Create task with system-assigned identity.

Create a user-assigned identity

Create an identity named myACRTasksId in your subscription using the az identity create command. You can use the same resource group you used previously to create a container registry, or a different one.

az identity create \
  --resource-group myResourceGroup \
  --name myACRTasksId

To configure the user-assigned identity in the following steps, use the az identity show command to store the identity's resource ID, principal ID, and client ID in variables.

# Get resource ID of the user-assigned identity
resourceID=$(az identity show \
  --resource-group myResourceGroup \
  --name myACRTasksId \
  --query id --output tsv)

# Get principal ID of the task's user-assigned identity
principalID=$(az identity show \
  --resource-group myResourceGroup \
  --name myACRTasksId \
  --query principalId --output tsv)

# Get client ID of the user-assigned identity
clientID=$(az identity show \
  --resource-group myResourceGroup \
  --name myACRTasksId \
  --query clientId --output tsv)

Create task

Create the task helloworldtask by executing the following az acr task create command. The task runs without a source code context, and the command references the file helloworldtask.yaml in the working directory. The --assign-identity parameter passes the resource ID of the user-assigned identity.

az acr task create \
  --registry myregistry \
  --name helloworldtask \
  --context /dev/null \
  --file helloworldtask.yaml \
  --assign-identity $resourceID

In the command output, the identity section shows the identity of type UserAssigned is set in the task:

[...]
"identity": {
    "principalId": null,
    "tenantId": null,
    "type": "UserAssigned",
    "userAssignedIdentities": {
      "/subscriptions/xxxxxxxx-d12e-4760-9ab6-xxxxxxxxxxxx/resourcegroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myACRTasksId": {
        "clientId": "xxxxxxxx-f17e-4768-bb4e-xxxxxxxxxxxx",
        "principalId": "xxxxxxxx-1335-433d-bb6c-xxxxxxxxxxxx"
      }
[...]

Give identity pull permissions to the base registry

In this section, give the managed identity permissions to pull from the base registry, mybaseregistry.

Use the az acr show command to get the resource ID of the base registry and store it in a variable:

baseregID=$(az acr show --name mybaseregistry --query id --output tsv)

Use the az role assignment create command to assign the identity the acrpull role to the base registry. This role has permissions only to pull images from the registry.

az role assignment create \
  --assignee $principalID \
  --scope $baseregID \
  --role acrpull

Proceed to Add target registry credentials to task.

Option 2: Create task with system-assigned identity

The steps in this section create a task and enable a system-assigned identity. If you want to enable a user-assigned identity instead, see Option 1: Create task with user-assigned identity.

Create task

Create the task helloworldtask by executing the following az acr task create command. The task runs without a source code context, and the command references the file helloworldtask.yaml in the working directory. The --assign-identity parameter with no value enables the system-assigned identity on the task.

az acr task create \
  --registry myregistry \
  --name helloworldtask \
  --context /dev/null \
  --file helloworldtask.yaml \
  --assign-identity 

In the command output, the identity section shows an identity of type SystemAssigned is set in the task. The principalId is the principal ID of the task identity:

[...]
  "identity": {
    "principalId": "xxxxxxxx-2703-42f9-97d0-xxxxxxxxxxxx",
    "tenantId": "xxxxxxxx-86f1-41af-91ab-xxxxxxxxxxxx",
    "type": "SystemAssigned",
    "userAssignedIdentities": null
  },
  "location": "eastus",
[...]

Use the az acr task show command to store the principalId in a variable, to use in later commands. Substitute the name of your task and your registry in the following command:

principalID=$(az acr task show \
  --name <task_name> --registry <registry_name> \
  --query identity.principalId --output tsv)

Give identity pull permissions to the base registry

In this section, give the managed identity permissions to pull from the base registry, mybaseregistry.

Use the az acr show command to get the resource ID of the base registry and store it in a variable:

baseregID=$(az acr show --name mybaseregistry --query id --output tsv)

Use the az role assignment create command to assign the identity the acrpull role to the base registry. This role has permissions only to pull images from the registry.

az role assignment create \
  --assignee $principalID \
  --scope $baseregID \
  --role acrpull

Add target registry credentials to task

Now use the az acr task credential add command to enable the task to authenticate with the base registry using the identity's credentials. Run the command corresponding to the type of managed identity you enabled in the task. If you enabled a user-assigned identity, pass --use-identity with the client ID of the identity. If you enabled a system-assigned identity, pass --use-identity [system].

# Add credentials for user-assigned identity to the task
az acr task credential add \
  --name helloworldtask \
  --registry myregistry \
  --login-server mybaseregistry.azurecr.io \
  --use-identity $clientID

# Add credentials for system-assigned identity to the task
az acr task credential add \
  --name helloworldtask \
  --registry myregistry \
  --login-server mybaseregistry.azurecr.io \
  --use-identity [system]

Manually run the task

To verify that the task in which you enabled a managed identity runs successfully, manually trigger the task with the az acr task run command.

az acr task run \
  --name helloworldtask \
  --registry myregistry

If the task runs successfully, output is similar to:

Queued a run with ID: cf10
Waiting for an agent...
2019/06/14 22:47:32 Using acb_vol_dbfbe232-fd76-4ca3-bd4a-687e84cb4ce2 as the home volume
2019/06/14 22:47:39 Creating Docker network: acb_default_network, driver: 'bridge'
2019/06/14 22:47:40 Successfully set up Docker network: acb_default_network
2019/06/14 22:47:40 Setting up Docker configuration...
2019/06/14 22:47:41 Successfully set up Docker configuration
2019/06/14 22:47:41 Logging in to registry: myregistry.azurecr.io
2019/06/14 22:47:42 Successfully logged into myregistry.azurecr.io
2019/06/14 22:47:42 Logging in to registry: mybaseregistry.azurecr.io
2019/06/14 22:47:43 Successfully logged into mybaseregistry.azurecr.io
2019/06/14 22:47:43 Executing step ID: acb_step_0. Timeout(sec): 600, Working directory: '', Network: 'acb_default_network'
2019/06/14 22:47:43 Scanning for dependencies...
2019/06/14 22:47:45 Successfully scanned dependencies
2019/06/14 22:47:45 Launching container with name: acb_step_0
Sending build context to Docker daemon   25.6kB
Step 1/6 : ARG REGISTRY_NAME
Step 2/6 : FROM ${REGISTRY_NAME}/baseimages/node:15-alpine
15-alpine: Pulling from baseimages/node
[...]
Successfully built 41b49a112663
Successfully tagged myregistry.azurecr.io/hello-world:cf10
2019/06/14 22:47:56 Successfully executed container: acb_step_0
2019/06/14 22:47:56 Executing step ID: acb_step_1. Timeout(sec): 600, Working directory: '', Network: 'acb_default_network'
2019/06/14 22:47:56 Pushing image: myregistry.azurecr.io/hello-world:cf10, attempt 1
The push refers to repository [myregistry.azurecr.io/hello-world]
[...]
2019/06/14 22:48:00 Step ID: acb_step_1 marked as successful (elapsed time in seconds: 2.517011)
2019/06/14 22:48:00 The following dependencies were found:
2019/06/14 22:48:00
- image:
    registry: myregistry.azurecr.io
    repository: hello-world
    tag: cf10
    digest: sha256:611cf6e3ae3cb99b23fadcd89fa144e18aa1b1c9171ad4a0da4b62b31b4e38d1
  runtime-dependency:
    registry: mybaseregistry.azurecr.io
    repository: baseimages/node
    tag: 15-alpine
    digest: sha256:e8e92cffd464fce3be9a3eefd1b65dc9cbe2484da31c11e813a4effc6105c00f
  git:
    git-head-revision: 0f988779c97fe0bfc7f2f74b88531617f4421643

Run ID: cf10 was successful after 32s

Run the az acr repository show-tags command to verify that the image built and was successfully pushed to myregistry:

az acr repository show-tags --name myregistry --repository hello-world --output tsv

Example output:

cf10

Next steps