Ensure Default Service Accounts Not Actively Used.
More Info:
The default service account should not be used to ensure that rights granted to applications can be more easily audited and reviewed.
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 all default service accounts and identify ones still auto-mounting tokens (run on any machine with kubectl access):
kubectl get serviceaccounts --all-namespaces -o json | jq -r '.items[]| select(.metadata.name=="default" and (.automountServiceAccountToken != false))| "\(.metadata.namespace)"' -
Disable token auto-mount for the default service account in each affected namespace (run on any machine with kubectl access; repeat for each NAMESPACE from step 1):
NAMESPACE=default # replace with each namespace namekubectl patch serviceaccount default -n "$NAMESPACE" --type merge -p \'{"automountServiceAccountToken": false}' -
Identify pods currently using the default service account (run on any machine with kubectl access):
kubectl get pods --all-namespaces -o json | jq -r '.items[]| select(.spec.serviceAccountName == "default")| "\(.metadata.namespace) \(.metadata.name)"' -
For each workload using the default service account, create a dedicated service account (run on any machine with kubectl access; repeat per namespace as needed):
NAMESPACE=default # namespace of the workloadSA_NAME=app-sa-example # choose a name per applicationkubectl create serviceaccount "$SA_NAME" -n "$NAMESPACE" -
Update workloads (Deployments/StatefulSets/etc.) to use the new service account (run on any machine with kubectl access; repeat per workload):
NAMESPACE=default # namespace of the workloadWORKLOAD=your-deployment-name # name of Deployment/StatefulSet/Job/etc.# Edit the object and set spec.template.spec.serviceAccountName to the new SAkubectl -n "$NAMESPACE" edit deployment "$WORKLOAD"# In the editor, under spec.template.spec, ensure:# serviceAccountName: app-sa-example -
Verify that default service accounts are not auto-mounting tokens and not used by pods (run on any machine with kubectl access):
default_sa_count=$(kubectl get serviceaccounts --all-namespaces -o json | jq '[.items[] | select(.metadata.name == "default" and (.automountServiceAccountToken != false))] | length')pods_using_default_sa=$(kubectl get pods --all-namespaces -o json | jq '[.items[] | select(.spec.serviceAccountName == "default")] | length')echo "default_sa_count=$default_sa_count"echo "pods_using_default_sa=$pods_using_default_sa"
Using kubectl
# 1) Disable token automount on all existing default ServiceAccounts
# Run on: any machine with kubectl access
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
kubectl patch serviceaccount default \
-n "$ns" \
--type='merge' \
-p '{"automountServiceAccountToken": false}'
done
# 2) Identify Pods currently using the default ServiceAccount
# (for each such Pod/Deployment/Job/etc, update its spec to use a
# dedicated ServiceAccount you create for that workload)
kubectl get pods --all-namespaces \
-o jsonpath='{range .items[?(@.spec.serviceAccountName=="default")]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}'
# Example manifest: create an explicit ServiceAccount and use it in a Deployment
# Save as app-sa.yaml and apply with `kubectl apply -f app-sa.yaml`
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
automountServiceAccountToken: true
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
serviceAccountName: app-sa
containers:
- name: my-app
image: nginx:stable
# Apply the manifest
kubectl apply -f app-sa.yaml
# 3) Verification
# Run the same logic as the audit (no output means pass)
default_sa_count=$(kubectl get serviceaccounts --all-namespaces -o json | jq '
[.items[] | select(.metadata.name == "default" and (.automountServiceAccountToken != false))] | length')
if [ "$default_sa_count" -gt 0 ]; then
echo "default_sa_not_auto_mounted"
fi
pods_using_default_sa=$(kubectl get pods --all-namespaces -o json | jq '
[.items[] | select(.spec.serviceAccountName == "default")] | length')
if [ "$pods_using_default_sa" -gt 0 ]; then
echo "default_sa_used_in_pods"
fi
Automation
#!/usr/bin/env bash
set -euo pipefail
# This script:
# 1. Sets automountServiceAccountToken: false on all existing "default" ServiceAccounts.
# 2. Identifies pods using the default ServiceAccount for manual follow‑up.
# 3. Verifies the setting across the cluster.
#
# Run from any machine with kubectl access and a current context.
echo "[*] Ensuring all 'default' ServiceAccounts have automountServiceAccountToken: false"
# Get all namespaces
namespaces=$(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')
for ns in $namespaces; do
# Check if default SA exists in this namespace
if ! kubectl get sa default -n "$ns" >/dev/null 2>&1; then
continue
fi
# Check current value; may be null/absent/true/false
current=$(kubectl get sa default -n "$ns" -o jsonpath='{.automountServiceAccountToken}' 2>/dev/null || echo "")
if [ "$current" = "false" ]; then
echo "[=] Namespace '$ns': default SA already has automountServiceAccountToken=false"
continue
fi
echo "[+] Patching default ServiceAccount in namespace '$ns' to set automountServiceAccountToken=false"
kubectl patch serviceaccount default -n "$ns" --type merge -p '{"automountServiceAccountToken": false}' >/dev/null
done
echo
echo "[*] Listing pods that currently use the 'default' ServiceAccount (for manual remediation)"
kubectl get pods --all-namespaces -o json | jq -r '
.items[]
| select(.spec.serviceAccountName == "default")
| [.metadata.namespace, .metadata.name, .spec.serviceAccountName]
| @tsv' | awk 'BEGIN{printf "%-30s %-40s %-20s\n","NAMESPACE","POD","SERVICEACCOUNT";print substr("--------------------------------------------------------------------------------------------------------------------------------",1,95)} {printf "%-30s %-40s %-20s\n",$1,$2,$3}' || true
echo
echo "[*] Verification: re-running benchmark-style checks"
# Check for any default SAs that do NOT have automountServiceAccountToken=false
default_sa_count=$(kubectl get serviceaccounts --all-namespaces -o json | jq '
[.items[] | select(.metadata.name == "default" and (.automountServiceAccountToken != false))] | length')
if [ "$default_sa_count" -gt 0 ]; then
echo "[!] VERIFICATION FAILED: Some 'default' ServiceAccounts still do not have automountServiceAccountToken=false"
echo " Count: $default_sa_count"
echo " Details:"
kubectl get serviceaccounts --all-namespaces -o json | jq -r '
.items[]
| select(.metadata.name == "default" and (.automountServiceAccountToken != false))
| [.metadata.namespace, .metadata.name, (.automountServiceAccountToken // "null")]
| @tsv' | awk 'BEGIN{printf "%-30s %-20s %-10s\n","NAMESPACE","SERVICEACCOUNT","AUTO_MOUNT";print substr("------------------------------------------------------------",1,60)} {printf "%-30s %-20s %-10s\n",$1,$2,$3}'
exit 1
else
echo "[+] VERIFICATION PASSED: All 'default' ServiceAccounts have automountServiceAccountToken=false"
fi
# Also echo the pod-usage condition per the audit logic
pods_using_default_sa=$(kubectl get pods --all-namespaces -o json | jq '
[.items[] | select(.spec.serviceAccountName == "default")] | length')
if [ "$pods_using_default_sa" -gt 0 ]; then
echo "[!] There are still pods using the 'default' ServiceAccount: $pods_using_default_sa"
echo " Create explicit ServiceAccounts and update these workloads to use them."
else
echo "[+] No pods are using the 'default' ServiceAccount."
fi