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

# Ensure That Service Account Tokens Are Only Mounted Where Necessary

### More Info:

Automatically mounting service account tokens into pods that do not need API access widens the attack surface. Set automountServiceAccountToken to false unless the workload requires API server access.

### 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. List service accounts and identify which workloads truly need API access (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get sa -A -o wide
           ```
           For each service account in application namespaces, confirm with the application owner whether the pod needs to talk to the Kubernetes API. Only those should keep `automountServiceAccountToken: true` or default behavior.

        2. For a service account that does NOT need API access, patch it to disable token mounting (run on any machine with kubectl access, replace NAMESPACE and SA\_NAME):
           ```bash theme={null}
           kubectl patch sa SA_NAME -n NAMESPACE -p '{"automountServiceAccountToken": false}'
           ```

        3. For pods that explicitly do NOT need API access but still rely on the default service account, create a dedicated “no-api” service account and disable token mounting on it (run on any machine with kubectl access, replace NAMESPACE and SA\_NAME):
           ```bash theme={null}
           kubectl create sa SA_NAME -n NAMESPACE
           kubectl patch sa SA_NAME -n NAMESPACE -p '{"automountServiceAccountToken": false}'
           ```
           Then, update the pod/deployment/daemonset/statefulset spec to use this service account:
           ```bash theme={null}
           kubectl patch deployment DEPLOYMENT_NAME -n NAMESPACE \
             --type='strategic' \
             -p '{"spec":{"template":{"spec":{"serviceAccountName":"SA_NAME"}}}}'
           ```

        4. For workloads that DO require API access, leave their service accounts as-is, but ensure least privilege by checking and minimizing their RBAC bindings (run on any machine with kubectl access, replace NAMESPACE and SA\_NAME):
           ```bash theme={null}
           kubectl get rolebinding,clusterrolebinding -A \
             -o jsonpath='{range .items[?(@.subjects[*].name=="SA_NAME")]}{.kind}{" "}{.metadata.name}{" in "}{.metadata.namespace}{"\n"}{end}'
           ```
           Adjust Role/ClusterRole and bindings to grant only the necessary verbs and resources.

        5. For any pod spec that explicitly sets `automountServiceAccountToken: true` but does NOT need API access, update it to false (run on any machine with kubectl access, replace kind/name/namespace):
           ```bash theme={null}
           kubectl get deployment DEPLOYMENT_NAME -n NAMESPACE -o yaml > /tmp/deploy.yaml
           # Edit /tmp/deploy.yaml: under spec.template.spec set:
           # automountServiceAccountToken: false
           kubectl apply -f /tmp/deploy.yaml
           ```

        6. Verify that only pods which truly need API access are mounting tokens (run on any machine with kubectl access):
           ```bash theme={null}
           pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '
             [.items[] | select(.spec.automountServiceAccountToken != false)] | length')

           echo "Pods with automountServiceAccountToken not set to false: $pods_with_token_mount"
           ```
      </Accordion>

      <Accordion title="Using kubectl">
        On any machine with kubectl access:

        1. Identify pods that should not have the token mounted but do

        ```bash theme={null}
        kubectl get pods --all-namespaces -o wide
        ```

        Manually determine which of these workloads do not need Kubernetes API access.

        2. For each workload that does NOT need API access, edit its Pod spec to disable the token mount. Prefer editing the owning controller (Deployment, DaemonSet, StatefulSet, Job, CronJob) rather than the live Pod.

        Example: patch a Deployment’s pod template

        ```bash theme={null}
        kubectl -n NAMESPACE patch deployment DEPLOYMENT_NAME \
          --type merge \
          -p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'
        ```

        Example: patch a ServiceAccount so that any pods using it do NOT auto‑mount the token

        ```bash theme={null}
        kubectl -n NAMESPACE patch serviceaccount SERVICEACCOUNT_NAME \
          --type merge \
          -p '{"automountServiceAccountToken":false}'
        ```

        3. Declarative manifest snippets (for future or GitOps use)

        In a ServiceAccount manifest:

        ```yaml theme={null}
        apiVersion: v1
        kind: ServiceAccount
        metadata:
          name: example-sa
          namespace: default
        automountServiceAccountToken: false
        ```

        In a controller’s Pod template (e.g., Deployment):

        ```yaml theme={null}
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: example-deployment
          namespace: default
        spec:
          replicas: 1
          selector:
            matchLabels:
              app: example
          template:
            metadata:
              labels:
                app: example
            spec:
              automountServiceAccountToken: false
              serviceAccountName: example-sa
              containers:
              - name: example
                image: nginx:stable
        ```

        Apply:

        ```bash theme={null}
        kubectl apply -f PATH/TO/MANIFEST.yaml
        ```

        4. Verification (same logic as the audit)

        ```bash theme={null}
        pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '
          [.items[] | select(.spec.automountServiceAccountToken != false)] | length')

        echo "$pods_with_token_mount pods with automountServiceAccountToken not explicitly set to false"
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Disable automountServiceAccountToken on ServiceAccounts and Pods that
        # do NOT require API server access by default, while allowing explicit
        # opt-in where needed.
        #
        # Usage: run on any machine with kubectl, jq, and bash installed, with
        #        sufficient RBAC to patch pods and serviceaccounts cluster-wide.
        #
        # Idempotent: safe to re-run; only updates objects that are not already
        #             configured as desired.

        set -euo pipefail

        # Namespace label key that explicitly marks workloads needing API access.
        # Any namespace with this label set to "true" is skipped.
        NEEDS_API_LABEL_KEY="security.kubernetes.io/needs-api-access"

        # Annotation key on Pods and ServiceAccounts for opt-out/opt-in control.
        # Values:
        #   "true"  -> requires API access (do NOT disable automount)
        #   "false" -> does NOT require API access (force disable automount)
        # Annotation on Pod takes precedence over ServiceAccount if both set.
        NEEDS_API_ANNOTATION_KEY="security.kubernetes.io/needs-api-access"

        echo "=== Step 1: Discovering Pods and ServiceAccounts currently mounting tokens ==="

        pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '
          [.items[] | select(.spec.automountServiceAccountToken != false)] | length')

        echo "Pods with automountServiceAccountToken not explicitly set to false: ${pods_with_token_mount}"

        echo "=== Step 2: Disable automountServiceAccountToken on ServiceAccounts ==="

        # Get all ServiceAccounts in all namespaces
        kubectl get sa --all-namespaces -o json | jq -r '.items[] |
          [.metadata.namespace, .metadata.name] | @tsv' | while IFS=$'\t' read -r ns sa; do

          # Skip namespaces explicitly marked as needing API access
          ns_needs_api=$(kubectl get ns "${ns}" -o jsonpath="{.metadata.labels.${NEEDS_API_LABEL_KEY}}" 2>/dev/null || true)
          if [ "${ns_needs_api}" = "true" ]; then
            continue
          fi

          # Check annotation on the ServiceAccount
          sa_needs_api=$(kubectl get sa "${sa}" -n "${ns}" -o jsonpath="{.metadata.annotations.${NEEDS_API_ANNOTATION_KEY}}" 2>/dev/null || true)

          # If explicitly annotated "true", skip; if annotated "false" or unset, disable automount
          if [ "${sa_needs_api}" = "true" ]; then
            continue
          fi

          # Get current value to keep idempotent
          current=$(kubectl get sa "${sa}" -n "${ns}" -o jsonpath="{.automountServiceAccountToken}" 2>/dev/null || true)
          if [ "${current}" = "false" ]; then
            continue
          fi

          echo "Patching ServiceAccount ${ns}/${sa} to set automountServiceAccountToken=false"
          kubectl patch sa "${sa}" -n "${ns}" --type='merge' -p '{
            "automountServiceAccountToken": false
          }' >/dev/null
        done

        echo "=== Step 3: Disable automountServiceAccountToken on existing Pods where safe ==="

        # Process all pods that do not already have automountServiceAccountToken=false
        kubectl get pods --all-namespaces -o json | jq -r '
          .items[]
          | select(.spec.automountServiceAccountToken != false)
          | [.metadata.namespace, .metadata.name]
          | @tsv' | while IFS=$'\t' read -r ns pod; do

          # Skip namespaces explicitly marked as needing API access
          ns_needs_api=$(kubectl get ns "${ns}" -o jsonpath="{.metadata.labels.${NEEDS_API_LABEL_KEY}}" 2>/dev/null || true)
          if [ "${ns_needs_api}" = "true" ]; then
            continue
          fi

          # Skip pods that are terminating
          phase=$(kubectl get pod "${pod}" -n "${ns}" -o jsonpath='{.status.phase}' 2>/dev/null || true)
          if [ "${phase}" = "Succeeded" ] || [ "${phase}" = "Failed" ]; then
            continue
          fi

          # Check per-pod annotation
          pod_needs_api=$(kubectl get pod "${pod}" -n "${ns}" -o jsonpath="{.metadata.annotations.${NEEDS_API_ANNOTATION_KEY}}" 2>/dev/null || true)
          if [ "${pod_needs_api}" = "true" ]; then
            continue
          fi

          # If pod annotation is "false", we will disable automount.
          # If pod annotation is unset, check its ServiceAccount annotation.
          if [ "${pod_needs_api}" != "false" ]; then
            sa_name=$(kubectl get pod "${pod}" -n "${ns}" -o jsonpath='{.spec.serviceAccountName}' 2>/dev/null || true)
            if [ -n "${sa_name}" ]; then
              sa_needs_api=$(kubectl get sa "${sa_name}" -n "${ns}" -o jsonpath="{.metadata.annotations.${NEEDS_API_ANNOTATION_KEY}}" 2>/dev/null || true)
              if [ "${sa_needs_api}" = "true" ]; then
                continue
              fi
            fi
          fi

          # Try strategic merge patch; may fail for some pod types (e.g. static pods / mirror pods)
          echo "Patching Pod ${ns}/${pod} to set spec.automountServiceAccountToken=false (may trigger restart via controller)"
          set +e
          kubectl patch pod "${pod}" -n "${ns}" --type='merge' -p '{
            "spec": { "automountServiceAccountToken": false }
          }' >/dev/null 2>&1
          rc=$?
          set -e
          if [ $rc -ne 0 ]; then
            echo "  Warning: could not patch pod ${ns}/${pod}; it may be controlled by a higher-level workload. Patch the controller object instead."
          fi
        done

        echo "=== Step 4: Verification ==="

        pods_with_token_mount_after=$(kubectl get pods --all-namespaces -o json | jq '
          [.items[] | select(.spec.automountServiceAccountToken != false)] | length')

        echo "Pods with automountServiceAccountToken not explicitly set to false AFTER remediation: ${pods_with_token_mount_after}"

        if [ "${pods_with_token_mount_after}" -gt 0 ]; then
          echo "Some pods still have automountServiceAccountToken not set to false."
          echo "These are likely in namespaces or workloads explicitly marked as requiring API access,"
          echo "or managed by controllers that must be updated via their manifests/Helm/IaC."
        fi

        echo "Automation complete."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
