diff --git a/content/chainguard/administration/assumable-ids/identity-examples/azure-identity.md b/content/chainguard/administration/assumable-ids/identity-examples/azure-identity.md new file mode 100644 index 0000000000..be1a62c576 --- /dev/null +++ b/content/chainguard/administration/assumable-ids/identity-examples/azure-identity.md @@ -0,0 +1,380 @@ +--- +title: "Create an Assumable Identity to Authenticate from Azure" +linktitle: "Azure" +lead: "" +description: "Procedural tutorial outlining how to create a Chainguard identity that can be assumed by an Azure workload using a managed identity." +type: "article" +date: 2026-05-15T00:00:00+00:00 +lastmod: 2026-05-15T00:00:00+00:00 +draft: false +tags: ["Chainguard Containers", "Procedural"] +images: [] +weight: 011 +--- + +> **Note:** If you're authenticating from a workload running in Azure +> Kubernetes Service (AKS), refer to the +> [Kubernetes identity guide](/chainguard/administration/assumable-ids/identity-examples/kubernetes-identity/) +> instead. + +Chainguard's [_assumable identities_](/chainguard/administration/assumable-ids/assumable-ids/) +are identities that can be assumed by external applications or workflows in +order to perform certain tasks that would otherwise have to be done by a human. + +This procedural tutorial outlines how to create an identity that can be assumed +by an Azure workload — such as a VM, Container App, or Function — using an +[Azure managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview) +and then used to interact with the Chainguard API. + +## Prerequisites + +To complete this guide, you will need the following. + +- `chainctl` — the Chainguard command line interface tool — installed on your + local machine. Follow our guide on + [How to Install `chainctl`](/chainguard/chainctl-usage/how-to-install-chainctl/) + to set this up. +- The [Azure CLI (`az`)](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli), + authenticated with `az login`. +- An Azure subscription, and permission to register applications in your Entra + ID tenant. App registration is enabled for all users in most tenants. If + it's disabled in yours, ask an administrator to pre-register the + application and share its client ID with you. +- To follow the Terraform example, [`terraform`](https://developer.hashicorp.com/terraform/install) + installed locally. + +## Find your Tenant ID + +Each Entra ID tenant has its own OIDC issuer URL, which has the following +format: + +``` +https://login.microsoftonline.com//v2.0 +``` + +Retrieve the tenant ID of your current Azure session with: + +```shell +az account show --query tenantId --output tsv +``` + +## Create the Entra ID Application + +Register an Entra ID application to serve as the audience for tokens issued +to managed identities in your tenant. We'll grant it no API permissions and +expose no scopes — its only purpose is to provide a unique, tenant-specific +`aud` claim that Chainguard's STS can match on. + +```shell +az ad app create \ + --display-name chainguard-audience \ + --sign-in-audience AzureADMyOrg +``` + +Note down the `appId` from the output — this is the client ID we'll use as the +token audience. Substitute it for `` in the following commands. + +Configure the application to issue v2.0 access tokens and to register +`api://` as an identifier URI, so that Entra ID will resolve it as +a valid scope when requesting tokens. + +```shell +az ad app update \ + --id \ + --requested-access-token-version 2 \ + --identifier-uris "api://" +``` + +Then create a service principal for the application. Entra ID requires this +in order to recognise the application as a valid token resource when a +managed identity requests a token for it. + +```shell +az ad sp create --id +``` + +## Create the Managed Identity + +If you don't already have a +[user-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-manage-user-assigned-managed-identities) +to bind the Chainguard identity to, create one now. Substitute +`` and `` (e.g. `eastus`) for your own values. + +```shell +az identity create \ + --name chainguard-identity \ + --resource-group \ + --location +``` + +Note down the `principalId` from the output — we'll use this as the subject of +the Chainguard identity in the next step. Note the `clientId` too, as the +workload will need it later to request a token. + +## Create the Assumable Identity + +This guide outlines two methods for creating the Chainguard identity: one +using `chainctl` over a command-line interface, and another using Terraform. + +### CLI + +Run this `chainctl` command to create the identity and bind it to the +`registry.pull` role. Substitute `` with the tenant ID you +retrieved earlier, `` with the managed identity's principal ID, +and `` with the application's client ID. + +```shell +chainctl iam id create azure-identity \ + --identity-issuer="https://login.microsoftonline.com//v2.0" \ + --subject="" \ + --audience="" \ + --role=registry.pull +``` + +This will return the [UIDP (unique identity path)](/chainguard/administration/cloudevents/events-reference/#uidp-identifiers) +of the identity, which we'll use when assuming it in the next section. + +If you need to retrieve the UIDP later, list the identity with this command: + +```shell +chainctl iam identities list --name=azure-identity +``` + +### Terraform + +You can also create the application registration, managed identity, and +Chainguard identity together with the +[Chainguard Terraform provider](https://registry.terraform.io/providers/chainguard-dev/chainguard/latest) +and the +[AzureRM](https://registry.terraform.io/providers/hashicorp/azurerm/latest) +and +[AzureAD](https://registry.terraform.io/providers/hashicorp/azuread/latest) +providers. + +Substitute `` with your Chainguard organization name, +`` with your Azure resource group, and `` with the +Azure region you want to deploy into. + +```hcl +terraform { + required_providers { + azurerm = { source = "hashicorp/azurerm" } + azuread = { source = "hashicorp/azuread" } + chainguard = { source = "chainguard-dev/chainguard" } + } +} + +provider "azurerm" { + features {} +} + +data "azurerm_client_config" "current" {} +data "azuread_client_config" "current" {} + +# A permission-free Entra ID application registration. Its sole purpose is +# to provide a unique audience for tokens exchanged with Chainguard's STS. +resource "azuread_application" "chainguard_audience" { + display_name = "chainguard-audience" + owners = [data.azuread_client_config.current.object_id] + + api { + requested_access_token_version = 2 + } + + lifecycle { + ignore_changes = [identifier_uris] + } +} + +# A service principal — required for Entra ID to recognise the application +# as a valid token audience when a managed identity requests a token. +resource "azuread_service_principal" "chainguard_audience" { + client_id = azuread_application.chainguard_audience.client_id + owners = [data.azuread_client_config.current.object_id] +} + +# Register api:// as the identifier URI so Entra ID resolves +# it as a valid resource for token requests. +resource "azuread_application_identifier_uri" "chainguard_audience" { + application_id = azuread_application.chainguard_audience.id + identifier_uri = "api://${azuread_application.chainguard_audience.client_id}" +} + +# A user-assigned managed identity that will assume the Chainguard identity. +# Attach this to any Azure workload that supports managed identities +# (VMs, Container Apps, Functions, etc). +resource "azurerm_user_assigned_identity" "chainguard" { + name = "chainguard-identity" + resource_group_name = "" + location = "" +} + +data "chainguard_group" "org" { + name = "" +} + +resource "chainguard_identity" "azure" { + parent_id = data.chainguard_group.org.id + name = "azure-identity" + description = "Assumed by Azure workloads via the chainguard-identity managed identity." + + claim_match { + issuer = "https://login.microsoftonline.com/${data.azurerm_client_config.current.tenant_id}/v2.0" + subject = azurerm_user_assigned_identity.chainguard.principal_id + audience = azuread_application.chainguard_audience.client_id + } +} + +data "chainguard_role" "registry_pull" { + name = "registry.pull" +} + +resource "chainguard_rolebinding" "registry_pull" { + identity = chainguard_identity.azure.id + role = data.chainguard_role.registry_pull.items[0].id + group = data.chainguard_group.org.id +} + +output "chainguard_identity_id" { + value = chainguard_identity.azure.id +} + +output "token_audience" { + value = "api://${azuread_application.chainguard_audience.client_id}" +} + +output "managed_identity_client_id" { + value = azurerm_user_assigned_identity.chainguard.client_id +} +``` + +The `chainguard_identity_id` output provides the identity's UIDP. The +`token_audience` and `managed_identity_client_id` outputs provide the values +your workload will need to request a managed identity token and exchange it +with Chainguard. + +For a full end-to-end example of using this pattern from an Azure Container +App, see the +[`image-copy-acr` example](https://github.com/chainguard-demo/platform-examples/tree/main/image-copy-acr) +in Chainguard's public `platform-examples` repository. + +## Assume the Identity + +Attach the managed identity to the Azure workload you want to authenticate +from. For example, to attach it to an existing VM: + +```shell +az vm identity assign \ + --name \ + --resource-group \ + --identities +``` + +From inside the workload, request a token from the +[Azure Instance Metadata Service (IMDS)](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token) +for the audience you configured on the application. Substitute `` +with the application's client ID, and `` with the +`clientId` of the managed identity. + +```shell +AZURE_TOKEN=$(curl -sSf \ + -H "Metadata: true" \ + "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://&client_id=" \ + | jq -r .access_token) +``` + +The following sections outline two methods for using the Azure token to +interact with Chainguard: one using `chainctl`, and another by exchanging +the token directly with the Chainguard STS over the API. + +### CLI + +Use the Azure token to log in with `chainctl`. Substitute `` +with the UIDP of the Chainguard identity created earlier. + +```shell +chainctl auth login \ + --identity \ + --identity-token "${AZURE_TOKEN}" +``` + +Now you can issue `chainctl` commands under this assumed identity. For +instance, you could list the container image repositories available to your +organization: + +```shell +chainctl image repo list +``` + +To pull images from the Chainguard registry with Docker, configure a +credential helper with `chainctl`. Provide the same identity and token: + +```shell +chainctl auth configure-docker \ + --identity \ + --identity-token "${AZURE_TOKEN}" +``` + +You can now pull images with `docker`. Substitute `` with your +Chainguard organization name and `` with an image available to your +organization. + +```shell +docker pull cgr.dev//:latest +``` + +### API + +If `chainctl` isn't installed in the workload, you can exchange the Azure +token for a Chainguard API token directly. Substitute `` with +the UIDP of the Chainguard identity created earlier. + +```shell +API_TOKEN=$(curl -sSf \ + -H "Authorization: Bearer ${AZURE_TOKEN}" \ + "https://issuer.enforce.dev/sts/exchange?aud=https://console-api.enforce.dev&identity=" \ + | jq -r .token) +``` + +Use the API token to interact with the Chainguard API. For instance, to list +the container image repositories available to your organization: + +```shell +curl -sSf -H "Authorization: Bearer ${API_TOKEN}" \ + https://console-api.enforce.dev/registry/v1/repos | jq -r .items[].name +``` + +If you're authenticating from Go, the +[Chainguard SDK](https://pkg.go.dev/chainguard.dev/sdk) and the +[Azure SDK for Go](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go) can +do the token exchange for you. The +[`image-copy-acr` example](https://github.com/chainguard-demo/platform-examples/tree/main/image-copy-acr) +demonstrates this pattern. + +## Clean Up + +Delete the Chainguard identity. + +```shell +chainctl iam id delete +``` + +Delete the managed identity. + +```shell +az identity delete \ + --name chainguard-identity \ + --resource-group +``` + +Delete the Entra ID application registration. This will also remove the +associated service principal. + +```shell +az ad app delete --id +``` + +## Learn more + +For more information about how assumable identities work in Chainguard, check +out our [conceptual overview of assumable identities](/chainguard/administration/assumable-ids/assumable-ids/).