Skip to main content

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

Manual Steps
  1. 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.

  2. For workloads managed by controllers (Deployment/StatefulSet/DaemonSet/Job/CronJob), edit the controller to set automountServiceAccountToken: false at the pod spec level (run on any machine with kubectl access):

    kubectl -n NAMESPACE edit DEPLOYMENT_NAME

    In the opened manifest, under spec.template.spec, add or change:

    spec:
    template:
    spec:
    automountServiceAccountToken: false

    Save and exit; Kubernetes will roll out updated pods.

  3. 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.yaml

    Edit /tmp/pod-POD_NAME.yaml and under spec add:

    spec:
    automountServiceAccountToken: false

    Then delete and re-create:

    kubectl -n NAMESPACE delete pod POD_NAME
    kubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml
  4. 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_NAME

    Add:

    automountServiceAccountToken: false

    Be sure this ServiceAccount is not used by workloads that need Kubernetes API access.

  5. 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 || true

    Adjust Roles/ClusterRoles separately so the token, where kept, has only necessary permissions.

  6. 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=true or that remaining flagged pods are those you intentionally allowed to keep tokens.

Using kubectl

On any machine with kubectl access to the cluster:

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

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

  1. 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
  1. 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'