> ## 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 Cluster Access To Read-Only For Azure Container Registry

### More Info:

Restrict the AKS clusters access to Azure Container Registry to read-only (pull) so the cluster cannot push or modify images.

### 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 how AKS pulls images from ACR**
           * On any machine with Azure CLI and access to the subscription, list the cluster and its node resource group:
             ```bash theme={null}
             az aks show \
               --resource-group <AKS_RESOURCE_GROUP> \
               --name <AKS_CLUSTER_NAME> \
               --query "{identity:identity, nodeResourceGroup:nodeResourceGroup}" \
               --output json
             ```
           * Determine whether you are using:
             * Managed identity (system-assigned or user-assigned) attached to the AKS cluster, or
             * A service principal, or
             * ACR firewall + private endpoint only (no identity/RBAC link).

        2. **Enumerate ACR instances and check role assignments**
           * List ACRs in the subscription:
             ```bash theme={null}
             az acr list --output table
             ```
           * For each ACR, list role assignments relevant to AKS (replace `<ACR_NAME>`):
             ```bash theme={null}
             az acr show --name <ACR_NAME> --query "id" --output tsv
             ACR_ID=$(az acr show --name <ACR_NAME> --query "id" --output tsv)

             az role assignment list \
               --scope "$ACR_ID" \
               --output table
             ```
           * In the output, look for assignments to:
             * The AKS managed identity (object/principal ID from step 1), or
             * The AKS service principal.
           * Note roles such as `AcrPull` (read-only) vs broader ones (`AcrPush`, `Owner`, `Contributor`, `AcrImageSigner`, custom roles that include write/delete actions).

        3. **Decide required access and plan corrections**
           * For each identity used by AKS:
             * If only image pulling is required, the desired role is **AcrPull** at the minimal required scope (ideally the specific ACR, not subscription or resource group).
             * If broader roles are in use, confirm whether any workload or pipeline actually needs push/delete/sign operations from inside the cluster; if so, limit that to a separate identity not bound to the entire cluster where possible.

        4. **Restrict existing over-privileged role assignments**
           * For each over-privileged assignment (example: `AcrPush`, `Contributor`, `Owner` for the AKS identity on ACR), remove it:
             ```bash theme={null}
             az role assignment list \
               --scope "$ACR_ID" \
               --assignee <AKS_PRINCIPAL_ID> \
               --output json
             ```
           * From the JSON, copy the `id` of the specific assignment to remove, then:
             ```bash theme={null}
             az role assignment delete --ids "<ROLE_ASSIGNMENT_ID>"
             ```
           * If the cluster still needs pull access and has no `AcrPull` assignment, add it:
             ```bash theme={null}
             az role assignment create \
               --assignee <AKS_PRINCIPAL_ID> \
               --scope "$ACR_ID" \
               --role "AcrPull"
             ```

        5. **Review other access paths to ACR (optional but recommended)**
           * Check subscription- or resource-group-level assignments that might indirectly grant write access to ACR:
             ```bash theme={null}
             az role assignment list \
               --assignee <AKS_PRINCIPAL_ID> \
               --all \
               --output table
             ```
           * If you find higher-scope roles that include ACR write/delete actions and are not strictly required, plan to replace them with more granular, read-only assignments where feasible.

        6. **Verify effective read-only configuration**
           * For each ACR used by the cluster, confirm only `AcrPull` (or other strictly read-only custom roles you have vetted) are assigned to the AKS identity at the ACR scope:
             ```bash theme={null}
             az role assignment list \
               --scope "$ACR_ID" \
               --assignee <AKS_PRINCIPAL_ID> \
               --output table
             ```
           * Independently verify that push/delete is no longer allowed from the cluster identity by attempting a test push using that identity’s context from a non-production environment, ensuring it fails while pulls still succeed.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot change Azure Container Registry permissions or the AKS-to-ACR integration; those settings are managed in Azure (portal/CLI/IaC) at the cloud provider/control plane level. Refer to the Manual Steps section for how to review and adjust AKS access to ACR to be read-only (pull).
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        set -euo pipefail

        # PURPOSE:
        # - Enumerate all AKS node identities and cluster-managed identities (if any)
        # - Enumerate all role assignments these identities have against Azure Container Registries (ACR)
        # - Highlight any roles that can push/modify images (problematic) vs pull-only (expected)
        #
        # REQUIREMENTS:
        # - Azure CLI logged in:    az login
        # - Subscription set:       az account set --subscription "<SUBSCRIPTION_ID>"
        # - kubectl configured for the AKS cluster
        #
        # RUN ON:
        # - Any machine with Azure CLI and kubectl access to the AKS cluster

        echo "=== 1. Discover AKS cluster resource group and node resource group ==="
        # Try to infer resource groups from current context
        CURRENT_CONTEXT="$(kubectl config current-context)"
        echo "Current kube context: ${CURRENT_CONTEXT}"

        # List AKS clusters and prompt user to confirm the relevant one
        echo
        echo "Available AKS clusters in current subscription (for reference):"
        az aks list --query '[].{name:name, resourceGroup:resourceGroup}' -o table || true
        echo

        read -r -p "Enter AKS cluster name: " AKS_NAME
        read -r -p "Enter AKS cluster resource group: " AKS_RG

        echo
        echo "Fetching node resource group for AKS cluster..."
        NODE_RG="$(az aks show -n "${AKS_NAME}" -g "${AKS_RG}" --query nodeResourceGroup -o tsv)"
        echo "AKS cluster resource group: ${AKS_RG}"
        echo "AKS node resource group:    ${NODE_RG}"
        echo

        echo "=== 2. Identify AKS node identities ==="
        # For user-assigned identities or VMSS identities backing nodes
        echo "Listing VM Scale Sets in node resource group (for node identities)..."
        az vmss list -g "${NODE_RG}" --query '[].{name:name, identity:identity}' -o json | jq '.[]' || true
        echo
        echo "NOTE:"
        echo "- For each VMSS, the 'identity' block contains the principalId / userAssignedIdentities."
        echo "- Copy the principalId values below if you want to manually inspect them:"
        az vmss list -g "${NODE_RG}" --query '[].{name:name, principalIds:identity.principalId}' -o table || true
        echo

        echo "=== 3. Identify ACR registries and their scopes ==="
        echo "Listing all Azure Container Registries in the subscription..."
        az acr list --query '[].{name:name, resourceGroup:resourceGroup, loginServer:loginServer, id:id}' -o table
        echo
        read -r -p "Optionally, restrict analysis to a single ACR name (leave blank for all): " ACR_FILTER

        if [[ -n "${ACR_FILTER}" ]]; then
          echo "Filtering to ACR: ${ACR_FILTER}"
          ACR_IDS=($(az acr list --query "[?name=='${ACR_FILTER}'].id" -o tsv))
        else
          ACR_IDS=($(az acr list --query '[].id' -o tsv))
        fi

        if [[ "${#ACR_IDS[@]}" -eq 0 ]]; then
          echo "No ACR registries found in this subscription. Exiting."
          exit 0
        fi

        echo "=== 4. Gather role definitions that can modify images ==="
        # Roles that typically allow PUSH or modify capabilities to ACR
        # (This list is not exhaustive; adjust per your org's custom roles.)
        PUSH_CAPABLE_ROLES=(
          "AcrPush"
          "AcrDelete"
          "AcrImageSigner"
          "Owner"
          "Contributor"
        )

        echo "Roles considered problematic (write/push or broad rights) for ACR access:"
        printf ' - %s\n' "${PUSH_CAPABLE_ROLES[@]}"
        echo

        echo "=== 5. Enumerate role assignments on each ACR ==="
        PROBLEM_FOUND=0

        for ACR_ID in "${ACR_IDS[@]}"; do
          echo
          echo ">>> Checking ACR: ${ACR_ID}"
          echo "Role assignments on this ACR (scope = registry only, not parents):"
          az role assignment list \
            --scope "${ACR_ID}" \
            --include-inherited false \
            -o json > /tmp/acr_role_assignments.json

          # Pretty-print all assignments first for review
          jq '.[] | {principalId, principalType, roleDefinitionName, scope}' /tmp/acr_role_assignments.json || true

          echo
          echo "Scanning for principals with PUSH/MODIFY-capable roles..."
          jq -r '.[] | "\(.principalId)\t\(.principalType)\t\(.roleDefinitionName)\t\(.scope)"' /tmp/acr_role_assignments.json \
            | while IFS=$'\t' read -r PRINCIPAL_ID PRINCIPAL_TYPE ROLE_NAME SCOPE; do
                for BAD_ROLE in "${PUSH_CAPABLE_ROLES[@]}"; do
                  if [[ "${ROLE_NAME}" == "${BAD_ROLE}" ]]; then
                    PROBLEM_FOUND=1
                    echo "!!! PROBLEMATIC ROLE ASSIGNMENT DETECTED:"
                    echo "    PrincipalId:        ${PRINCIPAL_ID}"
                    echo "    PrincipalType:      ${PRINCIPAL_TYPE}"
                    echo "    RoleDefinitionName: ${ROLE_NAME}"
                    echo "    Scope:              ${SCOPE}"
                    echo "    -> This principal can likely PUSH or MODIFY images in this ACR."
                    echo
                  fi
                done
              done
        done

        echo "=== 6. Cross-check: likely identities used by the AKS cluster ==="
        echo
        echo "Cluster managed identity / service principal:"
        az aks show -n "${AKS_NAME}" -g "${AKS_RG}" \
          --query '{identity: identity, servicePrincipalProfile: servicePrincipalProfile}' -o json | jq '.'

        echo
        echo "Compare the principalId(s) above against the problematic role assignments listed earlier."

        echo
        echo "=== 7. Interpretation of results ==="
        if [[ "${PROBLEM_FOUND}" -eq 0 ]]; then
          echo "No PUSH/MODIFY-capable ACR roles were found at registry scope for any principal."
          echo "From a registry-scope perspective, cluster access is likely limited to pull-only (or not granted)."
        else
          echo "One or more principals have ACR roles that allow image push/delete or broad modification."
          echo
          echo "WHAT OUTPUT INDICATES A PROBLEM:"
          echo "- Any '!!! PROBLEMATIC ROLE ASSIGNMENT DETECTED' block for a principal that is:"
          echo "  * The AKS cluster managed identity/service principal, or"
          echo "  * A VMSS / node managed identity used by the cluster."
          echo
          echo "These indicate the cluster (or its nodes) has more than read-only (pull) access to ACR."
          echo
          echo "This check is MANUAL: review whether those principals NEED push/modify rights."
          echo "If not, adjust role assignments via Azure Portal / CLI / IaC to use pull-only roles (e.g. AcrPull)."
        fi

        echo
        echo "=== 8. Optional: verify cluster identities explicitly against ACR scopes ==="
        echo "You can explicitly look up role assignments for a specific principalId and ACR:"
        echo "Example (replace values):"
        echo "  PRINCIPAL_ID=\"<principal-guid>\""
        echo "  ACR_ID=\"<acr-resource-id>\""
        echo "  az role assignment list --assignee \"\$PRINCIPAL_ID\" --scope \"\$ACR_ID\" -o table"
        echo
        echo "End of report."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
