Ensure Default Service Accounts Are 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 GKE
- 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
-
Identify default service accounts and workloads using them (run on any machine with kubectl access)
kubectl get serviceaccounts --all-namespaces \-o jsonpath='{range .items[?(@.metadata.name=="default")]}{.metadata.namespace}{"\n"}{end}'kubectl get pods --all-namespaces \-o jsonpath='{range .items[?(@.spec.serviceAccountName=="default")]}{@.metadata.namespace}{"\t"}{@.metadata.name}{"\n"}{end}' -
Create explicit service accounts to replace
defaultwhere needed (run on any machine with kubectl access; repeat per namespace)# Example: create a dedicated service account for a workload in namespace my-namespacekubectl create serviceaccount app-sa -n my-namespace# If workloads need specific RBAC, bind roles to the new service account, e.g.:kubectl create rolebinding app-sa-view \--clusterrole=view \--serviceaccount=my-namespace:app-sa \-n my-namespace -
Update pods/workloads to stop using the
defaultServiceAccount (run on any machine with kubectl access; repeat per workload)- For Deployments, StatefulSets, DaemonSets, Jobs, etc., edit the manifest and set
spec.template.spec.serviceAccountName:
# Example for a Deploymentkubectl -n my-namespace edit deployment my-deployment- In the editor, under
spec.template.spec, add or change:
serviceAccountName: app-sa- Save and exit to trigger a rollout using the new service account.
- For Deployments, StatefulSets, DaemonSets, Jobs, etc., edit the manifest and set
-
Disable token automount on each
defaultServiceAccount (run on any machine with kubectl access; repeat per namespace that has an active default SA)# Example for namespace my-namespacekubectl -n my-namespace patch serviceaccount default \-p '{"automountServiceAccountToken": false}' -
Optionally enforce pod-level override for any remaining pods (run on any machine with kubectl access; repeat where needed)
- For any pod spec that must explicitly ensure no token automount (and still uses
defaultor no SA set), edit and set:
kubectl -n my-namespace edit deployment my-deployment- Under
spec.template.spec, add:
automountServiceAccountToken: false - For any pod spec that must explicitly ensure no token automount (and still uses
-
Verify remediation (run on any machine with kubectl access)
echo "๐น Default Service Accounts with automountServiceAccountToken enabled:"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 ]; thenecho "default_sa_not_auto_mounted"elseecho "OK: all default serviceaccounts have automountServiceAccountToken=false"fiecho "\n๐น Pods using default ServiceAccount:"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 ]; thenecho "default_sa_used_in_pods"elseecho "OK: no pods are using the default serviceaccount"fi
Using kubectl
Using kubectlโ
-
Identify default ServiceAccounts with token automount enabled
Run on: any machine with kubectl accesskubectl get serviceaccounts --all-namespaces -o json \| jq -r '.items[]| select(.metadata.name=="default" and (.automountServiceAccountToken != false))| "\(.metadata.namespace)"' \| sort -u -
Disable token automount on each default ServiceAccount
Replace<NAMESPACE>with each namespace from the previous command.
Run on: any machine with kubectl accesskubectl patch serviceaccount default \-n <NAMESPACE> \--type merge \-p '{"automountServiceAccountToken": false}'To apply this for all current namespaces in one go (requires bash + jq):
for ns in $(kubectl get serviceaccounts --all-namespaces -o json \| jq -r '.items[]| select(.metadata.name=="default" and (.automountServiceAccountToken != false))| .metadata.namespace' | sort -u); dokubectl patch serviceaccount default \-n "$ns" \--type merge \-p '{"automountServiceAccountToken": false}'done -
Ensure workloads do not use the default ServiceAccount
For each deployment/statefulset/cronjob/etc. that currently usesserviceAccountName: default, update its manifest to use an explicit ServiceAccount, for example:Example ServiceAccount manifest (apply per namespace as needed):
apiVersion: v1kind: ServiceAccountmetadata:name: my-app-sanamespace: my-namespaceautomountServiceAccountToken: trueExample Deployment manifest snippet:
apiVersion: apps/v1kind: Deploymentmetadata:name: my-appnamespace: my-namespacespec:template:spec:serviceAccountName: my-app-saApply:
kubectl apply -f my-app-serviceaccount.yamlkubectl apply -f my-app-deployment.yaml -
Verification
Run on: any machine with kubectl accessecho "๐น Default Service Accounts with automountServiceAccountToken enabled:"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 ]; thenecho "default_sa_not_auto_mounted"elseecho "OK: all default ServiceAccounts have automountServiceAccountToken=false"fiechoecho "๐น Pods using default ServiceAccount:"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 ]; thenecho "default_sa_used_in_pods"elseecho "OK: no pods are using the default ServiceAccount"fi
Automation
#!/usr/bin/env bash
set -euo pipefail
# This script:
# 1. Ensures all *default* ServiceAccounts have automountServiceAccountToken: false
# 2. Identifies Pods still using the default ServiceAccount (for follow-up)
#
# Run on: any machine with kubectl access and jq installed.
# Fail fast if required tools are missing
for cmd in kubectl jq; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "ERROR: $cmd not found in PATH" >&2
exit 1
fi
done
echo "๐ง Disabling automountServiceAccountToken on all default ServiceAccounts..."
# Get all namespaces
namespaces=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}')
for ns in $namespaces; do
# Check if a default SA exists in this namespace
if ! kubectl get sa default -n "$ns" >/dev/null 2>&1; then
continue
fi
# Patch to ensure automountServiceAccountToken: false (idempotent)
kubectl patch sa default -n "$ns" \
--type merge \
-p '{"automountServiceAccountToken": false}' >/dev/null
echo " - Patched default ServiceAccount in namespace: $ns"
done
echo
echo "โ
Verification: checking default ServiceAccounts' automountServiceAccountToken..."
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 "โ Some default ServiceAccounts still have automountServiceAccountToken enabled:"
kubectl get serviceaccounts --all-namespaces -o json | jq -r '
.items[]
| select(.metadata.name == "default" and (.automountServiceAccountToken != false))
| "\(.metadata.namespace)/\(.metadata.name): automountServiceAccountToken=\(.automountServiceAccountToken)"'
exit 1
else
echo "โ
All default ServiceAccounts now have automountServiceAccountToken: false"
fi
echo
echo "๐ Pods currently using the default ServiceAccount (for manual review/migration):"
pods_using_default_sa=$(kubectl get pods --all-namespaces -o json | jq '
[.items[] | select(.spec.serviceAccountName == "default")]')
pods_count=$(echo "$pods_using_default_sa" | jq 'length')
if [ "$pods_count" -gt 0 ]; then
echo "โ ๏ธ Found $pods_count Pod(s) using the default ServiceAccount:"
echo "$pods_using_default_sa" | jq -r '.[] | "\(.metadata.namespace)/\(.metadata.name)"'
echo
echo "NOTE:"
echo "- Create explicit ServiceAccounts per workload with the required permissions."
echo "- Update each Pod/Deployment/etc. spec to set spec.serviceAccountName to the new account."
else
echo "โ
No Pods are currently using the default ServiceAccount."
fi