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

# Pods That Do Not Use The API Should Disable Token Automount

### More Info:

Verifies automountServiceAccountToken is false for pods that do not call the Kubernetes API. A mounted token is a ready-made credential for an attacker who lands in the pod.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* Cloudanix Best Practice

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. Identify noncompliant Pods and their owners (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get pods --all-namespaces -o json | jq -r '
             [ .items[]
             | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
             | .metadata as $m
             | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
             | (.spec.automountServiceAccountToken == false) as $ok
             | select($ok | not)
             | "ns=\($m.namespace) pod=\($m.name) ownerKind=\($own.kind // "Pod") ownerName=\($own.name // $m.name)"
             ][]'
           ```

        2. For each listed workload, decide if it legitimately calls the Kubernetes API (run on any machine with kubectl access):
           * Inspect container images, args, and env for in-cluster API use:
             ```bash theme={null}
             kubectl -n NAMESPACE get DEPLOYMENT_NAME -o yaml
             ```
           * If the app needs to talk to the API (client libraries, `KUBERNETES_SERVICE_HOST`, in-cluster config, or `curl` to `https://kubernetes.default.svc`), **do not** disable the token; instead, review RBAC separately.
           * Only proceed to the next step for workloads that do **not** need API access.

        3. Disable token automount at the Pod spec level for controller-managed workloads (recommended) (run on any machine with kubectl access, adjust `kind` as appropriate: Deployment, DaemonSet, StatefulSet, Job, etc.):
           ```bash theme={null}
           kubectl -n NAMESPACE patch deployment DEPLOYMENT_NAME \
             --type merge \
             -p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'
           ```
           Example for a StatefulSet:
           ```bash theme={null}
           kubectl -n NAMESPACE patch statefulset STATEFULSET_NAME \
             --type merge \
             -p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'
           ```

        4. For standalone Pods not managed by a controller, recreate them with token automount disabled (run on any machine with kubectl access):
           ```bash theme={null}
           # Export current Pod spec
           kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-POD_NAME.yaml

           # Edit the file locally
           sed -i 's/^spec:/spec:\n  automountServiceAccountToken: false/' /tmp/pod-POD_NAME.yaml

           # Remove status and cluster-assigned fields
           yq -i 'del(.status,.metadata.uid,.metadata.resourceVersion,.metadata.selfLink,.metadata.creationTimestamp,.metadata.managedFields,.metadata.ownerReferences,.metadata.annotations."kubectl.kubernetes.io/last-applied-configuration")' /tmp/pod-POD_NAME.yaml

           # Delete and recreate the Pod
           kubectl -n NAMESPACE delete pod POD_NAME
           kubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml
           ```

        5. Optionally, disable token automount on the ServiceAccount used by multiple non-API workloads (use only if all pods using it do not need the API) (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl -n NAMESPACE patch serviceaccount SERVICEACCOUNT_NAME \
             --type merge \
             -p '{"automountServiceAccountToken":false}'
           ```

        6. Verify compliance (run on any machine with kubectl access):
           ```bash theme={null}
           kubectl get pods --all-namespaces -o json | jq -r '
             [ .items[]
             | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
             | .metadata as $m
             | (.spec.automountServiceAccountToken == false) as $ok
             | select($ok | not)
             ] as $rows
             | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
           ```
           Ensure the output is `is_compliant=true` or that no remaining noncompliant Pods correspond to workloads that can safely disable token automount.
      </Accordion>

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

        1. Identify a noncompliant pod and its owner (from the audit output), for example:
           * Namespace: `prod`
           * Pod name: `web-abc123`
           * Owner: `Deployment/prod/web`

        2. Export the owning workload manifest and edit it locally (example for a Deployment):

        ```bash theme={null}
        kubectl -n prod get deployment web -o yaml > /tmp/deployment-web.yaml
        ```

        3. In `/tmp/deployment-web.yaml`, under `spec.template.spec`, set `automountServiceAccountToken: false`. For example:

        ```yaml theme={null}
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: web
          namespace: prod
        spec:
          replicas: 3
          selector:
            matchLabels:
              app: web
          template:
            metadata:
              labels:
                app: web
            spec:
              automountServiceAccountToken: false
              containers:
                - name: web
                  image: nginx:1.27
        ```

        4. Apply the updated manifest:

        ```bash theme={null}
        kubectl apply -f /tmp/deployment-web.yaml
        ```

        5. If the pod is created directly (no controller), patch it in place (note: this recreates the pod, not the spec from a controller):

        ```bash theme={null}
        kubectl -n prod patch pod web-abc123 \
          --type='merge' \
          -p '{"spec":{"automountServiceAccountToken":false}}'
        ```

        6. If you choose to set this at the ServiceAccount instead (applies to all pods using it):

        ```bash theme={null}
        kubectl -n prod get serviceaccount web-sa -o yaml > /tmp/sa-web-sa.yaml
        ```

        Edit `/tmp/sa-web-sa.yaml`:

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

        Apply:

        ```bash theme={null}
        kubectl apply -f /tmp/sa-web-sa.yaml
        ```

        7. Verification (rerun the audit on any machine with kubectl access):

        ```bash theme={null}
        kubectl get pods --all-namespaces -o json | jq -r '
          [ .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | .metadata as $m
          | (.spec.nodeName // "") as $node
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | (.spec.automountServiceAccountToken == false) as $ok
          | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $node   == ""   then "" else " node=\($node)" end)
            + (if $labels == ""   then "" else " labels=\($labels)" end)
            + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
            + " serviceAccount=\(.spec.serviceAccountName // "default")"
            + " automountServiceAccountToken=\(if .spec.automountServiceAccountToken == null then "unset" else .spec.automountServiceAccountToken end)"
            + " is_compliant=\(if $ok then "true" else "false" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # automate_pod_token_automount_fix.sh
        #
        # Idempotently set automountServiceAccountToken: false on Pods that
        # do NOT call the Kubernetes API, by patching their ServiceAccounts.
        #
        # REQUIREMENTS:
        # - Run on any machine with kubectl and jq installed and kubeconfig set.
        # - This script DOES NOT decide which pods call the API.
        #   You must provide an allowlist of namespaces/pods that legitimately
        #   call the API and should keep their token.

        set -euo pipefail

        #------------------------ CONFIGURATION --------------------------------#

        # Comma-separated list of namespaces that contain API-calling workloads
        # whose pods should NOT be remediated (tokens kept as-is).
        API_CALLER_NAMESPACES="kube-system"

        # Comma-separated list of pod name regexes that call the API and should
        # NOT be remediated (across any namespace), for example:
        # "metrics-server|external-dns|cluster-autoscaler"
        API_CALLER_POD_NAME_REGEX=""

        #------------------------ HELPER FUNCTIONS -----------------------------#

        ns_in_allowlist() {
          local ns="$1"
          IFS=',' read -r -a arr <<< "${API_CALLER_NAMESPACES}"
          for x in "${arr[@]}"; do
            [[ -n "${x}" && "${ns}" == "${x}" ]] && return 0
          done
          return 1
        }

        pod_in_allowlist_regex() {
          local pod="$1"
          [[ -z "${API_CALLER_POD_NAME_REGEX}" ]] && return 1
          if [[ "${pod}" =~ ${API_CALLER_POD_NAME_REGEX} ]]; then
            return 0
          fi
          return 1
        }

        #------------------------ DISCOVER TARGET PODS -------------------------#

        echo "[INFO] Discovering non-compliant pods (ignoring kube-system, kube-public, kube-node-lease)..."

        # Reuse the benchmark logic to find non-compliant pods
        non_compliant_json="$(kubectl get pods --all-namespaces -o json | jq '
          .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | select(.spec.automountServiceAccountToken != false)
          | {namespace: .metadata.namespace,
             name: .metadata.name,
             serviceAccount: (.spec.serviceAccountName // "default") }
        ')"

        if [[ -z "${non_compliant_json}" ]]; then
          echo "[INFO] No pods found (cluster empty?). Nothing to do."
          exit 0
        fi

        targets="$(
          printf '%s\n' "${non_compliant_json}" | jq -r '
            select(.namespace != null)
            | "\(.namespace) \(.name) \(.serviceAccount)"
          ' | sort -u
        )"

        if [[ -z "${targets}" ]]; then
          echo "[INFO] No non-compliant pods detected. Nothing to do."
          exit 0
        fi

        echo "[INFO] Candidate non-compliant pod tuples (ns pod serviceAccount):"
        echo "${targets}"

        #------------------------ PATCH SERVICEACCOUNTS ------------------------#

        echo "[INFO] Patching ServiceAccounts (setting automountServiceAccountToken=false) where appropriate..."

        while read -r ns pod sa; do
          [[ -z "${ns}" ]] && continue

          # Skip namespaces that you consider API callers
          if ns_in_allowlist "${ns}"; then
            echo "[SKIP] ${ns}/${pod}: namespace in API_CALLER_NAMESPACES"
            continue
          fi

          # Skip pods matching explicit allowlist regex
          if pod_in_allowlist_regex "${pod}"; then
            echo "[SKIP] ${ns}/${pod}: pod matches API_CALLER_POD_NAME_REGEX"
            continue
          fi

          # Ensure ServiceAccount exists
          if ! kubectl get serviceaccount "${sa}" -n "${ns}" >/dev/null 2>&1; then
            echo "[WARN] ${ns}/${pod}: ServiceAccount ${sa} not found, skipping"
            continue
          fi

          # Check current SA setting; avoid unnecessary patch
          current="$(kubectl get serviceaccount "${sa}" -n "${ns}" -o json | jq -r '.automountServiceAccountToken // "unset"')"
          if [[ "${current}" == "false" ]]; then
            echo "[OK]   ${ns}/sa/${sa}: automountServiceAccountToken already false"
            continue
          fi

          echo "[ACT]  Patching ServiceAccount ${ns}/${sa} (set automountServiceAccountToken=false)..."
          kubectl patch serviceaccount "${sa}" -n "${ns}" \
            --type merge \
            -p '{"automountServiceAccountToken": false}' >/dev/null

        done <<< "${targets}"

        #------------------------ VERIFICATION ---------------------------------#

        echo "[INFO] Waiting briefly for pods to be recreated with new SA settings (if controllers roll them)..."
        sleep 10

        echo "[INFO] Re-running benchmark-style audit to verify compliance..."

        kubectl get pods --all-namespaces -o json | jq -r '
          [ .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | .metadata as $m
          | (.spec.nodeName // "") as $node
          | (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
          | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
          | (.spec.automountServiceAccountToken == false) as $ok
          | "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
            + (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
            + (if $node   == ""   then "" else " node=\($node)" end)
            + (if $labels == ""   then "" else " labels=\($labels)" end)
            + (if $own    == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
            + " serviceAccount=\(.spec.serviceAccountName // "default")"
            + " automountServiceAccountToken=\(if .spec.automountServiceAccountToken == null then "unset" else .spec.automountServiceAccountToken end)"
            + " is_compliant=\(if $ok then "true" else "false" end)"
          ] as $rows
          | if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
