Service Account Tokens Are Only Mounted Where Necessary
More Info:
Service account 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
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CIS OKE
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- DPDPA
- Digital Operational Resilience Act (EU)
- Essential 8
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIS2 Directive
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with kubectl access, list all service accounts and see which explicitly disable token mounting:
kubectl get sa -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.automountServiceAccountToken}{"\n"}{end}' \| sortReview which service accounts do not need API access (pure data-plane apps, jobs without cluster interactions, etc.) and mark them for disabling token automount at the SA level.
-
For each service account that does not need API access, edit it to disable automounting of tokens:
kubectl -n <namespace> edit sa <sa-name>Add or set:
automountServiceAccountToken: falseSave and exit. This affects only new pods that use this service account (or pods re-created).
-
Identify pods that explicitly configure token automounting and workloads that may still get tokens even after SA-level changes:
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\t"}{.spec.automountServiceAccountToken}{"\n"}{end}' \| sortFor each pod, determine from application owners whether it truly needs to call the Kubernetes API. If not, plan to disable token mounting for its controller (Deployment, StatefulSet, Job, etc.).
-
For each controller whose workload does not need API calls:
- Identify the controller:
kubectl -n <namespace> get deploy,sts,ds,job,cronjob | grep <pod-or-app-identifier>
- Edit the controller spec to disable token mounting on its pods:
Underkubectl -n <namespace> edit deployment <name># or: edit statefulset/daemonset/job/cronjob as appropriate
spec.template.spec, set:automountServiceAccountToken: false
- Identify the controller:
-
For workloads that do need API access, verify least privilege instead of disabling tokens:
- List the service account, its role bindings, and cluster role bindings:
kubectl -n <namespace> get sa <sa-name> -o yamlkubectl -n <namespace> get rolebinding -o wide | grep "<sa-name>"kubectl get clusterrolebinding -o wide | grep "<namespace>:<sa-name>"
- Confirm with the application owner that the granted permissions are minimal and necessary; adjust Role/ClusterRole if they are overly broad.
- List the service account, its role bindings, and cluster role bindings:
-
Verify the effective state after your changes:
- Re-run:
kubectl get sa -N <namespace> -o yaml | grep -E 'name: |automountServiceAccountToken' -nkubectl get pods -N <namespace> -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\t"}{.spec.automountServiceAccountToken}{"\n"}{end}' \| sort
- Optionally exec into a sample pod that should not have a token and confirm the token file is absent:
kubectl -n <namespace> exec -it <pod-name> -- ls /var/run/secrets/kubernetes.io/serviceaccount || echo "No token directory"
- Re-run:
Using kubectl
# 1) List all service accounts and show if they auto-mount tokens
# Run on: any machine with kubectl access
kubectl get serviceaccounts --all-namespaces -o custom-columns=\
'NAMESPACE:{metadata.namespace},NAME:{metadata.name},AUTOMOUNT:{automountServiceAccountToken}' \
| sort
# Problem indication:
# - AUTOMOUNT is "true" or empty (unset) for service accounts used by pods
# that do NOT need to call the Kubernetes API.
# 2) Show all pods and whether they override automountServiceAccountToken
kubectl get pods --all-namespaces -o custom-columns=\
'NAMESPACE:{metadata.namespace},POD:{metadata.name},SA:{spec.serviceAccountName},POD_AUTOMOUNT:{spec.automountServiceAccountToken}' \
| sort
# Problem indication:
# - POD_AUTOMOUNT is "true" for pods that do not need API access.
# - POD_AUTOMOUNT is empty AND their service account has AUTOMOUNT "true" or empty
# (inheritance results in token mounting by default).
# 3) Inspect a specific service account in detail
# Replace <namespace> and <sa-name> with real values
kubectl get sa <sa-name> -n <namespace> -o yaml
# Problem indication:
# - Either no automountServiceAccountToken field (defaults to true), or:
# automountServiceAccountToken: true
# 4) Inspect a specific pod and its effective token-mount behavior
kubectl get pod <pod-name> -n <namespace> -o yaml
# In the output, review:
# - spec.automountServiceAccountToken:
# * true -> pod explicitly requests token mount
# * false -> pod explicitly disables token mount
# * unset -> inherits from its service account / global default
# - spec.serviceAccountName: which service account is used
# - spec.containers[*].volumeMounts and spec.volumes for "kube-api-access-*" or
# "kube-api-access" projected token volumes (names vary by version)
# Problem indication:
# - spec.automountServiceAccountToken: true for workloads that do not need API access.
# - Token volume (kube-api-access-*) mounted in containers that do not require it.
# 5) Quickly find running pods that have a projected token volume mounted
kubectl get pods --all-namespaces -o jsonpath=\
'{range .items[?(@.spec.volumes[*].projected.sources[*].serviceAccountToken)]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}'
# Problem indication:
# - Any listed pod whose workload does not actually talk to the Kubernetes API.
# 6) For a namespace, correlate pods with their SAs and SA automount setting
# Replace <namespace>
kubectl get sa -n <namespace> -o custom-columns=\
'SA:{metadata.name},SA_AUTOMOUNT:{automountServiceAccountToken}' \
; echo "---" \
; kubectl get pods -n <namespace> -o custom-columns=\
'POD:{metadata.name},SA:{spec.serviceAccountName},POD_AUTOMOUNT:{spec.automountServiceAccountToken}'
# Use this to manually decide:
# - Which SAs/pods obviously do not need API access (e.g. simple web frontends, batch
# jobs that never call Kubernetes).
# - Those should have token mounting disabled in their manifests.
Automation
#!/usr/bin/env bash
# Report pods and service accounts that may be unnecessarily mounting service account tokens.
# Run on: any machine with kubectl access and current-context set to the target cluster.
set -euo pipefail
# 1) Cluster-wide defaults from the API server (automountServiceAccountToken admission behavior)
echo "=== Cluster-wide Defaults (per Namespace & ServiceAccount) ==="
echo
echo "Namespaces and their default ServiceAccount automountServiceAccountToken:"
kubectl get sa --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{" "}{.automountServiceAccountToken}{"\n"}{end}' \
| sort \
| awk 'BEGIN{printf("%-30s %-40s %-10s\n","NAMESPACE","SERVICEACCOUNT","AUTO-MOUNT");
print "-------------------------------------------------------------------------------------------"}
{if($3==""){status="<cluster/namespace default>"} else {status=$3}
printf("%-30s %-40s %-10s\n",$1,$2,status)}'
echo
# 2) List ServiceAccounts that explicitly enable or disable automountServiceAccountToken
echo "=== ServiceAccounts with explicit automountServiceAccountToken ==="
echo "NAMESPACE / SERVICEACCOUNT / VALUE"
kubectl get sa --all-namespaces -o json | jq -r '
.items[]
| select(has("automountServiceAccountToken"))
| "\(.metadata.namespace) \(.metadata.name) \(.automountServiceAccountToken)"' \
| sort \
| awk 'BEGIN{printf("%-30s %-40s %-10s\n","NAMESPACE","SERVICEACCOUNT","AUTO-MOUNT");
print "-------------------------------------------------------------------------------------------"}
{printf("%-30s %-40s %-10s\n",$1,$2,$3)}'
echo
# 3) Pods and their effective automountServiceAccountToken
# This resolves the effective value considering:
# - pod.spec.automountServiceAccountToken (if set)
# - serviceAccount.automountServiceAccountToken (if set)
# - otherwise: unknown (falls back to namespace/cluster defaults)
echo "=== Pods and Effective/Evaluated automountServiceAccountToken ==="
echo "This may take some time in large clusters..."
echo
# Build a cache of ServiceAccount auto-mount values
declare -A SA_AUTOMOUNT
# Populate SA_AUTOMOUNT[namespace/name]=true|false|<empty>
while read -r ns name val; do
SA_AUTOMOUNT["$ns/$name"]="$val"
done < <(
kubectl get sa --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{" "}{.automountServiceAccountToken}{"\n"}{end}'
)
printf "%-30s %-40s %-30s %-12s %-12s %-12s\n" \
"NAMESPACE" "POD" "SERVICEACCOUNT" "POD-SET" "SA-SET" "EFFECTIVE"
printf '%*s\n' 120 '' | tr ' ' '-'
kubectl get pods --all-namespaces -o json | jq -r '
.items[]
| [.metadata.namespace,
.metadata.name,
(.spec.serviceAccountName // "default"),
(if has("spec")
and .spec|has("automountServiceAccountToken")
then (.spec.automountServiceAccountToken|tostring)
else ""
end)
] | @tsv' \
| while IFS=$'\t' read -r ns pod sa pod_automount; do
sa_key="$ns/$sa"
sa_automount="${SA_AUTOMOUNT[$sa_key]}"
# Determine effective behavior
# Priority: pod.spec.automountServiceAccountToken, then SA value, otherwise "unknown"
if [[ -n "$pod_automount" ]]; then
effective="$pod_automount"
elif [[ -n "${sa_automount:-}" ]]; then
effective="$sa_automount"
else
effective="unknown"
fi
# Normalize empty displays
[[ -z "$pod_automount" ]] && pod_automount="<unset>"
if [[ -z "${sa_automount:-}" ]]; then
sa_automount="<unset>"
fi
printf "%-30s %-40s %-30s %-12s %-12s %-12s\n" \
"$ns" "$pod" "$sa" "$pod_automount" "$sa_automount" "$effective"
done
cat <<'EOF'
How to interpret this report:
1) ServiceAccounts section:
- Any ServiceAccount with 'AUTO-MOUNT' = true means ALL pods using it inherit
token mounting by default, unless they explicitly set pod.spec.automountServiceAccountToken: false.
- Review whether these ServiceAccounts truly need API access. For those that don't,
consider setting: automountServiceAccountToken: false on the ServiceAccount.
2) Pods section:
- EFFECTIVE = true:
The pod will have a service account token mounted. If the workload does NOT
need to call the Kubernetes API, this is a potential problem.
- EFFECTIVE = false:
The pod will NOT have a service account token mounted. This is aligned with
the benchmark when the workload doesn't need API access.
- EFFECTIVE = unknown:
Neither the Pod nor its ServiceAccount explicitly set automountServiceAccountToken.
These pods rely on namespace/cluster defaults; you must review those defaults and
the workload requirements to decide whether this is acceptable.
Indicators of potential issues to review:
- Pods with EFFECTIVE = true that run:
* stateless frontends, pure batch jobs, or simple workers that do not
interact with the API server.
- ServiceAccounts with AUTO-MOUNT = true heavily used by such pods.
- Namespaces where all ServiceAccounts are implicitly using tokens (AUTO-MOUNT <cluster/namespace default>)
while most workloads do not require API access.
This script only surfaces candidates for review — you must decide case‑by‑case
whether each workload actually needs a service account token.
EOF