> ## 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 Amazon ECR

### More Info:

Restrict IAM user and role access to Amazon ECR to only what is required, using least-privilege IAM policies.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* CIS EKS

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Inventory who can access ECR**
           * On any machine with AWS CLI configured for the account:
             ```bash theme={null}
             aws ecr describe-repositories --region us-east-1
             aws iam list-users
             aws iam list-roles
             ```
           * In the AWS Console: IAM → Access Analyzer → Analyze policies (optional) to see which principals have ECR access.

        2. **Identify IAM policies that grant ECR permissions**
           * List all policies that mention `ecr:` actions:
             ```bash theme={null}
             aws iam list-policies --scope Local --only-attached \
               --query 'Policies[].Arn' --output text | tr '\t' '\n' | while read arn; do
                 aws iam get-policy-version \
                   --policy-arn "$arn" \
                   --version-id "$(aws iam get-policy --policy-arn "$arn" --query 'Policy.DefaultVersionId' --output text)" \
                 | jq -e '.PolicyVersion.Document.Statement[]?.Action? | tostring | test("ecr:")' >/dev/null && echo "$arn"
               done
             ```
           * For each returned policy ARN, inspect details:
             ```bash theme={null}
             aws iam get-policy --policy-arn <POLICY_ARN>
             aws iam get-policy-version \
               --policy-arn <POLICY_ARN> \
               --version-id <VERSION_ID>
             ```

        3. **Review scope for over-privilege**
           * For each policy, check for:
             * Wildcard actions (e.g., `"Action": "ecr:*"`).
             * Wildcard resources (e.g., `"Resource": "*"`, or all repositories).
             * Principals that do not need ECR access (users/roles not tied to CI/CD or workloads that pull/push images).
           * Use console: IAM → Policies → (select policy) → JSON tab; verify actions and resources are narrowed to required repositories and operations.

        4. **Redesign least-privilege policies based on actual need**
           * Determine for each user/role what they must do:
             * **Pull only** (e.g., node roles, application pods): `ecr:GetAuthorizationToken`, `ecr:BatchCheckLayerAvailability`, `ecr:GetDownloadUrlForLayer`, `ecr:BatchGetImage`.
             * **Push & pull (CI/CD)**: above plus `ecr:PutImage`, `ecr:InitiateLayerUpload`, `ecr:UploadLayerPart`, `ecr:CompleteLayerUpload`, `ecr:ListImages`.
           * Create a new scoped policy (example for one repository) with the console or CLI:
             ```bash theme={null}
             cat > ecr-push-pull-single-repo.json << 'EOF'
             {
               "Version": "2012-10-17",
               "Statement": [
                 {
                   "Effect": "Allow",
                   "Action": [
                     "ecr:GetAuthorizationToken"
                   ],
                   "Resource": "*"
                 },
                 {
                   "Effect": "Allow",
                   "Action": [
                     "ecr:BatchCheckLayerAvailability",
                     "ecr:GetDownloadUrlForLayer",
                     "ecr:BatchGetImage",
                     "ecr:PutImage",
                     "ecr:InitiateLayerUpload",
                     "ecr:UploadLayerPart",
                     "ecr:CompleteLayerUpload",
                     "ecr:ListImages"
                   ],
                   "Resource": "arn:aws:ecr:us-east-1:<ACCOUNT_ID>:repository/<REPOSITORY_NAME>"
                 }
               ]
             }
             EOF
             aws iam create-policy \
               --policy-name ecr-push-pull-single-repo \
               --policy-document file://ecr-push-pull-single-repo.json
             ```

        5. **Attach new scoped policies and remove broad ones**
           * Attach the least-privilege policy to only those users/roles that require it:
             ```bash theme={null}
             aws iam attach-role-policy \
               --role-name <ROLE_NAME> \
               --policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/ecr-push-pull-single-repo

             aws iam attach-user-policy \
               --user-name <USER_NAME> \
               --policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/ecr-pull-only-single-repo
             ```
           * Detach or delete overly broad policies (after verifying no dependency):
             ```bash theme={null}
             aws iam list-entities-for-policy --policy-arn <BROAD_POLICY_ARN>
             aws iam detach-role-policy --role-name <ROLE_NAME> --policy-arn <BROAD_POLICY_ARN>
             aws iam detach-user-policy --user-name <USER_NAME> --policy-arn <BROAD_POLICY_ARN>
             # Delete only when no entities are attached:
             aws iam delete-policy --policy-arn <BROAD_POLICY_ARN>
             ```

        6. **Re-verify effective access to ECR**
           * Re-run the policy search to ensure no remaining broad access:
             ```bash theme={null}
             aws iam list-policies --scope Local --only-attached \
               --query 'Policies[].Arn' --output text | tr '\t' '\n' | while read arn; do
                 aws iam get-policy-version \
                   --policy-arn "$arn" \
                   --version-id "$(aws iam get-policy --policy-arn "$arn" --query 'Policy.DefaultVersionId' --output text)" \
                 | jq -e '.PolicyVersion.Document.Statement[]? 
                          | select((.Action|tostring|test("ecr:")) and (.Resource=="*"))' \
                   >/dev/null && echo "Policy with broad ECR access: $arn"
               done
             ```
           * Optionally, use IAM → Access Analyzer (AWS Console) to confirm there are no unintended principals with ECR permissions beyond what is required.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot be used to change IAM user or role permissions for Amazon ECR, because those are configured at the AWS account level via IAM (console, AWS CLI, or IaC such as CloudFormation/Terraform), not through Kubernetes API objects. Please refer to the Manual Steps section for guidance on reviewing and tightening ECR-related IAM access.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Report Amazon ECR-related IAM access for review (CISEKS 5.1.2)
        #
        # Requirements:
        # - AWS CLI v2 configured with credentials that can list IAM and ECR resources
        # - jq installed
        #
        # Run location:
        # - Any machine with AWS CLI access to the target account(s)

        set -euo pipefail

        ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
        REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-us-east-1}}"

        echo "=== Context ==="
        echo "Account: ${ACCOUNT_ID}"
        echo "Region : ${REGION}"
        echo

        ###############################################################################
        # 1. List IAM policies that contain ECR actions
        ###############################################################################
        echo "=== IAM customer-managed policies with ECR permissions ==="

        aws iam list-policies --scope Local --no-paginate \
          --query 'Policies[].Arn' --output text | tr '\t' '\n' | while read -r POLICY_ARN; do
          [ -z "$POLICY_ARN" ] && continue
          VERSION_ID="$(aws iam get-policy --policy-arn "$POLICY_ARN" \
            --query 'Policy.DefaultVersionId' --output text)"

          aws iam get-policy-version --policy-arn "$POLICY_ARN" --version-id "$VERSION_ID" \
            --query '{Arn:PolicyArn,Document:PolicyVersion.Document}' --output json |
            jq -r '
              . as $root
              | $root.Document.Statement[]
              | select(
                  (.Action|tostring|test("ecr:", "i")) or
                  (try (.Resource|tostring|test("ecr", "i")) catch false)
                )
              | "Policy: \($root.Arn)\n  Effect : \(.Effect)\n  Actions: \(.Action)\n  Resources: \(.Resource // "*")\n"
            ' || true
        done

        cat <<'EOF'

        # REVIEW GUIDANCE (policies)
        - Problematic indicators:
          - Action includes overly broad patterns, e.g.:
              "ecr:*"
              "ecr:Get*"
              "ecr:Batch*"
          - Resource is "*" or all repositories:
              "Resource": "*"
              "arn:aws:ecr:*:<account>:repository/*"
          - Policies attached to many roles/users, especially generic ones (e.g. "dev-role", "ci-role", "admin-ci").

        - Preferred patterns:
          - Only necessary actions (e.g. "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability",
            "ecr:BatchGetImage", "ecr:PutImage", "ecr:InitiateLayerUpload", "ecr:UploadLayerPart",
            "ecr:CompleteLayerUpload") per use case.
          - Repository-level ARNs instead of "*", e.g.:
              "arn:aws:ecr:REGION:ACCOUNT:repository/app-backend"

        EOF

        ###############################################################################
        # 2. Show which roles/users/groups have ECR policies attached
        ###############################################################################
        echo "=== IAM roles with attached policies that may include ECR permissions ==="

        aws iam list-roles --no-paginate --query 'Roles[].RoleName' --output text \
          | tr '\t' '\n' | while read -r ROLE; do
          [ -z "$ROLE" ] && continue
          ATTACHED=$(aws iam list-attached-role-policies --role-name "$ROLE" \
            --query 'AttachedPolicies[].PolicyArn' --output text 2>/dev/null || true)
          INLINE=$(aws iam list-role-policies --role-name "$ROLE" \
            --query 'PolicyNames[]' --output text 2>/dev/null || true)

          HAS_ECR=false

          for P in $ATTACHED; do
            VERSION_ID="$(aws iam get-policy --policy-arn "$P" \
              --query 'Policy.DefaultVersionId' --output text 2>/dev/null || true)"
            [ -z "$VERSION_ID" ] && continue
            if aws iam get-policy-version --policy-arn "$P" --version-id "$VERSION_ID" \
                --query 'PolicyVersion.Document.Statement[].Action' --output json 2>/dev/null \
                | jq -e 'tostring|test("ecr:", "i")' >/dev/null 2>&1; then
              HAS_ECR=true
              break
            fi
          done

          if [ "$HAS_ECR" = false ] && [ -n "$INLINE" ]; then
            for NAME in $INLINE; do
              if aws iam get-role-policy --role-name "$ROLE" --policy-name "$NAME" \
                  --query 'PolicyDocument.Statement[].Action' --output json 2>/dev/null \
                  | jq -e 'tostring|test("ecr:", "i")' >/dev/null 2>&1; then
                HAS_ECR=true
                break
              fi
            done
          fi

          if [ "$HAS_ECR" = true ]; then
            echo "Role: $ROLE"
            echo "  Attached policies:"
            for P in $ATTACHED; do
              echo "    - $P"
            done
            echo "  Inline policies:"
            for NAME in $INLINE; do
              echo "    - $NAME"
            done
            echo
          fi
        done

        echo
        echo "=== IAM users with attached policies that may include ECR permissions ==="

        aws iam list-users --no-paginate --query 'Users[].UserName' --output text \
          | tr '\t' '\n' | while read -r USER; do
          [ -z "$USER" ] && continue
          ATTACHED=$(aws iam list-attached-user-policies --user-name "$USER" \
            --query 'AttachedPolicies[].PolicyArn' --output text 2>/dev/null || true)
          INLINE=$(aws iam list-user-policies --user-name "$USER" \
            --query 'PolicyNames[]' --output text 2>/dev/null || true)

          HAS_ECR=false

          for P in $ATTACHED; do
            VERSION_ID="$(aws iam get-policy --policy-arn "$P" \
              --query 'Policy.DefaultVersionId' --output text 2>/dev/null || true)"
            [ -z "$VERSION_ID" ] && continue
            if aws iam get-policy-version --policy-arn "$P" --version-id "$VERSION_ID" \
                --query 'PolicyVersion.Document.Statement[].Action' --output json 2>/dev/null \
                | jq -e 'tostring|test("ecr:", "i")' >/dev/null 2>&1; then
              HAS_ECR=true
              break
            fi
          done

          if [ "$HAS_ECR" = false ] && [ -n "$INLINE" ]; then
            for NAME in $INLINE; do
              if aws iam get-user-policy --user-name "$USER" --policy-name "$NAME" \
                  --query 'PolicyDocument.Statement[].Action' --output json 2>/dev/null \
                  | jq -e 'tostring|test("ecr:", "i")' >/dev/null 2>&1; then
                HAS_ECR=true
                break
              fi
            done
          fi

          if [ "$HAS_ECR" = true ]; then
            echo "User: $USER"
            echo "  Attached policies:"
            for P in $ATTACHED; do
              echo "    - $P"
            done
            echo "  Inline policies:"
            for NAME in $INLINE; do
              echo "    - $NAME"
            done
            echo
          fi
        done

        cat <<'EOF'

        # REVIEW GUIDANCE (principals)
        - Problematic indicators:
          - Human IAM users with direct ECR access (prefer roles + SSO/federation).
          - Roles used broadly (e.g. node roles, shared CI roles) with broad ECR permissions.
          - Any role/user that has ECR write/delete capabilities without clear business need:
              ecr:DeleteRepository*
              ecr:BatchDeleteImage
              ecr:PutLifecyclePolicy
              ecr:SetRepositoryPolicy
              ecr:ReplicateImage

        EOF

        ###############################################################################
        # 3. Enumerate repositories and their resource-based policies
        ###############################################################################
        echo "=== ECR repositories and repository policies (resource-based) ==="

        aws ecr describe-repositories --region "$REGION" --no-paginate \
          --query 'repositories[].repositoryName' --output text 2>/dev/null \
          | tr '\t' '\n' | while read -r REPO; do
          [ -z "$REPO" ] && continue
          ARN="$(aws ecr describe-repositories --region "$REGION" \
            --repository-names "$REPO" \
            --query 'repositories[0].repositoryArn' --output text 2>/dev/null || true)"

          echo "Repository: $REPO"
          echo "  ARN: $ARN"

          POLICY_JSON="$(aws ecr get-repository-policy --region "$REGION" \
            --repository-name "$REPO" \
            --query 'policyText' --output text 2>/dev/null || true || true)"

          if [ -z "$POLICY_JSON" ] || [ "$POLICY_JSON" = "None" ]; then
            echo "  Resource policy: <none>"
          else
            echo "  Resource policy statements granting access:"
            echo "$POLICY_JSON" | jq -r '
              .Statement[]
              | "    Effect   : \(.Effect)\n    Principal: \(.Principal)\n    Actions  : \(.Action)\n    Condition: \(.Condition // "{}")\n"
            '
          fi
          echo
        done

        cat <<'EOF'

        # REVIEW GUIDANCE (repository policies)
        - Problematic indicators:
          - "Principal": "*" (public or overly broad access).
          - Principals not controlled by your organization (cross-account) without explicit justification.
          - "Action": "ecr:*" or similar wildcards.
          - Missing restrictive "Condition" blocks when granting cross-account access.

        - Preferred patterns:
          - No resource policies unless explicitly required (use IAM policies instead).
          - If present, tightly scoped principals and actions, with conditions (e.g. aws:PrincipalOrgID).

        EOF

        ###############################################################################
        # 4. (Optional) Mapping to EKS-related roles
        ###############################################################################
        echo "=== EKS node and cluster roles (if present) and their attached policies ==="

        aws eks list-clusters --region "$REGION" --query 'clusters[]' --output text 2>/dev/null \
          | tr '\t' '\n' | while read -r CLUSTER; do
          [ -z "$CLUSTER" ] && continue
          echo "Cluster: $CLUSTER"

          aws eks describe-cluster --name "$CLUSTER" --region "$REGION" --output json \
            | jq -r '
                {
                  clusterRoleArn: .cluster.roleArn,
                  nodeRoles: (try .cluster.resourcesVpcConfig.clusterSecurityGroupId catch null)
                }' 2>/dev/null || true

          echo "  (Review IAM roles used by nodegroups / Fargate profiles separately; ensure they only have minimal ECR access.)"
          echo
        done

        cat <<'EOF'

        INTERPRETING RESULTS:
        - This script does NOT change any configuration.
        - Use it to identify:
          - Policies with broad ECR access.
          - Roles/users with ECR permissions that are unnecessary or too wide.
          - Repository policies that overexpose images.

        ANY OF THE FOLLOWING TYPICALLY INDICATE A PROBLEM:
        - "ecr:*" in Action.
        - "Resource": "*" or "repository/*".
        - Public or "*" Principals in repository policies.
        - Human IAM users with direct ECR push/pull when federation/roles could be used.

        Next steps are manual:
        - Refine policies to least privilege.
        - Scope resources to specific repositories.
        - Remove or tighten unnecessary repository policies.
        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
