Service Account Tokens Are Only Mounted Where Necessary
More Info:​
Pods that automount service account tokens without needing them expand the attack surface. Token mounting should be disabled where API access is not required.
Risk Level​
Medium
Address​
Security
Compliance Standards​
- CIS GKE
Triage and Remediation​
- Remediation
Remediation​
Manual Steps
-
Identify pods and service accounts that do not need API access
- On any machine with kubectl access:
kubectl get pods --all-namespaces -o widekubectl get sa --all-namespaces
- Based on application knowledge, list pods and service accounts that never call the Kubernetes API.
- On any machine with kubectl access:
-
Disable token automount on service accounts that do not need API access
- On any machine with kubectl access, for each such service account:
kubectl patch sa SERVICEACCOUNT_NAME \-n NAMESPACE \--type merge \-p '{"automountServiceAccountToken": false}'
- For GitOps or manifest-based management, edit the ServiceAccount manifest and add:
then apply:automountServiceAccountToken: falsekubectl apply -f serviceaccount.yaml
- On any machine with kubectl access, for each such service account:
-
Disable token automount on pods that do not need API access (per‑pod override)
- For standalone pods:
(This affects the existing pod; ensure your controllers or manifests won’t immediately recreate it without the setting.)kubectl patch pod POD_NAME \-n NAMESPACE \--type merge \-p '{"spec":{"automountServiceAccountToken":false}}'
- For standalone pods:
-
Disable token automount on controllers (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs)
- On any machine with kubectl access, for each controller whose workloads do not require API access:
kubectl get deployment DEPLOYMENT_NAME -n NAMESPACE -o yaml > deployment.yaml
- Edit
deployment.yamland underspec.template.specadd:automountServiceAccountToken: false - Apply the change:
kubectl apply -f deployment.yaml
- Repeat similarly for other controller kinds:
Edit each file underkubectl get statefulset STATEFULSET_NAME -n NAMESPACE -o yaml > ss.yamlkubectl get daemonset DAEMONSET_NAME -n NAMESPACE -o yaml > ds.yamlkubectl get job JOB_NAME -n NAMESPACE -o yaml > job.yamlkubectl get cronjob CRONJOB_NAME -n NAMESPACE -o yaml > cj.yaml
spec.template.specand addautomountServiceAccountToken: false, then:kubectl apply -f ss.yamlkubectl apply -f ds.yamlkubectl apply -f job.yamlkubectl apply -f cj.yaml
- On any machine with kubectl access, for each controller whose workloads do not require API access:
-
Review for exceptions where API access is required
- Confirm you did not change service accounts or pods that legitimately need to call the Kubernetes API (e.g., controllers, operators, metrics collectors).
- For critical namespaces (like
kube-system), review individually before applying changes:kubectl get pods -n kube-system -o widekubectl get sa -n kube-system -o yaml
-
Verification
- On any machine with kubectl access, re-run a refined audit to list only pods still mounting tokens:
kubectl get pods --all-namespaces -o json | jq '.items[]| select(.spec.automountServiceAccountToken != false)| {namespace: .metadata.namespace, name: .metadata.name, automountServiceAccountToken: .spec.automountServiceAccountToken}'
- Confirm that pods and service accounts which do not need API access no longer appear with
automountServiceAccountTokenset (either missing ortrue).
- On any machine with kubectl access, re-run a refined audit to list only pods still mounting tokens:
Using kubectl
# 1) Identify pods that explicitly mount a service account token
# Run on: any machine with kubectl access
kubectl get pods --all-namespaces -o jsonpath='{range .items[?(@.spec.automountServiceAccountToken!=false)]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}'
# 2) Identify service accounts that explicitly allow token automount
kubectl get serviceaccounts --all-namespaces -o jsonpath='{range .items[?(@.automountServiceAccountToken!=false)]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}'
Update service accounts (preferred, so all attached pods default to no token unless overridden):
# Example: disable token automount for a specific service account
# Replace NAMESPACE and SERVICEACCOUNT with real values
kubectl -n NAMESPACE patch serviceaccount SERVICEACCOUNT \
--type merge \
-p '{"automountServiceAccountToken": false}'
Update pod specs via their owning workload (Deployment/DaemonSet/StatefulSet/CronJob/Job):
# Example: patch a Deployment to disable token automount for its pods
# Replace NAMESPACE and DEPLOYMENT with real values
kubectl -n NAMESPACE patch deployment DEPLOYMENT \
--type merge \
-p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'
Declarative manifest snippets (for future changes):
# ServiceAccount manifest
apiVersion: v1
kind: ServiceAccount
metadata:
name: example-sa
namespace: example-namespace
automountServiceAccountToken: false
# Pod template in a Deployment (same pattern for other controllers)
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
namespace: example-namespace
spec:
replicas: 1
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
serviceAccountName: example-sa
automountServiceAccountToken: false
containers:
- name: app
image: nginx:1.27
Apply manifests:
# Run on: any machine with kubectl access
kubectl apply -f /absolute/path/to/your-manifest.yaml
Verification (adapted from the audit):
# Run on: any machine with kubectl access
echo "🔹 Pods with automountServiceAccountToken enabled:"
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 "automountServiceAccountToken"
else
echo "No pods with automountServiceAccountToken enabled (all are false or unset via SA defaults)."
fi
Automation
#!/usr/bin/env bash
#
# Purpose:
# Disable automountServiceAccountToken where it is not explicitly needed by
# setting:
# - Pod spec.automountServiceAccountToken: false
# - ServiceAccount automountServiceAccountToken: false
#
# Scope:
# - Runs from any machine with kubectl access and sufficient RBAC.
# - Idempotent: safe to re-run.
#
# WARNING:
# This is a conservative baseline hardening script. It will:
# * Set automountServiceAccountToken: false on ALL ServiceAccounts
# that do not already have it explicitly set to true.
# * Set automountServiceAccountToken: false on ALL Pods (workloads)
# that do not already have it explicitly set to true.
# Review before use and consider scoping to selected namespaces/labels
# if some workloads need default token mounting.
set -euo pipefail
#------------ Configuration ------------#
KUBECTL_BIN="${KUBECTL_BIN:-kubectl}"
# Optional: limit to specific namespaces, space-separated (leave empty for all)
NAMESPACE_FILTER="${NAMESPACE_FILTER:-}"
#------------ Helper Functions ------------#
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "ERROR: required command '$1' not found in PATH" >&2
exit 1
fi
}
run_kubectl() {
# Wrapper for kubectl to centralize binary selection
"$KUBECTL_BIN" "$@"
}
#------------ Pre-flight Checks ------------#
require_cmd "$KUBECTL_BIN"
require_cmd jq
echo "Using kubectl: $KUBECTL_BIN"
echo "Namespace filter: ${NAMESPACE_FILTER:-<all namespaces>}"
# Determine namespaces to operate on
if [ -n "$NAMESPACE_FILTER" ]; then
read -r -a NAMESPACES <<<"$NAMESPACE_FILTER"
else
mapfile -t NAMESPACES < <(run_kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
fi
echo "Target namespaces:"
printf ' %s\n' "${NAMESPACES[@]}"
#------------ Function: Patch ServiceAccounts ------------#
harden_serviceaccounts() {
local ns="$1"
echo "Processing ServiceAccounts in namespace: $ns"
# Get all SAs as JSON
local sa_json
if ! sa_json=$(run_kubectl get sa -n "$ns" -o json 2>/dev/null); then
echo " Skipping namespace $ns (unable to list ServiceAccounts)"
return
fi
# For each SA where .automountServiceAccountToken is null or not set or false,
# we will explicitly set it to false (if not already true).
# This preserves any explicit true values.
echo "$sa_json" \
| jq -r '
.items[]
| select(.automountServiceAccountToken != true)
| .metadata.name
' \
| while read -r sa_name; do
[ -z "$sa_name" ] && continue
echo " Patching ServiceAccount/$sa_name: set automountServiceAccountToken=false"
run_kubectl patch sa "$sa_name" -n "$ns" \
--type merge \
-p '{"automountServiceAccountToken": false}' >/dev/null
done
}
#------------ Function: Patch Workloads (Pods via controllers) ------------#
# This section adjusts controllers, not live Pods directly:
# - Deployments
# - StatefulSets
# - DaemonSets
# - ReplicaSets
# - Jobs
# - CronJobs
harden_workloads() {
local ns="$1"
echo "Processing workloads in namespace: $ns"
# Helper to patch a controller kind
local kind
for kind in deployment statefulset daemonset replicaset job cronjob; do
# Skip kinds not present in this namespace
if ! run_kubectl get "$kind" -n "$ns" >/dev/null 2>&1; then
continue
fi
local items_json
if ! items_json=$(run_kubectl get "$kind" -n "$ns" -o json 2>/dev/null); then
echo " Unable to list $kind in $ns; skipping"
continue
fi
# For each object whose pod template does NOT explicitly set
# spec.template.spec.automountServiceAccountToken to true,
# we set it to false.
echo "$items_json" \
| jq -r '
.items[]
| select(.spec.template.spec.automountServiceAccountToken != true)
| .metadata.name
' \
| while read -r name; do
[ -z "$name" ] && continue
echo " Patching ${kind}/${name}: set spec.template.spec.automountServiceAccountToken=false"
run_kubectl patch "$kind" "$name" -n "$ns" \
--type merge \
-p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}' >/dev/null
done
done
}
#------------ Main: Apply Fixes ------------#
for ns in "${NAMESPACES[@]}"; do
harden_serviceaccounts "$ns"
harden_workloads "$ns"
done
#------------ Verification ------------#
echo
echo "Verification: listing pods with automountServiceAccountToken not explicitly false."
echo "This should ideally return only pods that explicitly require token mounting."
pods_with_token_mount=$(run_kubectl get pods --all-namespaces -o json \
| jq '[.items[] | select(.spec.automountServiceAccountToken != false)] | length')
echo "Pods with automountServiceAccountToken enabled or unset: $pods_with_token_mount"
if [ "$pods_with_token_mount" -eq 0 ]; then
echo "SUCCESS: All pods now have automountServiceAccountToken explicitly set to false."
else
echo "NOTICE: Some pods still have automountServiceAccountToken enabled or unset."
echo " Review them manually to confirm they genuinely need API access."
fi