> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Minimize User Access To Azure Container Registry

### More Info:

Grant only the minimum permissions required for the AKS cluster service principal to read and pull images from Azure Container Registry, avoiding broad Owner or account administrator roles.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* CIS AKS

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify the AKS cluster’s identity and attached ACRs**
           * Run on any machine with Azure CLI access:
             ```bash theme={null}
             # Get cluster identity info
             az aks show \
               --resource-group <AKS_RESOURCE_GROUP> \
               --name <AKS_CLUSTER_NAME> \
               --query "{type:identity.type, principalId:identity.principalId, userAssignedIdentities:identity.userAssignedIdentities}" \
               --output json

             # List all ACRs in the subscription (or restrict by resource group)
             az acr list --output table
             ```
           * Determine whether the cluster uses a system-assigned identity, user-assigned managed identity, or legacy service principal.

        2. **List current role assignments on each ACR and identify over-privileged roles**
           * For each registry (replace with actual names and resource groups):
             ```bash theme={null}
             ACR_NAME=<ACR_NAME>
             ACR_RG=<ACR_RESOURCE_GROUP>

             # Get the ACR resource ID
             ACR_ID=$(az acr show --name "$ACR_NAME" --resource-group "$ACR_RG" --query id -o tsv)

             # List role assignments scoped to this ACR
             az role assignment list \
               --scope "$ACR_ID" \
               --output table
             ```
           * Look for assignments of broad roles like `Owner`, `Contributor`, or subscription/resource-group–scoped roles to the AKS identity or to generic user/service principals that are not required for image pull.

        3. **Verify that the AKS identity has only the minimum ACR role (AcrPull or AcrPull-equivalent)**
           * From the output above, for the AKS identity principalId (or service principal appId), confirm that:
             * Scope is the ACR resource (not entire subscription or resource group) unless broader scope is intentionally required.
             * Role is `AcrPull` (or `AcrImageSigner`/`AcrPush` only where there is a clear business need).
           * If AKS authenticates using image pull secrets instead of managed identity, identify the underlying principal:
             ```bash theme={null}
             # On any machine with kubectl access
             kubectl get secret --all-namespaces | grep dockerconfigjson || true
             ```
           * Review who manages the credentials used in those secrets and what roles they have on the ACR (via `az role assignment list` as above).

        4. **Remove over-privileged role assignments and reassign least-privilege roles**
           * For any AKS identity or service principal that has `Owner`, `Contributor`, or other broad roles on the ACR and does not require them, remove the assignment:
             ```bash theme={null}
             # Example: remove an over-privileged role assignment by ID
             az role assignment list \
               --scope "$ACR_ID" \
               --assignee <PRINCIPAL_ID_OR_APPID> \
               --output table

             # Note the 'Id' field from the row you want to remove, then:
             az role assignment delete --ids <ROLE_ASSIGNMENT_ID>
             ```
           * Then assign the minimal required role (`AcrPull`) scoped to the ACR:
             ```bash theme={null}
             az role assignment create \
               --assignee <PRINCIPAL_ID_OR_APPID> \
               --role "AcrPull" \
               --scope "$ACR_ID"
             ```
           * If needed, configure or update the AKS–ACR integration so AKS uses this principal:
             ```bash theme={null}
             # Using system-assigned identity
             az aks update \
               --resource-group <AKS_RESOURCE_GROUP> \
               --name <AKS_CLUSTER_NAME> \
               --attach-acr "$ACR_NAME"
             # Or, if using a specific service principal/managed identity, configure per your chosen auth pattern.
             ```

        5. **Optionally migrate from legacy service principal / broad roles to dedicated identity for ACR access**
           * If the AKS cluster or image-pulling process currently uses a generic Owner/Contributor principal, create or select a dedicated identity with only ACR permissions:
             ```bash theme={null}
             # Example: create a user-assigned managed identity
             az identity create \
               --name <ACR_PULL_IDENTITY_NAME> \
               --resource-group <IDENTITY_RESOURCE_GROUP>

             # Grant AcrPull on the ACR to this identity
             PULL_MI_PRINCIPAL_ID=$(az identity show \
               --name <ACR_PULL_IDENTITY_NAME> \
               --resource-group <IDENTITY_RESOURCE_GROUP> \
               --query principalId -o tsv)

             az role assignment create \
               --assignee "$PULL_MI_PRINCIPAL_ID" \
               --role "AcrPull" \
               --scope "$ACR_ID"
             ```
           * Integrate this identity into AKS either as the cluster managed identity or via pull secrets, following your chosen authentication pattern.

        6. **Verify least-privilege configuration and cluster image pull functionality**
           * Re-list ACR role assignments and confirm only minimal roles remain for AKS-related principals:
             ```bash theme={null}
             az role assignment list \
               --scope "$ACR_ID" \
               --output table
             ```
           * Validate that pods can still pull images from ACR:
             ```bash theme={null}
             # On any machine with kubectl access
             kubectl run acr-pull-test \
               --image <ACR_NAME>.azurecr.io/<REPO>/<IMAGE>:<TAG> \
               --restart=Never --rm -it
             ```
           * If the test pod starts successfully and the role assignments show only scoped `AcrPull` (and any explicitly justified roles), the configuration meets the intent of the control.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot change Azure Container Registry role assignments or AKS–ACR integration, because these live in Azure RBAC and AKS configuration managed via the Azure portal, Azure CLI, or IaC. To address this finding, make the changes described in the Manual Steps section using those cloud provider tools.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Audit AKS -> ACR access for excessive permissions
        # Requirements:
        #   - Azure CLI logged in with rights to read AKS, ACR and role assignments
        #   - jq installed
        #
        # Run on: any machine with az, jq, and access to the subscription(s)

        set -euo pipefail

        # -----------------------------
        # CONFIGURATION
        # -----------------------------
        # Space-separated list of subscriptions to inspect
        SUBSCRIPTIONS="SUBSCRIPTION_ID_1 SUBSCRIPTION_ID_2"

        echo "=== AKS to ACR access review ==="
        date
        echo

        for SUB in $SUBSCRIPTIONS; do
          echo "=== Subscription: $SUB ==="
          az account set -s "$SUB"

          # List AKS clusters
          CLUSTERS_JSON=$(az aks list -o json)
          if [[ "$(echo "$CLUSTERS_JSON" | jq 'length')" -eq 0 ]]; then
            echo "No AKS clusters found in $SUB"
            echo
            continue
          fi

          echo "$CLUSTERS_JSON" | jq -r '.[] | "\(.name) \(.resourceGroup)"' | while read -r AKS_NAME RG; do
            echo "---- AKS cluster: $AKS_NAME (RG: $RG) ----"

            # Try to get the identity used by the cluster for ACR pulls
            # 1) user-assigned or system-assigned managed identity
            IDENTITY_TYPE=$(echo "$CLUSTERS_JSON" | jq -r ".[] | select(.name==\"$AKS_NAME\" and .resourceGroup==\"$RG\") | .identity.type // \"\"")
            ID_PRINCIPAL_ID=""

            if [[ "$IDENTITY_TYPE" == "SystemAssigned" || "$IDENTITY_TYPE" == "SystemAssigned, UserAssigned" || "$IDENTITY_TYPE" == "UserAssigned" ]]; then
              # Primary identity principalId
              ID_PRINCIPAL_ID=$(echo "$CLUSTERS_JSON" | jq -r ".[] | select(.name==\"$AKS_NAME\" and .resourceGroup==\"$RG\") | .identity.principalId // \"\"")
              if [[ -n "$ID_PRINCIPAL_ID" && "$ID_PRINCIPAL_ID" != "null" ]]; then
                echo "  Managed identity principalId: $ID_PRINCIPAL_ID"
              fi
            fi

            # 2) Legacy service principal (if present)
            SP_CLIENT_ID=$(az aks show -n "$AKS_NAME" -g "$RG" --query "servicePrincipalProfile.clientId" -o tsv 2>/dev/null || echo "")
            if [[ -n "$SP_CLIENT_ID" && "$SP_CLIENT_ID" != "msi" && "$SP_CLIENT_ID" != "null" ]]; then
              echo "  Service principal clientId: $SP_CLIENT_ID"
              # Resolve to objectId
              SP_OBJECT_ID=$(az ad sp list --filter "appId eq '$SP_CLIENT_ID'" --query "[0].id" -o tsv 2>/dev/null || echo "")
              if [[ -n "$SP_OBJECT_ID" ]]; then
                echo "  Service principal objectId: $SP_OBJECT_ID"
              fi
            fi

            # Gather principal IDs to check
            PRINCIPALS=()
            if [[ -n "${ID_PRINCIPAL_ID:-}" && "$ID_PRINCIPAL_ID" != "null" ]]; then
              PRINCIPALS+=("$ID_PRINCIPAL_ID")
            fi
            if [[ -n "${SP_OBJECT_ID:-}" ]]; then
              PRINCIPALS+=("$SP_OBJECT_ID")
            fi

            if [[ "${#PRINCIPALS[@]}" -eq 0 ]]; then
              echo "  WARNING: No identifiable principal (managed identity or service principal) found for this cluster."
              echo
              continue
            fi

            # 3) Discover linked ACRs via 'az aks show' (automatic linkage)
            ACR_IDS=$(az aks show -n "$AKS_NAME" -g "$RG" --query "addonProfiles.aciConnectorLinux.config.subnetName" -o tsv 2>/dev/null || true)
            # Fallback: use 'az aks show' | grep acr (for classic ACR integration)
            ACR_IDS_FROM_CMD=$(az aks show -n "$AKS_NAME" -g "$RG" --query "servicePrincipalProfile.clientId" -o tsv >/dev/null 2>&1 || true)

            # Instead, rely on all ACRs in subscription and later focus on roles they have with these principals
            echo "  Listing ACRs in subscription..."
            ACRS_JSON=$(az acr list -o json)
            if [[ "$(echo "$ACRS_JSON" | jq 'length')" -eq 0 ]]; then
              echo "  No ACR registries found in subscription."
              echo
              continue
            fi

            echo "  Checking role assignments on ACRs for AKS principals..."
            echo

            echo "$ACRS_JSON" | jq -r '.[] | "\(.name) \(.id)"' | while read -r ACR_NAME ACR_ID; do
              echo "  ACR: $ACR_NAME"
              for PID in "${PRINCIPALS[@]}"; do
                echo "    Principal: $PID"
                # Role assignments scoped at the registry
                RA_JSON=$(az role assignment list --assignee "$PID" --scope "$ACR_ID" -o json 2>/dev/null || echo "[]")

                if [[ "$(echo "$RA_JSON" | jq 'length')" -eq 0 ]]; then
                  echo "      No direct role assignments on this ACR."
                else
                  echo "      Role assignments on this ACR:"
                  echo "$RA_JSON" | jq -r '.[] | "        Role: \(.roleDefinitionName) | Scope: \(.scope)"'
                fi
              done
              echo
            done

            # 4) Check if principals have broad roles (Owner/Contributor) at subscription or resource group scope that could implicitly grant ACR access
            echo "  Checking for broad roles (Owner/Contributor) at subscription and RG scopes..."
            for PID in "${PRINCIPALS[@]}"; do
              echo "    Principal: $PID"

              echo "      Subscription-scope assignments:"
              az role assignment list --assignee "$PID" --scope "/subscriptions/$SUB" \
                --query "[?roleDefinitionName=='Owner' || roleDefinitionName=='Contributor'].[roleDefinitionName, scope]" \
                -o tsv 2>/dev/null || echo "        none"

              echo "      Resource-group-scope assignments (RG: $RG):"
              az role assignment list --assignee "$PID" --scope "/subscriptions/$SUB/resourceGroups/$RG" \
                --query "[?roleDefinitionName=='Owner' || roleDefinitionName=='Contributor'].[roleDefinitionName, scope]" \
                -o tsv 2>/dev/null || echo "        none"
            done

            echo
          done

          echo
        done

        cat <<'EOF'

        INTERPRETING RESULTS
        --------------------
        Potential PROBLEMS (needs review/minimization) include:
        1. On any ACR:
           - AKS identity/service principal has roles like:
             - "Owner"
             - "Contributor"
             - Any custom role with write/delete/admin actions
           Instead, prefer "AcrPull" (or at most "AcrPush" if justified).

        2. At subscription or resource group scope:
           - AKS identity/service principal is assigned:
             - "Owner"
             - "Contributor"
           This is broader than needed for pulling images.

        3. No identifiable principal:
           - If the script reports "No identifiable principal" for a cluster, verify how it authenticates
             to ACR (managed identity, service principal, or image pull secrets) and ensure access is
             not via over-privileged user accounts.

        Use this report to manually:
          - Remove broad Owner/Contributor assignments where not strictly required.
          - Assign least-privilege roles (e.g., AcrPull on the specific ACRs used by the cluster).
          - Consider using a dedicated managed identity/service principal per cluster or workload.
        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
