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
- Remediation
Remediation
Manual Steps
-
Identify noncompliant pods and their owners (run on any machine with kubectl access):
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)| "\($m.namespace) \($m.name) \($own.kind) \($own.name) \(.spec.serviceAccountName // "default")"][]'Review each listed workload and decide whether it legitimately needs to call the Kubernetes API; only proceed for those that do not.
-
For workloads managed by controllers (Deployment/StatefulSet/DaemonSet/Job/CronJob), edit the controller to set
automountServiceAccountToken: falseat the pod spec level (run on any machine with kubectl access):kubectl -n NAMESPACE edit DEPLOYMENT_NAMEIn the opened manifest, under
spec.template.spec, add or change:spec:template:spec:automountServiceAccountToken: falseSave and exit; Kubernetes will roll out updated pods.
-
For standalone Pods you control directly (no controller ownerReference), edit the Pod spec and re-create it (run on any machine with kubectl access):
kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-POD_NAME.yamlEdit
/tmp/pod-POD_NAME.yamland underspecadd:spec:automountServiceAccountToken: falseThen delete and re-create:
kubectl -n NAMESPACE delete pod POD_NAMEkubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml -
Optionally harden shared ServiceAccounts so all attached pods disable token automount by default (run on any machine with kubectl access):
kubectl -n NAMESPACE edit serviceaccount SERVICEACCOUNT_NAMEAdd:
automountServiceAccountToken: falseBe sure this ServiceAccount is not used by workloads that need Kubernetes API access.
-
For pods that legitimately need the Kubernetes API, document the decision and ensure least-privilege RBAC:
kubectl -n NAMESPACE get rolebinding,clusterrolebinding -o wide | grep SERVICEACCOUNT_NAME || trueAdjust Roles/ClusterRoles separately so the token, where kept, has only necessary permissions.
-
Verify remediation (run on any machine with kubectl access):
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'Confirm the output is
is_compliant=trueor that remaining flagged pods are those you intentionally allowed to keep tokens.
Using kubectl
On any machine with kubectl access to the cluster:
- Identify one non‑compliant pod (example):
kubectl get pods -A \
-o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName,AUTO:.spec.automountServiceAccountToken' \
| grep -v ' true$' | grep -v ' false$' | grep -v NAMESPACE
- Get the owning workload and current pod spec:
kubectl get pod POD_NAME -n POD_NAMESPACE -o yaml > /tmp/pod-debug.yaml
Check .metadata.ownerReferences to see if it is owned by a Deployment, StatefulSet, Job, CronJob, etc. You must edit the owning workload, not the live Pod.
- Patch a Deployment to disable token automount for pods that do not need the API (example for a Deployment owner):
kubectl patch deployment DEPLOYMENT_NAME -n POD_NAMESPACE \
--type='strategic' \
-p '{
"spec": {
"template": {
"spec": {
"automountServiceAccountToken": false
}
}
}
}'
For other controllers, replace deployment with statefulset, daemonset, job, or cronjob as appropriate.
- If the pod is standalone (no ownerReferences), edit the Pod manifest source and re‑apply it declaratively. Example manifest snippet:
apiVersion: v1
kind: Pod
metadata:
name: example-pod
namespace: example-namespace
spec:
automountServiceAccountToken: false
containers:
- name: app
image: your-image
Apply it:
kubectl apply -f path/to/pod.yaml
- Verification (matches the audit intent):
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
| "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'
Automation
#!/usr/bin/env bash
#
# Disable automountServiceAccountToken for pods that do NOT need the Kubernetes API.
#
# IMPORTANT: This script does NOT change anything automatically.
# For each non-compliant Pod, it:
# - Shows current Pod spec
# - Shows owning controller (if any)
# - Prints a kubectl patch command you can copy, review, and run
#
# This is deliberate: the benchmark specifies that pods which legitimately
# call the API must be reviewed, not blindly remediated.
#
# Run on: any machine with kubectl access to the cluster
# Requirements: bash, kubectl, jq
set -euo pipefail
echo "=== Scanning for pods with automountServiceAccountToken not explicitly set to false ==="
# Reuse the audit logic to list only non-compliant pods (outside system namespaces)
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)
')"
if [[ -z "${non_compliant_json}" ]]; then
echo "No non-compliant pods found. Cluster is compliant for this control."
exit 0
fi
echo "Non-compliant pods detected. Listing details and suggested patches."
echo
# Iterate each offending pod
echo "${non_compliant_json}" | jq -c '.' | while read -r pod; do
ns="$(jq -r '.metadata.namespace' <<< "${pod}")"
name="$(jq -r '.metadata.name' <<< "${pod}")"
sa="$(jq -r '.spec.serviceAccountName // "default"' <<< "${pod}")"
owner_kind="$(jq -r '[.metadata.ownerReferences[]? | select(.controller)] | first.kind // "Pod"' <<< "${pod}")"
owner_name="$(jq -r '[.metadata.ownerReferences[]? | select(.controller)] | first.name // ""' <<< "${pod}")"
echo "-----------------------------------------------------------------"
echo "Pod: ${ns}/${name}"
echo " ServiceAccount: ${sa}"
echo " Controller: ${owner_kind}${owner_name:+/${owner_name}}"
if [[ "${owner_kind}" == "Pod" || -z "${owner_kind}" || "${owner_name}" == "null" ]]; then
echo " NOTE: This is a standalone Pod (not controlled by a higher-level workload)."
echo " Review whether this Pod ever calls the Kubernetes API. If it does NOT,"
echo " you can set automountServiceAccountToken=false directly on the Pod spec."
echo
echo " Suggested patch (standalone Pod):"
cat <<EOF
kubectl -n ${ns} patch pod ${name} --type merge -p '{
"spec": {
"automountServiceAccountToken": false
}
}'
EOF
else
echo " NOTE: This Pod is managed by a controller. You MUST patch the controller"
echo " (e.g., Deployment, StatefulSet, DaemonSet, Job, CronJob, etc.), not the Pod."
echo " Review whether this workload ever calls the Kubernetes API. If it does NOT,"
echo " you can set automountServiceAccountToken=false in the pod template."
echo
echo " Suggested patch (controller template):"
# For typical workload types we can provide a generic patch.
# User must adjust 'spec.template.spec' path if their controller is custom.
case "${owner_kind}" in
Deployment|StatefulSet|DaemonSet)
cat <<EOF
kubectl -n ${ns} patch ${owner_kind,,} ${owner_name} --type merge -p '{
"spec": {
"template": {
"spec": {
"automountServiceAccountToken": false
}
}
}
}'
EOF
;;
Job)
cat <<EOF
kubectl -n ${ns} patch job ${owner_name} --type merge -p '{
"spec": {
"template": {
"spec": {
"automountServiceAccountToken": false
}
}
}
}'
EOF
;;
CronJob)
cat <<EOF
kubectl -n ${ns} patch cronjob ${owner_name} --type merge -p '{
"spec": {
"jobTemplate": {
"spec": {
"template": {
"spec": {
"automountServiceAccountToken": false
}
}
}
}
}
}'
EOF
;;
*)
echo " Unrecognized or custom controller kind: ${owner_kind}"
echo " Inspect its spec and add automountServiceAccountToken: false under the Pod template:"
echo " - For most controllers, this is under .spec.template.spec"
echo " - For CronJob, it is under .spec.jobTemplate.spec.template.spec"
echo
echo " Generic example (adjust apiVersion/kind/path as needed):"
cat <<'EOF'
kubectl -n <namespace> patch <kind> <name> --type merge -p '{
"spec": {
"template": {
"spec": {
"automountServiceAccountToken": false
}
}
}
}'
EOF
;;
esac
fi
echo
done
echo "-----------------------------------------------------------------"
echo "Review and run the suggested kubectl patch commands ONLY for workloads"
echo "that do NOT need to call the Kubernetes API."
echo
echo "=== Verification (re-run the benchmark-style audit) ==="
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)
| (.spec.automountServiceAccountToken == false) as $ok
| select($ok | not)
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else "Non-compliant pods remain; review above output." end'