> ## 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. On any machine with kubectl access, list the non-compliant pods and choose one to review (replace NAMESPACE and POD\_NAME in the next steps accordingly):
           ```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)
             | "ns=\($m.namespace) name=\($m.name)"
             ][]'
           ```

        2. Still on any machine with kubectl access, inspect the chosen pod to determine whether it legitimately calls the Kubernetes API (look for in-cluster client libraries, API server URLs, or service account token usage in args/env/config):
           ```bash theme={null}
           kubectl -n NAMESPACE get pod POD_NAME -o yaml
           ```
           If the workload needs to call the Kubernetes API, document the exception and do not change `automountServiceAccountToken` for this pod.

        3. If the pod does not need Kubernetes API access and is controlled by a higher-level object (Deployment, StatefulSet, DaemonSet, Job, CronJob, etc.), identify that owner:
           ```bash theme={null}
           kubectl -n NAMESPACE get pod POD_NAME -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{"\n"}'
           ```
           Then edit the owner resource’s pod template to disable token automount:
           ```bash theme={null}
           kubectl -n NAMESPACE edit OWNER_KIND OWNER_NAME
           ```
           In the opened YAML, under `spec.template.spec`, add or set:
           ```yaml theme={null}
           automountServiceAccountToken: false
           ```
           Save and exit to trigger a rolling update of the pods.

        4. If the pod is not controlled by a higher-level object (no ownerReferences or kind is “Pod”), edit the pod spec directly (note this will not persist across re-creates from external systems):
           ```bash theme={null}
           kubectl -n NAMESPACE edit pod POD_NAME
           ```
           Under `spec`, add or set:
           ```yaml theme={null}
           automountServiceAccountToken: false
           ```

        5. As an alternative (and where appropriate), you may set this at the ServiceAccount level so all pods using it disable token automount by default. On any machine with kubectl access:
           ```bash theme={null}
           kubectl -n NAMESPACE edit serviceaccount SERVICEACCOUNT_NAME
           ```
           Add or set:
           ```yaml theme={null}
           automountServiceAccountToken: false
           ```
           Then ensure pods that should *not* have tokens use this ServiceAccount in their pod templates.

        6. Verify compliance 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
             | "kind=Pod ns=\($m.namespace) name=\($m.name) 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'
           ```
           Confirm that pods which do not need API access now show `automountServiceAccountToken=false` and `is_compliant=true`.
      </Accordion>

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

        1. Identify the noncompliant pod and its owner (from the audit output), for example:
           * Namespace: `my-namespace`
           * Pod name: `my-app-6f7b9d8c7d-abcde`
           * Owner: `Deployment/my-app`

        2. Export the owning workload manifest (example for a Deployment):

        ```bash theme={null}
        kubectl -n my-namespace get deployment my-app -o yaml > my-app-deployment.yaml
        ```

        3. Edit the manifest locally (`my-app-deployment.yaml`) and set `automountServiceAccountToken: false` in the pod spec. For example:

        ```yaml theme={null}
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: my-app
          namespace: my-namespace
        spec:
          template:
            spec:
              automountServiceAccountToken: false
              containers:
                - name: my-app
                  image: myregistry/my-app:1.0.0
        ```

        4. Apply the updated manifest:

        ```bash theme={null}
        kubectl apply -f my-app-deployment.yaml
        ```

        5. (Optional) If the pod is created directly (no owner), patch it in place:

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

        6. Verification (same style as the audit, on any kubectl machine):

        ```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
        set -euo pipefail

        # This script:
        # - Lists all Pods that do NOT have automountServiceAccountToken=false set at pod level
        # - Skips kube-system, kube-public, kube-node-lease
        # - For each such Pod whose owner is a higher-level controller (Deployment, StatefulSet, etc.),
        #   it PATCHes the owning workload spec.template to set automountServiceAccountToken=false.
        # - It does NOT touch bare Pods (no controller ownerReference), because they may be ephemeral or system-created.
        #
        # Run on: any machine with kubectl access to the AKS cluster.
        # Requirements: kubectl, jq

        # Safety guard: ensure we can reach the cluster
        kubectl version --short >/dev/null

        echo "Discovering non-compliant Pods (excluding kube-system, kube-public, kube-node-lease)..."

        NON_COMPLIANT_JSON=$(kubectl get pods --all-namespaces -o json | jq -c '
          .items[]
          | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
          | select(.spec.automountServiceAccountToken != false)
          | {
              namespace: .metadata.namespace,
              pod: .metadata.name,
              owner: (
                [(.metadata.ownerReferences // [])[] | select(.controller)] | first
              )
            }
          ')

        if [[ -z "${NON_COMPLIANT_JSON}" ]]; then
          echo "No non-compliant Pods found. Nothing to do."
          exit 0
        fi

        echo "Evaluating Pods and their owners..."
        echo

        # Helper: map Pod owner.kind to the scalable controller resource that owns the Pod template
        map_owner_kind_to_resource() {
          local kind="$1"
          case "${kind}" in
            Deployment) echo "deployments" ;;
            ReplicaSet) echo "replicasets" ;;
            StatefulSet) echo "statefulsets" ;;
            DaemonSet) echo "daemonsets" ;;
            Job) echo "jobs" ;;
            CronJob) echo "cronjobs" ;;
            *)
              # Unknown or bare Pod owner; return empty to skip
              echo ""
              ;;
          esac
        }

        # Process each Pod
        while IFS= read -r item; do
          ns=$(jq -r '.namespace' <<< "${item}")
          pod=$(jq -r '.pod' <<< "${item}")
          owner_present=$(jq -r 'has("owner") and .owner != null' <<< "${item}")

          if [[ "${owner_present}" != "true" ]]; then
            echo "Skipping bare Pod ${ns}/${pod} (no controller ownerReference). Please review manually if it should disable token automount."
            continue
          fi

          owner_kind=$(jq -r '.owner.kind' <<< "${item}")
          owner_name=$(jq -r '.owner.name' <<< "${item}")

          resource=$(map_owner_kind_to_resource "${owner_kind}")
          if [[ -z "${resource}" ]]; then
            echo "Skipping Pod ${ns}/${pod} (owner kind ${owner_kind} not handled by this script). Review manually."
            continue
          fi

          echo "Processing ${owner_kind} ${ns}/${owner_name} (from Pod ${pod})..."

          # Patch the controller's Pod template to set automountServiceAccountToken=false
          # This is idempotent: repeated patches keep the same value.
          patch='{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'

          kubectl -n "${ns}" patch "${resource}" "${owner_name}" --type=merge -p "${patch}" >/dev/null

          echo "  Patched ${owner_kind} ${ns}/${owner_name} to set spec.template.spec.automountServiceAccountToken=false"
        done <<< "${NON_COMPLIANT_JSON}"

        echo
        echo "Waiting briefly for controllers to reconcile and new Pods to appear..."
        sleep 10

        echo
        echo "Re-running compliance audit to verify..."

        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>
