> ## 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 Service Account Tokens Are Only Mounted Where Necessary

### More Info:

Pods and service accounts that do not call the API server should not mount service account tokens, reducing the credentials exposed in the workload.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* CIS Kubernetes

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. Identify non-compliant Pods and their ServiceAccounts
           * Run on: any machine with kubectl access
           ```bash theme={null}
           kubectl get pods --all-namespaces -o custom-columns=POD_NAMESPACE:.metadata.namespace,POD_NAME:.metadata.name,POD_SERVICE_ACCOUNT:.spec.serviceAccount,POD_IS_AUTOMOUNTSERVICEACCOUNTTOKEN:.spec.automountServiceAccountToken --no-headers | while read -r pod_namespace pod_name pod_service_account pod_is_automountserviceaccounttoken
           do
             svacc_is_automountserviceaccounttoken=$(kubectl get serviceaccount -n "${pod_namespace}" "${pod_service_account}" -o json | jq -r '.automountServiceAccountToken' | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
             pod_is_automountserviceaccounttoken=$(echo "${pod_is_automountserviceaccounttoken}" | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
             if [ "${svacc_is_automountserviceaccounttoken}" = "false" ] && ( [ "${pod_is_automountserviceaccounttoken}" = "false" ] || [ "${pod_is_automountserviceaccounttoken}" = "notset" ] ); then
               is_compliant="true"
             elif [ "${svacc_is_automountserviceaccounttoken}" = "true" ] && [ "${pod_is_automountserviceaccounttoken}" = "false" ]; then
               is_compliant="true"
             else
               is_compliant="false"
             fi
             echo "**namespace: ${pod_namespace} pod_name: ${pod_name} service_account: ${pod_service_account} pod_is_automountserviceaccounttoken: ${pod_is_automountserviceaccounttoken} svacc_is_automountServiceAccountToken: ${svacc_is_automountserviceaccounttoken} is_compliant: ${is_compliant}"
           done | grep 'is_compliant: false'
           ```
           * Review the output and decide which workloads truly need API server access. Only those should continue to mount tokens.

        2. For Pods that do NOT need API access but MUST keep using their current ServiceAccount
           * Run on: any machine with kubectl access
           * For each such Pod, edit the controller that creates it (Deployment/StatefulSet/DaemonSet/CronJob/Job) and set `spec.template.spec.automountServiceAccountToken: false`. Example for a Deployment:
           ```bash theme={null}
           kubectl -n NAMESPACE edit deployment DEPLOYMENT_NAME
           ```
           * Add or update under `spec.template.spec`:
           ```yaml theme={null}
           spec:
             template:
               spec:
                 automountServiceAccountToken: false
           ```
           * Save and exit; the Pods will be recreated automatically without mounting tokens.

        3. For ServiceAccounts used ONLY by Pods that do NOT need API access
           * Run on: any machine with kubectl access
           * Edit the ServiceAccount to disable token automounting by default:
           ```bash theme={null}
           kubectl -n NAMESPACE edit serviceaccount SERVICEACCOUNT_NAME
           ```
           * Add or update at top-level of the ServiceAccount spec:
           ```yaml theme={null}
           automountServiceAccountToken: false
           ```
           * This makes all Pods using this ServiceAccount compliant as long as their Pod spec does not override with `true`.

        4. For ServiceAccounts shared by Pods that DO and DO NOT need API access
           * Run on: any machine with kubectl access
           * Keep the ServiceAccount as-is (or even `automountServiceAccountToken: true` if needed), and control per-Pod behavior:
             * For Pods that need API access: no change required (they may inherit the default).
             * For Pods that must NOT mount tokens: set `spec.template.spec.automountServiceAccountToken: false` as in Step 2 so the Pod-level value overrides the ServiceAccount.

        5. For static workload manifests managed via files (Git/IaC) rather than `kubectl edit`
           * Run on: any machine with kubectl access and repo access
           * Locate the YAML manifest for each non-compliant workload and modify it directly:
             * In every ServiceAccount that should not mount tokens by default:
               ```yaml theme={null}
               apiVersion: v1
               kind: ServiceAccount
               metadata:
                 name: SERVICEACCOUNT_NAME
                 namespace: NAMESPACE
               automountServiceAccountToken: false
               ```
             * In every Pod template that must explicitly disable mounting:
               ```yaml theme={null}
               spec:
                 serviceAccountName: SERVICEACCOUNT_NAME
                 automountServiceAccountToken: false
               ```
           * Apply the updated manifests:
           ```bash theme={null}
           kubectl apply -f PATH/TO/MANIFEST.yaml
           ```

        6. Verify remediation
           * Run on: any machine with kubectl access
           ```bash theme={null}
           kubectl get pods --all-namespaces -o custom-columns=POD_NAMESPACE:.metadata.namespace,POD_NAME:.metadata.name,POD_SERVICE_ACCOUNT:.spec.serviceAccount,POD_IS_AUTOMOUNTSERVICEACCOUNTTOKEN:.spec.automountServiceAccountToken --no-headers | while read -r pod_namespace pod_name pod_service_account pod_is_automountserviceaccounttoken
           do
             svacc_is_automountserviceaccounttoken=$(kubectl get serviceaccount -n "${pod_namespace}" "${pod_service_account}" -o json | jq -r '.automountServiceAccountToken' | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
             pod_is_automountserviceaccounttoken=$(echo "${pod_is_automountserviceaccounttoken}" | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
             if [ "${svacc_is_automountserviceaccounttoken}" = "false" ] && ( [ "${pod_is_automountserviceaccounttoken}" = "false" ] || [ "${pod_is_automountserviceaccounttoken}" = "notset" ] ); then
               is_compliant="true"
             elif [ "${svacc_is_automountserviceaccounttoken}" = "true" ] && [ "${pod_is_automountserviceaccounttoken}" = "false" ]; then
               is_compliant="true"
             else
               is_compliant="false"
             fi
             echo "**namespace: ${pod_namespace} pod_name: ${pod_name} service_account: ${pod_service_account} pod_is_automountserviceaccounttoken: ${pod_is_automountserviceaccounttoken} svacc_is_automountServiceAccountToken: ${svacc_is_automountserviceaccounttoken} is_compliant: ${is_compliant}"
           done | grep 'is_compliant: false' || echo "All evaluated Pods are compliant"
           ```
      </Accordion>

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

        1. Identify pods and service accounts to change (those that do not need API access but are non‑compliant):

        ```bash theme={null}
        kubectl get pods --all-namespaces -o custom-columns=POD_NAMESPACE:.metadata.namespace,POD_NAME:.metadata.name,POD_SERVICE_ACCOUNT:.spec.serviceAccount,POD_IS_AUTOMOUNTSERVICEACCOUNTTOKEN:.spec.automountServiceAccountToken --no-headers | while read -r pod_namespace pod_name pod_service_account pod_is_automountserviceaccounttoken
        do
          svacc_is_automountserviceaccounttoken=$(kubectl get serviceaccount -n "${pod_namespace}" "${pod_service_account}" -o json | jq -r '.automountServiceAccountToken' | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
          pod_is_automountserviceaccounttoken=$(echo "${pod_is_automountserviceaccounttoken}" | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
          if [ "${svacc_is_automountserviceaccounttoken}" = "false" ] && ( [ "${pod_is_automountserviceaccounttoken}" = "false" ] || [ "${pod_is_automountserviceaccounttoken}" = "notset" ] ); then
            is_compliant="true"
          elif [ "${svacc_is_automountserviceaccounttoken}" = "true" ] && [ "${pod_is_automountserviceaccounttoken}" = "false" ]; then
            is_compliant="true"
          else
            is_compliant="false"
          fi
          echo "**namespace: ${pod_namespace} pod_name: ${pod_name} service_account: ${pod_service_account} pod_is_automountserviceaccounttoken: ${pod_is_automountserviceaccounttoken} svacc_is_automountServiceAccountToken: ${svacc_is_automountserviceaccounttoken} is_compliant: ${is_compliant}"
        done | grep "is_compliant: false"
        ```

        Review the listed workloads and confirm they do not require Kubernetes API access before changing anything.

        2. Patch ServiceAccounts that should never auto‑mount tokens (declarative via kubectl patch):

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

        3. For pods created by controllers (Deployments, DaemonSets, StatefulSets, Jobs, CronJobs), set `automountServiceAccountToken: false` in the pod template. Example for a Deployment:

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

        Example for a DaemonSet:

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

        Example for a StatefulSet:

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

        Example for a Job:

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

        Example for a CronJob:

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

        These patches will roll out new pods with tokens not mounted.

        4. For standalone Pods defined directly (no controller), edit the pod manifest and re‑create it, or patch if acceptable (note: patching an existing running Pod will not change already mounted volumes). Recommended approach:

        * Export, edit, and re‑create:

        ```bash theme={null}
        kubectl get pod my-pod -n my-namespace -o yaml > /tmp/my-pod.yaml
        ```

        Edit `/tmp/my-pod.yaml` so the pod spec contains:

        ```yaml theme={null}
        spec:
          automountServiceAccountToken: false
        ```

        Then delete and recreate:

        ```bash theme={null}
        kubectl delete pod my-pod -n my-namespace
        kubectl apply -f /tmp/my-pod.yaml
        ```

        5. Verification (same logic as audit):

        ```bash theme={null}
        kubectl get pods --all-namespaces -o custom-columns=POD_NAMESPACE:.metadata.namespace,POD_NAME:.metadata.name,POD_SERVICE_ACCOUNT:.spec.serviceAccount,POD_IS_AUTOMOUNTSERVICEACCOUNTTOKEN:.spec.automountServiceAccountToken --no-headers | while read -r pod_namespace pod_name pod_service_account pod_is_automountserviceaccounttoken
        do
          svacc_is_automountserviceaccounttoken=$(kubectl get serviceaccount -n "${pod_namespace}" "${pod_service_account}" -o json | jq -r '.automountServiceAccountToken' | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
          pod_is_automountserviceaccounttoken=$(echo "${pod_is_automountserviceaccounttoken}" | sed -e 's/<none>/notset/g' -e 's/null/notset/g')
          if [ "${svacc_is_automountserviceaccounttoken}" = "false" ] && ( [ "${pod_is_automountserviceaccounttoken}" = "false" ] || [ "${pod_is_automountserviceaccounttoken}" = "notset" ] ); then
            is_compliant="true"
          elif [ "${svacc_is_automountserviceaccounttoken}" = "true" ] && [ "${pod_is_automountserviceaccounttoken}" = "false" ]; then
            is_compliant="true"
          else
            is_compliant="false"
          fi
          echo "**namespace: ${pod_namespace} pod_name: ${pod_name} service_account: ${pod_service_account} pod_is_automountserviceaccounttoken: ${pod_is_automountserviceaccounttoken} svacc_is_automountServiceAccountToken: ${svacc_is_automountserviceaccounttoken} is_compliant: ${is_compliant}"
        done | grep "is_compliant: false" || echo "All pods compliant with automountServiceAccountToken policy"
        ```
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Automation for: Ensure Service Account Tokens Are Only Mounted Where Necessary (CIS 5.1.6)
        # Runs on: any machine with kubectl access and jq installed.
        # Behavior:
        # - Scans all pods
        # - For pods that do NOT need a token (user-provided list), ensures:
        #     * Pod spec has .spec.automountServiceAccountToken=false (by patching the controller)
        #     * The bound ServiceAccount has automountServiceAccountToken=false
        # - Safe to re-run (idempotent)

        set -euo pipefail

        # -----------------------------
        # CONFIGURATION (EDIT REQUIRED)
        # -----------------------------
        # You MUST define which workloads do NOT need API access.
        # Format: "namespace:kind/name" where kind is one of Deployment,StatefulSet,DaemonSet,Job,CronJob
        # Example:
        #   NO_TOKEN_WORKLOADS=(
        #     "default:Deployment/nginx"
        #     "batch:Job/offline-processor"
        #   )
        NO_TOKEN_WORKLOADS=(
          # "default:Deployment/nginx"
        )

        if [ "${#NO_TOKEN_WORKLOADS[@]}" -eq 0 ]; then
          echo "No workloads specified in NO_TOKEN_WORKLOADS. Nothing to do."
          exit 0
        fi

        # --------------
        # PREREQUISITES
        # --------------
        command -v kubectl >/dev/null 2>&1 || { echo "kubectl not found in PATH"; exit 1; }
        command -v jq >/dev/null 2>&1 || { echo "jq not found in PATH"; exit 1; }

        # Ensure we can talk to the cluster
        kubectl version --client >/dev/null 2>&1 || { echo "kubectl not configured correctly"; exit 1; }

        # --------------
        # HELPER FUNCS
        # --------------
        patch_controller() {
          local ns="$1" kind="$2" name="$3"

          # Determine the pod template path for automountServiceAccountToken
          local path
          case "$kind" in
            Deployment|StatefulSet|DaemonSet)
              path=".spec.template.spec"
              ;;
            Job)
              path=".spec.template.spec"
              ;;
            CronJob)
              path=".spec.jobTemplate.spec.template.spec"
              ;;
            *)
              echo "Unsupported controller kind: $kind in ${ns}/${name}, skipping" >&2
              return
              ;;
          esac

          # Check current value
          local current
          current="$(kubectl get "$kind" "$name" -n "$ns" -o json | jq -r "${path}.automountServiceAccountToken // \"notset\"")"

          if [ "$current" = "false" ]; then
            echo "Controller ${kind}/${name} in namespace ${ns}: automountServiceAccountToken already false"
          else
            echo "Patching controller ${kind}/${name} in namespace ${ns} to set automountServiceAccountToken=false"
            kubectl patch "$kind" "$name" -n "$ns" \
              --type='merge' \
              -p "{\"spec\":$(kubectl get \"$kind\" \"$name\" -n \"$ns\" -o json | \
                  jq -c --argjson val false \
                     "$(if [ "$kind" = "CronJob" ]; then
                           echo '.spec.jobTemplate.spec.template.spec.automountServiceAccountToken=$val'
                         else
                           echo '.spec.template.spec.automountServiceAccountToken=$val'
                         fi)")}"
          fi

          # Get serviceAccountName from the controller template
          local sa_name
          sa_name="$(kubectl get "$kind" "$name" -n "$ns" -o json | jq -r "${path}.serviceAccountName // \"default\"")"

          # Ensure ServiceAccount has automountServiceAccountToken=false
          if kubectl get serviceaccount "$sa_name" -n "$ns" >/dev/null 2>&1; then
            local sa_current
            sa_current="$(kubectl get serviceaccount "$sa_name" -n "$ns" -o json | jq -r '.automountServiceAccountToken // "notset"')"
            if [ "$sa_current" = "false" ]; then
              echo "ServiceAccount ${ns}/${sa_name}: automountServiceAccountToken already false"
            else
              echo "Patching ServiceAccount ${ns}/${sa_name} to set automountServiceAccountToken=false"
              kubectl patch serviceaccount "$sa_name" -n "$ns" \
                --type='merge' \
                -p '{"automountServiceAccountToken":false}'
            fi
          else
            echo "ServiceAccount ${ns}/${sa_name} not found (referenced by ${kind}/${name}), skipping SA patch" >&2
          fi
        }

        # --------------
        # MAIN EXECUTION
        # --------------
        echo "Applying automountServiceAccountToken=false to configured workloads and their ServiceAccounts..."

        for entry in "${NO_TOKEN_WORKLOADS[@]}"; do
          ns="${entry%%:*}"
          rest="${entry#*:}"
          kind="${rest%%/*}"
          name="${rest#*/}"

          if [ -z "$ns" ] || [ -z "$kind" ] || [ -z "$name" ]; then
            echo "Invalid entry in NO_TOKEN_WORKLOADS: ${entry} (expected namespace:Kind/name)" >&2
            continue
          fi

          if ! kubectl get "$kind" "$name" -n "$ns" >/dev/null 2>&1; then
            echo "Controller ${kind}/${name} not found in namespace ${ns}, skipping" >&2
            continue
          fi

          patch_controller "$ns" "$kind" "$name"
        done

        # --------------
        # VERIFICATION
        # --------------
        echo
        echo "Verification: checking pods and service accounts for non-compliant automountServiceAccountToken settings..."

        kubectl get pods --all-namespaces -o custom-columns=POD_NAMESPACE:.metadata.namespace,POD_NAME:.metadata.name,POD_SERVICE_ACCOUNT:.spec.serviceAccount,POD_IS_AUTOMOUNTSERVICEACCOUNTTOKEN:.spec.automountServiceAccountToken --no-headers | \
        while read -r pod_namespace pod_name pod_service_account pod_is_automountserviceaccounttoken; do
          # Normalize pod SA name (empty means 'default')
          if [ -z "$pod_service_account" ] || [ "$pod_service_account" = "<none>" ] || [ "$pod_service_account" = "null" ]; then
            pod_service_account="default"
          fi

          svacc_is_automountserviceaccounttoken="$(kubectl get serviceaccount -n "${pod_namespace}" "${pod_service_account}" -o json 2>/dev/null | jq -r '.automountServiceAccountToken' | sed -e 's/<none>/notset/g' -e 's/null/notset/g')"
          pod_is_automountserviceaccounttoken="$(echo "${pod_is_automountserviceaccounttoken}" | sed -e 's/<none>/notset/g' -e 's/null/notset/g')"

          if [ "${svacc_is_automountserviceaccounttoken}" = "false" ] && { [ "${pod_is_automountserviceaccounttoken}" = "false" ] || [ "${pod_is_automountserviceaccounttoken}" = "notset" ]; }; then
            is_compliant="true"
          elif { [ "${svacc_is_automountserviceaccounttoken}" = "true" ] || [ "${svacc_is_automountserviceaccounttoken}" = "notset" ]; } && [ "${pod_is_automountserviceaccounttoken}" = "false" ]; then
            is_compliant="true"
          else
            is_compliant="false"
          fi

          echo "namespace: ${pod_namespace} pod_name: ${pod_name} service_account: ${pod_service_account} pod_is_automountserviceaccounttoken: ${pod_is_automountserviceaccounttoken} svacc_is_automountServiceAccountToken: ${svacc_is_automountserviceaccounttoken} is_compliant: ${is_compliant}"
        done
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
