> ## 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.

# Prefer Using Dedicated Amazon Eks Service Accounts

### More Info:

Kubernetes workloads should not use cluster node service accounts to authenticate to Amazon EKS APIs. Each Kubernetes workload that needs to authenticate to other AWS services using AWS IAM should be provisioned with a dedicated service account.

### Risk Level

High

### Address

Security

### Compliance Standards

* APRA CPS 234 (Australia)
* BSI C5 (Germany)
* Brazil LGPD
* CCPA / CPRA (California)
* CIS Critical Security Controls v8
* CIS EKS
* CMMC 2.0
* CSA Cloud Controls Matrix v4
* DPDPA
* Digital Operational Resilience Act (EU)
* Essential 8
* ISO/IEC 27017
* ISO/IEC 27018
* ISO/IEC 27701
* KSA PDPL
* MAS Technology Risk Management (Singapore)
* MITRE ATT\&CK (Cloud)
* NIST SP 800-171
* NYDFS 23 NYCRR 500
* SWIFT Customer Security Controls Framework
* Sarbanes-Oxley IT General Controls
* UK NCSC Cyber Assessment Framework

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify pods using node IAM role credentials**
           * On any machine with `kubectl` access, list all namespaces and pods that appear to use the EC2 instance role (for example via environment variables or mounted credential files):
             ```bash theme={null}
             kubectl get ns
             kubectl get pods -A -o wide
             kubectl get pods -A -o yaml | grep -nE '169\.254\.169\.254|AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN'
             ```
           * In the AWS console, review the IAM policies attached to your EKS worker node instance profile; if they include broad permissions for application services (S3, DynamoDB, SQS, etc.), those workloads are likely relying on node roles.

        2. **Confirm IRSA (IAM Roles for Service Accounts) is enabled for the cluster**
           * On any machine with AWS CLI configured, check for the OIDC provider associated with your EKS cluster (replace the cluster name and region):
             ```bash theme={null}
             aws eks describe-cluster \
               --name my-eks-cluster \
               --region us-east-1 \
               --query "cluster.identity.oidc.issuer" \
               --output text
             ```
           * If this returns `None` or an empty value, create an IAM OIDC provider for the cluster using the AWS console or:
             ```bash theme={null}
             eksctl utils associate-iam-oidc-provider \
               --cluster=my-eks-cluster \
               --region=us-east-1 \
               --approve
             ```

        3. **Select one workload that currently uses node credentials and analyze its access needs**
           * On any machine with `kubectl` access, inspect a representative pod:
             ```bash theme={null}
             kubectl -n my-namespace get pod my-pod -o yaml
             ```
           * Determine what AWS services the application actually needs (for example from code, documentation, or environment variables like `S3_BUCKET`, `QUEUE_URL`).
           * Document the minimal IAM permissions required (actions and resources) for this workload.

        4. **Create a dedicated IAM role and Kubernetes service account for that workload (IRSA)**
           * On any machine with AWS CLI configured, create an IAM role that trusts the EKS OIDC provider and your chosen service account (substitute the actual OIDC URL, namespace, and service account name):
             ```bash theme={null}
             OIDC_URL="$(aws eks describe-cluster \
               --name my-eks-cluster \
               --region us-east-1 \
               --query "cluster.identity.oidc.issuer" \
               --output text)"
             ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"

             cat > trust-policy.json << 'EOF'
             {
               "Version": "2012-10-17",
               "Statement": [
                 {
                   "Effect": "Allow",
                   "Principal": {
                     "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/OIDC_HOST"
                   },
                   "Action": "sts:AssumeRoleWithWebIdentity",
                   "Condition": {
                     "StringEquals": {
                       "OIDC_HOST:sub": "system:serviceaccount:my-namespace:my-sa"
                     }
                   }
                 }
               ]
             }
             EOF

             # Replace ACCOUNT_ID and OIDC_HOST placeholders
             sed -i "s/ACCOUNT_ID/${ACCOUNT_ID}/g" trust-policy.json
             sed -i "s/OIDC_HOST/${OIDC_URL#https:\/\/}/g" trust-policy.json

             aws iam create-role \
               --role-name my-workload-role \
               --assume-role-policy-document file://trust-policy.json

             # Attach least-privilege policy (use a custom policy ARN you’ve created)
             aws iam attach-role-policy \
               --role-name my-workload-role \
               --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/my-workload-policy
             ```
           * On any machine with `kubectl` access, create/annotate the Kubernetes service account to use this role:
             ```bash theme={null}
             kubectl create namespace my-namespace --dry-run=client -o yaml | kubectl apply -f -

             kubectl apply -f - << 'EOF'
             apiVersion: v1
             kind: ServiceAccount
             metadata:
               name: my-sa
               namespace: my-namespace
               annotations:
                 eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/my-workload-role
             EOF
             ```
             Replace `ACCOUNT_ID` with your actual account ID.

        5. **Migrate the workload to use the dedicated service account instead of node credentials**
           * On any machine with `kubectl` access, patch or update the workload spec to reference the new service account and remove explicit long-term credentials or metadata-URL usage from the pod spec:
             ```bash theme={null}
             kubectl -n my-namespace patch deployment my-deployment \
               -p '{"spec":{"template":{"spec":{"serviceAccountName":"my-sa"}}}}'
             ```
           * Ensure application code is using standard AWS SDK credential resolution (no hard-coded keys, no direct calls to `169.254.169.254`) so it automatically uses the IRSA-provided credentials.

        6. **Verify correct use of dedicated service accounts and plan node-role permissions reduction**
           * On any machine with `kubectl` access, confirm the running pod uses the intended service account:
             ```bash theme={null}
             kubectl -n my-namespace get pod -l app=my-app -o jsonpath='{.items[0].spec.serviceAccountName}'; echo
             ```
           * Inside the pod, validate AWS identity (replace pod name and container as appropriate):
             ```bash theme={null}
             kubectl -n my-namespace exec -it my-pod -- aws sts get-caller-identity
             ```
             The returned ARN should be the IRSA role (for example `arn:aws:iam::ACCOUNT_ID:role/my-workload-role`), not the node instance profile.
           * Once a sufficient number of workloads have migrated to IRSA, review and tighten the IAM policy attached to the EKS node instance role in the AWS console or via:
             ```bash theme={null}
             aws iam list-attached-role-policies --role-name my-node-instance-role
             ```
             Detach or narrow any application-specific permissions so that pods are no longer relying on the node role for AWS API access.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl can’t change how pods obtain AWS credentials or how IAM roles are bound to service accounts; this is configured in the EKS control plane and AWS IAM (console/CLI/IaC). To address this finding, follow the guidance in the Manual Steps section using AWS tooling rather than kubectl.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Report pods that may be using node IAM roles instead of dedicated IRSA roles
        # Run on: any machine with kubectl access and AWS CLI configured
        # Requirements: kubectl, jq, aws

        set -euo pipefail

        # --- configuration (edit as needed) ---
        # Namespace(s) to include; leave empty for all namespaces
        NAMESPACES=""     # e.g. "default kube-system app-namespace"
        # Known service accounts that SHOULD have IRSA roles (optional allowlist)
        KNOWN_IRSA_SA=""  # e.g. "kube-system:external-dns prod:app-sa"

        echo "== Detect pods likely using node IAM roles (no IRSA) ==" >&2

        # Helper: list namespaces
        if [[ -z "${NAMESPACES}" ]]; then
          mapfile -t NS_LIST < <(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
        else
          read -ra NS_LIST <<< "${NAMESPACES}"
        fi

        tmpdir="$(mktemp -d)"
        trap 'rm -rf "${tmpdir}"' EXIT

        # 1. Identify service accounts that are configured for IRSA (have eks.amazonaws.com/role-arn)
        echo "== ServiceAccounts with IRSA annotation (eks.amazonaws.com/role-arn) =="

        for ns in "${NS_LIST[@]}"; do
          kubectl get sa -n "${ns}" -o json \
          | jq -r --arg ns "${ns}" '
              .items[]
              | select(.metadata.annotations["eks.amazonaws.com/role-arn"] != null)
              | "\($ns):\(.metadata.name) -> \(.metadata.annotations["eks.amazonaws.com/role-arn"])"
            ' || true
        done | tee "${tmpdir}/irsa-sas.txt"

        echo
        echo "== Pods and their service accounts (cluster-wide) =="

        # 2. List all pods and their SAs, marking whether the SA has IRSA
        {
          printf "NAMESPACE\tPOD\tSERVICE_ACCOUNT\tHAS_IRSA_SA_ANNOTATION\n"
          for ns in "${NS_LIST[@]}"; do
            kubectl get pods -n "${ns}" -o json \
            | jq -r --arg ns "${ns}" '
                .items[]
                | [
                    .metadata.namespace,
                    .metadata.name,
                    (.spec.serviceAccountName // "default"),
                    (if .spec.serviceAccountName == null then "UNKNOWN"
                     else "CHECK"
                     end)
                  ]
                  | @tsv
              ' || true
          done
        } | while IFS=$'\t' read -r ns pod sa flag; do
          if [[ "${ns}" == "NAMESPACE" ]]; then
            echo -e "${ns}\t${pod}\t${sa}\t${flag}"
            continue
          fi
          # Determine if SA has IRSA annotation
          if kubectl get sa "${sa}" -n "${ns}" -o json 2>/dev/null \
             | jq -e '.metadata.annotations["eks.amazonaws.com/role-arn"]' >/dev/null; then
            has_irsa="yes"
          else
            has_irsa="no"
          fi
          echo -e "${ns}\t${pod}\t${sa}\t${has_irsa}"
        done | tee "${tmpdir}/pods-sa-irsa.txt"

        echo
        echo "== Summary: pods whose service accounts DO NOT have an IRSA role (potentially using node IAM role) =="

        awk 'NR==1 {print; next} $4=="no" {print}' "${tmpdir}/pods-sa-irsa.txt" \
          | tee "${tmpdir}/pods-missing-irsa.txt"

        echo
        echo "== Optional: Check if node IAM role has broad permissions (coarse indicator) =="

        # 3. Identify node IAM role(s) used by the EKS worker nodes (if using EKS-managed or self-managed nodes with IAM roles)
        #    This section is informational; you may need to adapt for your environment.

        # Try to derive the cluster name from current context
        CURRENT_CONTEXT="$(kubectl config current-context 2>/dev/null || true)"
        CLUSTER_NAME=""
        if [[ -n "${CURRENT_CONTEXT}" ]]; then
          # Common eks context pattern: arn:aws:eks:region:account:cluster/CLUSTER_NAME
          CLUSTER_NAME="$(aws eks list-clusters --output json 2>/dev/null \
            | jq -r --arg ctx "${CURRENT_CONTEXT##*/}" '
                .clusters[]
                | select(. == $ctx)
              ' || true)"
        fi

        if [[ -z "${CLUSTER_NAME}" ]]; then
          echo "Could not reliably determine EKS cluster name from kubectl context; skipping node IAM role analysis." >&2
        else
          echo "Using EKS cluster: ${CLUSTER_NAME}" >&2

          # Describe nodegroups and pull node IAM roles (for managed nodegroups)
          echo "Managed nodegroup IAM roles (if any):" >&2
          aws eks list-nodegroups --cluster-name "${CLUSTER_NAME}" --output json \
          | jq -r '.nodegroups[]' \
          | while read -r ng; do
              aws eks describe-nodegroup --cluster-name "${CLUSTER_NAME}" --nodegroup-name "${ng}" --output json \
              | jq -r --arg ng "${ng}" '
                  .nodegroup.nodeRole
                  | "nodegroup \($ng) IAM role: \(. )"
                '
            done 2>/dev/null || true
        fi

        cat <<'EOF'

        INTERPRETING THE OUTPUT:

        1) The "ServiceAccounts with IRSA annotation" section lists all Kubernetes service
           accounts that already have an IAM role associated via the
           eks.amazonaws.com/role-arn annotation. Pods using these SAs are using IRSA.

        2) The "Pods and their service accounts (cluster-wide)" and "Summary" sections:
           - Any pod with HAS_IRSA_SA_ANNOTATION = "no" is a candidate for review.
           - These pods are likely falling back to the node IAM role to access AWS APIs.

        3) A finding (POTENTIAL PROBLEM) is indicated when:
           - A workload requires AWS API access AND
           - Its service account does not have eks.amazonaws.com/role-arn set (HAS_IRSA_SA_ANNOTATION = "no"), AND
           - The node IAM role attached to the worker nodes has broad permissions that are
             not least-privilege relative to that workload.

        NEXT STEPS (MANUAL REVIEW, NOT AUTOMATED FIX):

        - For each pod listed in "pods-missing-irsa.txt":
          * Determine whether the application inside actually calls AWS APIs
            (review code, images, environment variables, configuration).
          * If it does, plan to:
              - Create a dedicated Kubernetes service account for that workload.
              - Create an IAM role with least-privilege permissions.
              - Annotate the service account with eks.amazonaws.com/role-arn.
              - Update the workload spec to use that service account.
          * If it does not use AWS at all, no change is required for this control.

        EOF
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)
