TutorialsScanning Images from Azure ACR

Scanning Images from Azure ACR

Give the rad-sbom plugin pull access to a private Azure Container Registry with AKS Workload Identity so RAD Security can scan its images

The rad-sbom plugin generates an SBOM (Software Bill of Materials) for every container image running in your cluster. It downloads images directly from registry APIs, so it needs credentials for private registries.

rad-sbom pulls images itself, independently of the kubelet. Attaching an ACR to your cluster with az aks update --attach-acr only grants pull access to the node's kubelet identity, not to rad-sbom. You must give rad-sbom its own identity with pull access to the registry.

The recommended approach is Microsoft Entra Workload ID: a user-assigned managed identity federated with the rad-sbom Kubernetes service account. No long-lived credentials are stored in the cluster.

How it works

When rad.azureWorkloadIdentityClientId is set, the rad-plugins Helm chart:

  • annotates the rad-sbom service account with azure.workload.identity/client-id: <client-id>
  • labels the rad-sbom pod with azure.workload.identity/use: "true"

The AKS workload identity webhook then injects a federated token into the rad-sbom pod, which rad-sbom exchanges for an Entra ID token to authenticate to ACR.

Prerequisites

  • An AKS cluster with the OIDC issuer and Workload Identity enabled
  • Azure CLI, logged in with permissions to create managed identities and role assignments on the registry
  • Helm CLI
  • RAD Security API credentials

Configuration steps

Set these variables once. They are used by all the commands below:

export RG=<aks-resource-group>
export CLUSTER_NAME=<aks-cluster-name>
export LOCATION=<azure-region>
export ACR_NAME=<acr-name>
# Namespace where rad-plugins is (or will be) installed - the Helm release namespace
export NAMESPACE=rad

Enable OIDC issuer and Workload Identity on the cluster

Check whether both features are already enabled:

az aks show -g $RG -n $CLUSTER_NAME \
  --query "{oidcIssuer: oidcIssuerProfile.enabled, workloadIdentity: securityProfile.workloadIdentity.enabled}" -o table

If either value is not True, enable them:

az aks update -g $RG -n $CLUSTER_NAME --enable-oidc-issuer --enable-workload-identity

Create a managed identity for rad-sbom

az identity create --name rad-sbom -g $RG --location $LOCATION

export RAD_SBOM_CLIENT_ID="$(az identity show -g $RG --name rad-sbom --query clientId -o tsv)"
export RAD_SBOM_PRINCIPAL_ID="$(az identity show -g $RG --name rad-sbom --query principalId -o tsv)"
echo $RAD_SBOM_CLIENT_ID

Note the client ID. You will need it in the Helm values.

Grant the identity pull access to the registry

export ACR_ID="$(az acr show --name $ACR_NAME --query id -o tsv)"

az role assignment create \
  --assignee-object-id $RAD_SBOM_PRINCIPAL_ID \
  --assignee-principal-type ServicePrincipal \
  --role "AcrPull" \
  --scope $ACR_ID

az acr show resolves the registry's resource ID, so this works even when the registry lives in a different resource group or subscription than the cluster.

Repeat this step for every registry your workloads pull from. A role assignment is scoped to a single registry.

If the registry uses the RBAC Registry + ABAC Repository Permissions role assignment mode, AcrPull has no effect. Assign the Container Registry Repository Reader role instead. Check the mode with az acr show --name $ACR_NAME --query roleAssignmentMode -o tsv.

Federate the identity with the rad-sbom service account

export AKS_OIDC_ISSUER="$(az aks show -g $RG -n $CLUSTER_NAME --query oidcIssuerProfile.issuerUrl -o tsv)"

az identity federated-credential create \
  --name rad-sbom-to-${CLUSTER_NAME} \
  --identity-name rad-sbom \
  --resource-group $RG \
  --issuer ${AKS_OIDC_ISSUER} \
  --subject system:serviceaccount:${NAMESPACE}:rad-sbom \
  --audience api://AzureADTokenExchange

The subject must match exactly: system:serviceaccount:<release-namespace>:rad-sbom. <release-namespace> is the namespace you install rad-plugins into. If it does not match, authentication fails and private images are not scanned.

Install or upgrade rad-plugins

Add the client ID to your values.yaml:

rad:
  apiUrl: "https://api.rad.security"
  base64AccessKeyId: "<rad-access-key-id>"
  base64SecretKey: "<rad-secret-key>"
  clusterName: "<cluster-name>"
  azureWorkloadIdentityClientId: "<rad-sbom-client-id>"

Then install or upgrade the chart into $NAMESPACE:

helm repo add rad-security https://charts.rad.security/stable
helm repo update

helm upgrade --install rad rad-security/rad-plugins \
  --namespace $NAMESPACE \
  --create-namespace \
  -f values.yaml

See ArtifactHub for all chart options.

If rad-plugins was already running when you created the federated credential or role assignment, restart rad-sbom so it picks up the new configuration: kubectl rollout restart deployment/rad-sbom -n $NAMESPACE. Role assignments can take a few minutes to propagate.

Verify the setup

Check that the workload identity webhook injected the identity into the rad-sbom pod:

kubectl get sa rad-sbom -n $NAMESPACE -o jsonpath='{.metadata.annotations.azure\.workload\.identity/client-id}{"
"}'

kubectl get pod -n $NAMESPACE -l app_name=rad-sbom \
  -o jsonpath='{.items[0].spec.containers[0].env[?(@.name=="AZURE_CLIENT_ID")].value}{"
"}'

Both commands should print the rad-sbom client ID. If the second one prints nothing, the webhook did not mutate the pod. Confirm Workload Identity is enabled on the cluster, then restart the deployment.

Then deploy a workload that uses an image from your ACR and check the rad-sbom logs for errors:

kubectl logs -n $NAMESPACE deployment/rad-sbom --since=10m | grep -i -E "error|unauthorized|denied"

For more detail, set sbom.env.LOG_LEVEL: debug in your values and upgrade the release.

Troubleshooting