Service Account Tokens Are Only Mounted Where Necessary
More Info:
Automatically mounting service account tokens into pods that do not call the API server expands the attack surface. Disable token mounting on service accounts and pods that do not need it.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify service accounts and pods that auto-mount tokens
- Run on any machine with kubectl access:
kubectl get serviceaccounts --all-namespaces -o json \| jq -r '.items[] | [.metadata.namespace, .metadata.name,( .automountServiceAccountToken // "default(true)" )] | @tsv'kubectl get pods --all-namespaces -o json \| jq -r '.items[] | [.metadata.namespace, .metadata.name,( .spec.automountServiceAccountToken // "inherit" ),.spec.serviceAccountName] | @tsv'
- Run on any machine with kubectl access:
-
Select candidates that do not need API access
- Focus on namespaces and workloads that are not control‑plane or infrastructure (e.g., non‑
kube-system, non‑monitoring/ingress). - For each candidate deployment/statefulset/cronjob, inspect its containers for API use:
kubectl get deploy -A -o yaml > /tmp/all-deployments.yaml
- Review
/tmp/all-deployments.yaml(or per‑namespace exports) for images and arguments that clearly do not interact with the Kubernetes API (simple stateless apps, frontends, batch jobs with no cluster logic).
- Focus on namespaces and workloads that are not control‑plane or infrastructure (e.g., non‑
-
Confirm with application owners whether API access is required
- For each candidate workload, provide: namespace, resource name, serviceAccountName, and current
automountServiceAccountTokenbehavior (from step 1). - Ask owners if the pod ever: uses in‑cluster config, lists/watches Kubernetes objects, or needs token‑based authentication. If not, mark it safe to disable token mounting.
- For each candidate workload, provide: namespace, resource name, serviceAccountName, and current
-
Disable token auto-mount on service accounts where safe
- For a specific service account confirmed as not needing API access (example: namespace
prod, service accountweb-sa), run:kubectl patch serviceaccount web-sa -n prod \-p '{"automountServiceAccountToken": false}' - For new service accounts, ensure manifests include:
apiVersion: v1kind: ServiceAccountmetadata:name: web-sanamespace: prodautomountServiceAccountToken: false
- For a specific service account confirmed as not needing API access (example: namespace
-
Override token auto-mount at pod spec level where needed
- For workloads using shared service accounts where some pods need tokens and others do not, set
automountServiceAccountToken: falseon the pod template of the specific workload. Examples:kubectl -n prod patch deploy web-frontend \--type merge \-p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}' - Persist this in the source manifests/Helm charts so it is not reverted by future deployments.
- For workloads using shared service accounts where some pods need tokens and others do not, set
-
Verify changes cluster-wide
- Re-run the cluster-wide inspection:
kubectl get serviceaccounts --all-namespaces -o json \| jq -r '.items[] | [.metadata.namespace, .metadata.name,( .automountServiceAccountToken // "default(true)" )] | @tsv'kubectl get pods --all-namespaces -o json \| jq -r '.items[] | [.metadata.namespace, .metadata.name,( .spec.automountServiceAccountToken // "inherit" ),.spec.serviceAccountName] | @tsv'
- Confirm that pods and service accounts identified as not needing API access now have
automountServiceAccountTokenset tofalseeither on the service account or on the pod spec.
- Re-run the cluster-wide inspection:
Using kubectl
Using kubectl
1. List all ServiceAccounts and check their token mounting behavior
Run on: any machine with kubectl access.
kubectl get serviceaccounts --all-namespaces -o yaml > /tmp/all-serviceaccounts.yaml
Review /tmp/all-serviceaccounts.yaml and look for automountServiceAccountToken:
- If missing on a ServiceAccount, it inherits the namespace/pod default (usually
trueunless overridden). - If explicitly set to
true, that ServiceAccount’s pods will mount tokens by default. - For ServiceAccounts used by workloads that do not need to call the API server,
automountServiceAccountToken: trueor unset is a potential problem; those are candidates to setautomountServiceAccountToken: falseafter review.
To focus on ServiceAccounts that explicitly enable token mounting:
kubectl get serviceaccounts --all-namespaces -o json \
| jq -r '.items[]
| select(.automountServiceAccountToken == true)
| [.metadata.namespace, .metadata.name, .automountServiceAccountToken]
| @tsv'
Output columns:
- Namespace
- ServiceAccount name
true
Each line represents a ServiceAccount that forces token mounting; verify whether its pods truly need API access.
2. Find pods that explicitly control token mounting
Run on: any machine with kubectl access.
kubectl get pods --all-namespaces -o yaml > /tmp/all-pods.yaml
Inspect /tmp/all-pods.yaml and look for automountServiceAccountToken:
- If
automountServiceAccountToken: trueat pod spec level, the pod will mount a token even if the ServiceAccount is more restrictive. - If
automountServiceAccountToken: false, the pod is explicitly protected and will not mount a token. - Pods with
automountServiceAccountToken: truethat do not need to call the API server are potential problems.
To list pods that explicitly enable token mounting:
kubectl get pods --all-namespaces -o json \
| jq -r '.items[]
| select(.spec.automountServiceAccountToken == true)
| [.metadata.namespace, .metadata.name, .spec.serviceAccountName, .spec.automountServiceAccountToken]
| @tsv'
Output columns:
- Namespace
- Pod name
- ServiceAccount name
true
Each line is a pod that forces token mounting; review the workload purpose to decide if that is justified.
3. Identify pods that implicitly mount tokens (likely default behavior)
These pods do not set automountServiceAccountToken but are still likely getting a token via default behavior.
Run on: any machine with kubectl access.
kubectl get pods --all-namespaces -o json \
| jq -r '.items[]
| select(.spec.automountServiceAccountToken == null)
| [.metadata.namespace, .metadata.name, .spec.serviceAccountName]
| @tsv'
Output columns:
- Namespace
- Pod name
- ServiceAccount name
Interpretation:
- These pods do not override token mounting; whether they receive a token depends on:
- The ServiceAccount’s
automountServiceAccountTokenfield, or - The namespace/global defaults.
- The ServiceAccount’s
- For workloads that do not need API calls, confirm whether their ServiceAccounts are unnecessarily allowing token mounting.
4. Check if pods actually have token files mounted
This helps confirm effective behavior, independent of spec fields.
Run on: any machine with kubectl access.
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
for pod in $(kubectl get pods -n "$ns" --no-headers -o custom-columns=':metadata.name'); do
echo "=== $ns/$pod ==="
kubectl exec -n "$ns" "$pod" -- sh -c 'ls -l /var/run/secrets/kubernetes.io/serviceaccount 2>/dev/null || echo "no token directory"'
done
done
Interpretation:
- If you see files such as
token,ca.crt, andnamespaceunder/var/run/secrets/kubernetes.io/serviceaccount, that pod has a service account token mounted. - For pods that do not need to interact with the Kubernetes API, the presence of this directory and token file indicates unnecessary exposure.
5. Verification after making manual changes
After you manually update manifests/ServiceAccounts (outside of this section), re-run:
kubectl get serviceaccounts --all-namespaces -o yaml > /tmp/all-serviceaccounts.yaml
kubectl get pods --all-namespaces -o yaml > /tmp/all-pods.yaml
Confirm that:
- ServiceAccounts and pods that do not need API access either:
- have
automountServiceAccountToken: false, and/or - no longer show a token directory when checked with the command in step 4.
- have
Automation
#!/usr/bin/env bash
# Audit service account token mounting across the whole cluster
# Run on: any machine with kubectl access and current context set
set -euo pipefail
echo "=== 1) Cluster-wide ServiceAccount defaults (automountServiceAccountToken) ==="
echo
echo "# Namespaces where the default for ServiceAccounts is NOT explicitly disabled (potentially risky):"
kubectl get sa --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
name: .metadata.name,
automount: ( .automountServiceAccountToken // "inherited" )
}
| [ .ns, .name, .automount ]
| @tsv' \
| column -t
cat <<'EOF'
Interpretation:
- If "automount" is "true": this ServiceAccount will mount a token into pods by default (higher risk).
- If "automount" is "false": this ServiceAccount will NOT mount a token by default (more secure).
- If "automount" is "inherited": it follows the namespace or cluster default; review pods using it.
Risk indicators:
- ServiceAccounts used by workloads that do NOT need to call the API server but show "true" or "inherited".
EOF
echo
echo "=== 2) Pods and their effective token-mount setting ==="
echo
echo "# Listing all pods with the effective automount behavior and what it is derived from:"
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| $pod.spec.serviceAccountName as $sa
| $pod.metadata.namespace as $ns
| $pod.metadata.name as $podname
| (
if $pod.spec.automountServiceAccountToken != null then
{source:"pod", value:$pod.spec.automountServiceAccountToken}
else
(
(input_filename | .) as $f
)
end
)' 2>/dev/null || true
The above jq block is complex to inline correctly across all clusters; instead, use the following more explicit two-step approach:
#!/usr/bin/env bash
set -euo pipefail
echo "=== 2) Pods and their effective token-mount setting (explicit view) ==="
echo
# For each pod, show:
# - namespace, pod name
# - serviceAccountName
# - pod-level automountServiceAccountToken (if set)
# - ServiceAccount-level automountServiceAccountToken (if set)
# Note: effective behavior is:
# pod.automount (if not null) else SA.automount (if not null) else namespace/cluster default (usually true)
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| .metadata.namespace as $ns
| .metadata.name as $podname
| .spec.serviceAccountName as $sa
| .spec.automountServiceAccountToken as $pod_auto
| {
ns: $ns,
pod: $podname,
sa: $sa,
pod_auto: (if $pod_auto==null then "unset" else $pod_auto|tostring end)
}
| @base64' \
| while read -r line; do
obj=$(echo "$line" | base64 -d)
ns=$(echo "$obj" | jq -r '.ns')
pod=$(echo "$obj" | jq -r '.pod')
sa=$(echo "$obj" | jq -r '.sa')
pod_auto=$(echo "$obj" | jq -r '.pod_auto')
# Look up SA-level automount for this pod
sa_auto=$(kubectl get sa "$sa" -n "$ns" -o json 2>/dev/null \
| jq -r '.automountServiceAccountToken // "unset"' || echo "missing-SA")
printf "%s\t%s\t%s\tpod_auto=%s\tsa_auto=%s\n" "$ns" "$pod" "$sa" "$pod_auto" "$sa_auto"
done \
| column -t | sed '1iNAMESPACE POD SERVICEACCOUNT POD_AUTOMOUNT SA_AUTOMOUNT'
cat <<'EOF'
Interpretation:
- POD_AUTOMOUNT=unset and SA_AUTOMOUNT=unset:
- Pod inherits namespace/cluster default (commonly true) -> likely mounts a token.
- POD_AUTOMOUNT=true:
- Pod will mount a token regardless of SA/namespace defaults (higher risk if API not needed).
- POD_AUTOMOUNT=false:
- Pod will NOT mount a token even if SA/namespace defaults are true (more secure).
- SA_AUTOMOUNT=true:
- All pods using this SA (that do not override at pod level) will mount a token.
- SA_AUTOMOUNT=false:
- Pods using this SA will not mount a token unless they explicitly set POD_AUTOMOUNT=true.
Risk indicators (potential problems to review):
- Pods that do NOT need to talk to the Kubernetes API but show:
- POD_AUTOMOUNT=true, OR
- POD_AUTOMOUNT=unset and SA_AUTOMOUNT=true or unset (i.e., likely mounting a token).
- Namespaces or teams where *every* ServiceAccount has automount=true or unset by default.
EOF
To verify the script:
- Run it from any machine with
kubectlandjqinstalled. - Confirm it completes without errors and prints:
- A table of ServiceAccounts and their
automountServiceAccountToken. - A table of pods showing pod-level and ServiceAccount-level automount values for review.
- A table of ServiceAccounts and their