Skip to main content

Service Account Tokens Are Only Mounted Where Necessary

More Info:​

Service accounts tokens should not be mounted in pods except where the workload running in the pod explicitly needs to communicate with the API server

Risk Level​

Medium

Address​

Security

Compliance Standards​

  • CIS Kubernetes

Triage and Remediation​

Remediation​

Manual Steps
  1. Identify pods and service accounts that are not compliant

    • Run on any machine with kubectl access:
      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 non-compliant pods and decide which workloads actually need API server access. Only those should keep automountServiceAccountToken: true or notset as appropriate.
  2. Disable token automount at the ServiceAccount level where API access is not needed

    • For each namespace and service account that should not get tokens by default, run on any machine with kubectl access:
      kubectl -n NAMESPACE get serviceaccount SERVICEACCOUNT -o yaml > /tmp/serviceaccount-NAMESPACE-SERVICEACCOUNT.yaml
    • Edit /tmp/serviceaccount-NAMESPACE-SERVICEACCOUNT.yaml and ensure this field is present under metadata (top level of the ServiceAccount):
      automountServiceAccountToken: false
    • Apply the change:
      kubectl apply -f /tmp/serviceaccount-NAMESPACE-SERVICEACCOUNT.yaml
  3. Override specific Pods to allow token mount when needed

    • For workloads that truly require API access and use a ServiceAccount you have set to automountServiceAccountToken: false, explicitly set pod-level automountServiceAccountToken: true so they keep a token:
      kubectl -n NAMESPACE get deployment DEPLOYMENT_NAME -o yaml > /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml
    • Edit /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml and, under spec.template.spec, add or set:
      automountServiceAccountToken: true
    • Apply the change:
      kubectl apply -f /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml
    • This will trigger a rollout; existing pods in that workload will be recreated.
  4. Disable token automount at the Pod level for workloads that do not need API access

    • For each non-compliant workload you determined does not need API access, edit the controller manifest (Deployment/StatefulSet/DaemonSet/CronJob, etc.):
      kubectl -n NAMESPACE get deployment DEPLOYMENT_NAME -o yaml > /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml
    • Under spec.template.spec, add or set:
      automountServiceAccountToken: false
    • Apply the manifest:
      kubectl apply -f /tmp/deployment-NAMESPACE-DEPLOYMENT_NAME.yaml
    • This will recreate pods for that workload without mounting service account tokens.
  5. For bare Pods (not managed by a controller), recreate them with the correct setting

    • Export and edit the Pod definition:
      kubectl -n NAMESPACE get pod POD_NAME -o yaml --export=false > /tmp/pod-NAMESPACE-POD_NAME.yaml
    • Remove metadata.resourceVersion, metadata.uid, metadata.creationTimestamp, status, and other server-generated fields from the file.
    • Under spec, set:
      automountServiceAccountToken: false
    • Delete and recreate the Pod:
      kubectl -n NAMESPACE delete pod POD_NAME
      kubectl apply -f /tmp/pod-NAMESPACE-POD_NAME.yaml
  6. Verification

    • After changes have rolled out and pods are Running, re-run the audit on any machine with kubectl access:
      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 5.1.6"
Using kubectl

On any machine with kubectl access:

  1. Identify non-compliant Pods and ServiceAccounts (for review)
kubectl get pods --all-namespaces -o wide
kubectl get serviceaccounts --all-namespaces
  1. Patch a ServiceAccount so that tokens are not mounted by default
kubectl patch serviceaccount default \
-n your-namespace \
--type merge \
-p '{"automountServiceAccountToken": false}'

Replace default and your-namespace with the specific ServiceAccount and namespace that do not need API access.

Declarative example (ServiceAccount manifest):

apiVersion: v1
kind: ServiceAccount
metadata:
name: example-sa
namespace: your-namespace
automountServiceAccountToken: false

Apply:

kubectl apply -f serviceaccount-example-sa.yaml
  1. Override at Pod/Workload level where the Pod must NOT mount the token

For individual Pods:

apiVersion: v1
kind: Pod
metadata:
name: example-pod
namespace: your-namespace
spec:
serviceAccountName: example-sa
automountServiceAccountToken: false
containers:
- name: app
image: nginx:1.27
kubectl apply -f pod-example-pod.yaml

For Deployments (or similar controllers):

apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
namespace: your-namespace
spec:
replicas: 2
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
serviceAccountName: example-sa
automountServiceAccountToken: false
containers:
- name: app
image: nginx:1.27
kubectl apply -f deployment-example.yaml
  1. Override at Pod/Workload level where the Pod MUST mount the token (explicitly true)

If you have set automountServiceAccountToken: false on a ServiceAccount but a particular Pod using it needs API access, set it to true in the Pod spec (Pod takes precedence):

spec:
serviceAccountName: example-sa
automountServiceAccountToken: true
containers:
- name: app
image: your-image

Apply with kubectl apply -f ....

  1. Verification (on any machine with kubectl access)

Re-run the benchmark-style audit and review that is_compliant is true for Pods that should not mount the token:

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
Automation
#!/usr/bin/env bash
#
# Automate CIS Kubernetes 5.1.6 remediation:
# Ensure Service Account Tokens Are Only Mounted Where Necessary
#
# Requirements:
# - Run on any machine with kubectl access and jq installed.
# - Assumes cluster-admin (or equivalent) privileges.
#
# Behavior:
# - For each Pod using a real ServiceAccount (not "default"):
# - If Pod.spec.automountServiceAccountToken is "true" or not set,
# patch the ServiceAccount to automountServiceAccountToken=false
# (if not already false).
# - If Pod.spec.automountServiceAccountToken is "true",
# patch the Pod to automountServiceAccountToken=false.
# - Skips "kube-system" namespace and DaemonSets/StatefulSets/Deployments,
# because those should be fixed at controller/manifest level.
# - Safe to re-run (uses server-side apply / merge patches).
# - Finishes by re-running the benchmark audit logic to show compliance.

set -euo pipefail

# CONFIGURABLE: namespaces to exclude from automatic changes
EXCLUDED_NAMESPACES_REGEX='^(kube-system|kube-public|kube-node-lease)$'

echo "Discovering pods and service accounts..."
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
# Skip excluded namespaces
if [[ "${pod_namespace}" =~ ${EXCLUDED_NAMESPACES_REGEX} ]]; then
continue
fi

# Skip pods without a service account or with default SA if you prefer to handle defaults manually
if [ -z "${pod_service_account}" ] || [ "${pod_service_account}" = "<none>" ]; then
continue
fi

# Normalize pod automount value
pod_is_automountserviceaccounttoken=$(echo "${pod_is_automountserviceaccounttoken}" | sed -e 's/<none>/notset/g' -e 's/null/notset/g')

# Fetch current SA automount value (may be null)
svacc_is_automountserviceaccounttoken=$(
kubectl get serviceaccount -n "${pod_namespace}" "${pod_service_account}" -o json \
| jq -r '.automountServiceAccountToken // "notset"' \
| sed -e 's/<none>/notset/g' -e 's/null/notset/g'
)

# Determine owning controller to avoid patching pods that will be recreated immediately
owner_kind=$(kubectl get pod "${pod_name}" -n "${pod_namespace}" -o jsonpath='{.metadata.ownerReferences[0].kind}' 2>/dev/null || echo "")
if [ -n "${owner_kind}" ] && [[ "${owner_kind}" =~ ^(DaemonSet|StatefulSet|ReplicaSet|Deployment|Job|CronJob)$ ]]; then
# These should be fixed by editing the controller manifests instead
continue
fi

echo "------------------------------------------------------------"
echo "Namespace: ${pod_namespace}"
echo "Pod: ${pod_name}"
echo "ServiceAccount: ${pod_service_account}"
echo "Pod automountServiceAccountToken: ${pod_is_automountserviceaccounttoken}"
echo "SA automountServiceAccountToken: ${svacc_is_automountserviceaccounttoken}"

# Decide if remediation is needed by reproducing compliance logic

is_compliant="false"
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"
fi

if [ "${is_compliant}" = "true" ]; then
echo "Status: already compliant, skipping."
continue
fi

echo "Status: NON-COMPLIANT, applying remediation..."

# 1) Ensure ServiceAccount has automountServiceAccountToken=false
if [ "${svacc_is_automountserviceaccounttoken}" != "false" ]; then
echo " - Patching ServiceAccount ${pod_service_account} in ${pod_namespace} to automountServiceAccountToken=false"
kubectl patch serviceaccount "${pod_service_account}" \
-n "${pod_namespace}" \
--type='merge' \
-p '{"automountServiceAccountToken": false}'
else
echo " - ServiceAccount already has automountServiceAccountToken=false"
fi

# 2) If Pod spec explicitly sets automountServiceAccountToken to true, patch it to false
if [ "${pod_is_automountserviceaccounttoken}" = "true" ]; then
echo " - Patching Pod ${pod_name} in ${pod_namespace} to automountServiceAccountToken=false"
kubectl patch pod "${pod_name}" \
-n "${pod_namespace}" \
--type='merge' \
-p '{"spec":{"automountServiceAccountToken": false}}'
elif [ "${pod_is_automountserviceaccounttoken}" = "notset" ]; then
# With SA set to false, Pod not setting automount is compliant; no need to patch Pod.
echo " - Pod does not set automountServiceAccountToken; SA=false is sufficient"
else
echo " - Pod already has automountServiceAccountToken=false"
fi

done

echo
echo "Verification: re-running compliance evaluation..."
echo

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 2>/dev/null \
| jq -r '.automountServiceAccountToken // "notset"' \
| 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

echo
echo "Automation complete. Review any remaining non-compliant workloads (likely controller-managed) and update their manifests accordingly."

Additional Reading:​