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
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CIS EKS
- 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
-
List pods and service accounts that may not need API access (run on any machine with kubectl access):
# Pods that currently auto-mount a token (candidate review set)kubectl get pods --all-namespaces -o json | jq '[.items[] | select(.spec.automountServiceAccountToken != false)] |[.[] | {ns:.metadata.namespace, pod:.metadata.name, sa:(.spec.serviceAccountName // "default")}]'# ServiceAccounts that might be used by those podskubectl get sa --all-namespaces -o json | jq '[.items[] | {ns:.metadata.namespace, sa:.metadata.name,automount:(.automountServiceAccountToken // "unset")}]' -
For each pod, determine if it really needs API server access (manual review):
- Inspect the container images and commands:
kubectl -n <namespace> describe pod <pod-name>
- Look for:
- Use of
kubectl, client SDKs, or API calls to Kubernetes - RBAC permissions or config mounting kubeconfig/service-account token
- Controllers/operators, admission webhooks, or in-cluster automation
- Use of
- If you are unsure, consult the application owner before disabling token mounting.
- Inspect the container images and commands:
-
For workloads that do NOT need API access, set
automountServiceAccountToken: falseat the pod/workload level (preferred; run on any machine with kubectl access):- For a Deployment (similar for StatefulSet/DaemonSet/Job/CronJob):
Editkubectl -n <namespace> get deploy <deploy-name> -o yaml > /tmp/deploy.yaml
/tmp/deploy.yamland underspec.template.specadd:Then apply:automountServiceAccountToken: falsekubectl apply -f /tmp/deploy.yaml - For a standalone Pod manifest, add the same field under
specand re-create the pod if needed.
- For a Deployment (similar for StatefulSet/DaemonSet/Job/CronJob):
-
Optionally harden ServiceAccounts that should never mount tokens by default (run on any machine with kubectl access):
- For each such ServiceAccount:
Editkubectl -n <namespace> get sa <sa-name> -o yaml > /tmp/sa.yaml
/tmp/sa.yamlto include:underautomountServiceAccountToken: falsemetadata(same level asname/namespace), then:kubectl apply -f /tmp/sa.yaml - Ensure any pods that DO require API access and use this ServiceAccount explicitly set:
in their pod template.spec:automountServiceAccountToken: true
- For each such ServiceAccount:
-
Coordinate and monitor rollout impact:
- Changing pod templates causes pods to be re-created/rolled; schedule changes during a maintenance window if needed.
- After changes, confirm affected pods are running and application functionality is intact:
kubectl -n <namespace> get pods
-
Verification (run on any machine with kubectl access):
pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '[.items[] | select(.spec.automountServiceAccountToken != false)] | length')if [ "$pods_with_token_mount" -gt 0 ]; thenecho "automountServiceAccountToken"elseecho "OK: all pods explicitly set automountServiceAccountToken=false or do not mount tokens unnecessarily"fi
Using kubectl
On any machine with kubectl access:
-
Identify pods and service accounts that do not need API server access
kubectl get pods --all-namespaces -o widekubectl get sa --all-namespacesReview workloads and decide which ones truly need to talk to the Kubernetes API. Only those should keep token mounting enabled.
-
Patch individual Pods that do not need the token (ephemeral; also fix their controllers)
# Example: disable token mount on a running podkubectl patch pod <pod-name> -n <namespace> \--type=merge \-p '{"spec":{"automountServiceAccountToken":false}}' -
Configure ServiceAccounts to not mount tokens by default
For service accounts whose workloads do not need API access:kubectl patch serviceaccount <sa-name> -n <namespace> \--type=merge \-p '{"automountServiceAccountToken":false}'Or declaratively, create/update a manifest such as:
apiVersion: v1kind: ServiceAccountmetadata:name: example-sanamespace: example-namespaceautomountServiceAccountToken: falseApply it:
kubectl apply -f example-sa.yaml -
Configure controllers (Deployments, DaemonSets, StatefulSets, Jobs, CronJobs) so new pods do not mount tokens
Edit or patch the pod template for each controller whose workloads don’t need API access.Example patch for a Deployment:
kubectl patch deployment <deploy-name> -n <namespace> \--type=merge \-p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'Example declarative Deployment snippet:
apiVersion: apps/v1kind: Deploymentmetadata:name: example-deploymentnamespace: example-namespacespec:template:spec:automountServiceAccountToken: falseserviceAccountName: example-sacontainers:- name: appimage: nginx:stableApply it:
kubectl apply -f example-deployment.yaml -
Recreate pods if needed
For controllers, new pods will inherit the new setting automatically after rollout. To force recreation:kubectl rollout restart deployment <deploy-name> -n <namespace> -
Verification (same logic as the audit)
pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '[.items[] | select(.spec.automountServiceAccountToken != false)] | length')if [ "$pods_with_token_mount" -gt 0 ]; thenecho "automountServiceAccountToken still enabled on some pods"elseecho "All pods have automountServiceAccountToken set to false"fi
Automation
#!/usr/bin/env bash
set -euo pipefail
# Automation: Disable automountServiceAccountToken where not explicitly required
# Scope: Run on any machine with kubectl and jq installed, authenticated to the cluster.
#
# WARNING:
# - This will PATCH pods and serviceaccounts that do not explicitly set
# automountServiceAccountToken=false to set it to false.
# - It cannot distinguish workloads that truly need API access from those that don't.
# Review candidates before/after running, or restrict via namespace/label filters.
# Optional: limit scope by namespace (uncomment and set, or leave empty for all)
NAMESPACE_FILTER=""
# Helper to run kubectl with optional namespace filter
k() {
if [[ -n "$NAMESPACE_FILTER" ]]; then
kubectl -n "$NAMESPACE_FILTER" "$@"
else
kubectl "$@"
fi
}
echo "Discovering pods with automountServiceAccountToken not explicitly set to false..."
# List pods that currently have automountServiceAccountToken != false
pods_json=$(k get pods --all-namespaces -o json)
pods_to_patch=$(echo "$pods_json" | jq -r '
.items[]
| select(.spec.automountServiceAccountToken != false)
| "\(.metadata.namespace) \(.metadata.name)"')
if [[ -z "$pods_to_patch" ]]; then
echo "No pods require patch for automountServiceAccountToken."
else
echo "Patching pods to set spec.automountServiceAccountToken=false..."
while read -r ns name; do
[[ -z "$ns" || -z "$name" ]] && continue
echo " Patching pod: $ns/$name"
k -n "$ns" patch pod "$name" --type=merge -p '{
"spec": {
"automountServiceAccountToken": false
}
}' >/dev/null
done <<< "$pods_to_patch"
fi
echo "Discovering ServiceAccounts with automountServiceAccountToken not explicitly set to false..."
sa_json=$(k get serviceaccounts --all-namespaces -o json)
sa_to_patch=$(echo "$sa_json" | jq -r '
.items[]
| select(.automountServiceAccountToken != false)
| "\(.metadata.namespace) \(.metadata.name)"')
if [[ -z "$sa_to_patch" ]]; then
echo "No ServiceAccounts require patch for automountServiceAccountToken."
else
echo "Patching ServiceAccounts to set automountServiceAccountToken=false..."
while read -r ns name; do
[[ -z "$ns" || -z "$name" ]] && continue
echo " Patching ServiceAccount: $ns/$name"
k -n "$ns" patch serviceaccount "$name" --type=merge -p '{
"automountServiceAccountToken": false
}' >/dev/null
done <<< "$sa_to_patch"
fi
echo "Verification: re-running audit for pods..."
pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '
[.items[] | select(.spec.automountServiceAccountToken != false)] | length')
if [ "$pods_with_token_mount" -gt 0 ]; then
echo "Verification FAILED: Some pods still have automountServiceAccountToken != false"
echo "Count: $pods_with_token_mount"
exit 1
else
echo "Verification PASSED: All pods now have automountServiceAccountToken=false"
fi
echo "Listing ServiceAccounts that still allow token automount (if any)..."
kubectl get serviceaccounts --all-namespaces -o json | jq -r '
.items[]
| select(.automountServiceAccountToken != false)
| "\(.metadata.namespace) \(.metadata.name)"' || true
echo "Automation completed."