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 AKS
- CIS Critical Security Controls v8
- 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 all default service accounts and their token settings
- Run on: any machine with kubectl access
kubectl get serviceaccounts --all-namespaces \-o jsonpath='{range .items[?(@.metadata.name=="default")]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.automountServiceAccountToken}{"\n"}{end}'- Review which namespaces have
defaultSAs withautomountServiceAccountTokennot set or set totrue.
-
Find pods currently using the default service account
- Run on: any machine with kubectl access
kubectl get pods --all-namespaces \-o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}' \| awk '$3=="default"'- Review each listed pod and determine whether it should instead use a dedicated service account.
-
Review permissions granted to default service accounts
- Run on: any machine with kubectl access
# ClusterRoleBindings involving default SAskubectl get clusterrolebindings -o yaml | grep -B4 -A6 "kind: ServiceAccount" | grep -B10 -A10 "name: default"# RoleBindings involving default SAs, per namespacefor ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); doecho "### Namespace: $ns"kubectl get rolebindings -n "$ns" -o yaml | grep -B4 -A6 "kind: ServiceAccount" | grep -B10 -A10 "name: default" || truedone- For each binding involving a
defaultSA, assess whether those permissions are necessary or should be moved to a custom service account.
-
Plan and create custom service accounts and bindings where needed
- For each workload that should not use the
defaultSA, design a least-privilege service account and roles. Example (adjust names and rules):
# Run on: any machine with kubectl accesskubectl create serviceaccount app-sa -n my-namespacecat <<'EOF' | kubectl apply -f -apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:name: app-sa-rolenamespace: my-namespacerules:- apiGroups: [""]resources: ["pods"]verbs: ["get", "list"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:name: app-sa-rbnamespace: my-namespacesubjects:- kind: ServiceAccountname: app-sanamespace: my-namespaceroleRef:kind: Rolename: app-sa-roleapiGroup: rbac.authorization.k8s.ioEOF- Update pod manifests / deployments to use the new service account (
spec.serviceAccountName: app-sa) and re-deploy them.
- For each workload that should not use the
-
Disable token automount on default service accounts
- After moving workloads off the default SAs, disable token mounting as per the remediation. Run on: any machine with kubectl access
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); dokubectl patch serviceaccount default -n "$ns" \-p '{"automountServiceAccountToken": false}' || truedone- If any namespace must legitimately use the default SA, document the justification and consider leaving
automountServiceAccountTokenenabled only there.
-
Verify no active workloads rely on default service accounts
- Run on: any machine with kubectl access
# Confirm automount is disabled on default SAskubectl get serviceaccounts --all-namespaces \-o jsonpath='{range .items[?(@.metadata.name=="default")]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.automountServiceAccountToken}{"\n"}{end}'# Confirm no pods (or only documented exceptions) use default SAkubectl get pods --all-namespaces \-o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}' \| awk '$3=="default"'- Investigate and resolve any remaining pods using
defaultSAs that do not have an explicit, documented justification.
Using kubectl
Using kubectl
1. List all namespaces and their default service accounts
Run on: any machine with kubectl access.
kubectl get ns
For each namespace, check the default service account and whether it auto-mounts tokens:
# Example loop to inspect all namespaces
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
echo "Namespace: $ns"
kubectl get sa default -n "$ns" -o yaml | grep -E '^(kind:|metadata:| name:|automountServiceAccountToken:)' || echo " (no default SA found)"
echo
done
Indicates a problem: In any namespace, default service account shows automountServiceAccountToken: true or has no automountServiceAccountToken field (which means it may inherit a default true from pod spec or namespace).
2. Find pods that are using the default service account
# All pods and their serviceAccounts
kubectl get pods -A -o custom-columns='NAMESPACE:.metadata.namespace,POD:.metadata.name,SA:.spec.serviceAccountName' | sort
Indicates a problem: Any workload pod (non-system, non-addon) shows SA as default in its namespace.
You can narrow this down by excluding well-known system namespaces:
kubectl get pods -A \
--field-selector=status.phase=Running \
-o custom-columns='NAMESPACE:.metadata.namespace,POD:.metadata.name,SA:.spec.serviceAccountName' \
| egrep -v '^(kube-system|kube-public|kube-node-lease|default)[[:space:]]' \
| sort
Then manually review pods still showing SA as default.
3. Inspect specific pods that might be problematic
For any suspicious pod:
kubectl get pod <pod-name> -n <namespace> -o yaml
Review:
.spec.serviceAccountName— is itdefault?.spec.automountServiceAccountToken— is ittrueor omitted (thus possibly inheritingtrue)?
Indicates a problem: Application pods using serviceAccountName: default and/or auto-mounting tokens when they do not need Kubernetes API access.
4. Check namespace-level default for automountServiceAccountToken
kubectl get ns <namespace> -o yaml | grep -A3 'annotations:'
Review for any namespace annotation like:
kubernetes.io/allow-automount-service-account-token: "true"
Indicates a problem: Namespaces where policy or annotations cause broad token auto-mounting, increasing the likelihood that default SAs are used with tokens.
5. Verification after you make manual changes
After you have manually switched workloads to custom service accounts and updated defaults as needed, re-run:
kubectl get pods -A -o custom-columns='NAMESPACE:.metadata.namespace,POD:.metadata.name,SA:.spec.serviceAccountName' | sort
Confirm that application workloads no longer use SA = default, and re-run the default SA inspection loop to confirm automountServiceAccountToken is set according to your chosen policy.
Automation
#!/usr/bin/env bash
# Report default ServiceAccount usage and token automount settings across the cluster.
# Run on: any machine with kubectl access and current-context pointing to the target cluster.
set -euo pipefail
echo "=== 1) Default ServiceAccount automountServiceAccountToken settings ==="
echo
kubectl get sa --all-namespaces -o json \
| jq -r '
.items[]
| select(.metadata.name == "default")
| [
.metadata.namespace,
.metadata.name,
(if .automountServiceAccountToken == true then "true"
elif .automountServiceAccountToken == false then "false"
else "null(inherited)" end)
]
| @tsv' \
| awk 'BEGIN{printf "%-30s %-15s %-25s\n","NAMESPACE","SERVICEACCOUNT","AUTOMOUNT_SA_TOKEN"; print gensub(/./,"-","g",sprintf("%-30s %-15s %-25s"," "," "," "));} {printf "%-30s %-15s %-25s\n",$1,$2,$3;}'
cat <<'EOF'
Interpretation:
- Any row where AUTOMOUNT_SA_TOKEN is "true" or "null(inherited)" indicates a potential problem.
Remediation guidance:
- Prefer: set automountServiceAccountToken: false directly on the default SA in that namespace.
- And: use dedicated ServiceAccounts for workloads instead of the default.
EOF
echo "=== 2) Pods using the default ServiceAccount (explicitly or implicitly) ==="
echo
# List all running/non-terminated Pods and show which SA they use and whether they mount the token.
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| select(.status.phase != "Succeeded" and .status.phase != "Failed")
| [
.metadata.namespace,
.metadata.name,
(.spec.serviceAccountName // "default"),
# detect automount: pod-level override, then SA default (unknown here), then default=true
(if .spec.automountServiceAccountToken == true then "true(pod-override)"
elif .spec.automountServiceAccountToken == false then "false(pod-override)"
else "unknown(SA/default)" end)
]
| @tsv' \
| awk 'BEGIN{printf "%-30s %-45s %-20s %-30s\n","NAMESPACE","POD","SERVICEACCOUNT","AUTOMOUNT_TOKEN_EFFECTIVE?"; print gensub(/./,"-","g",sprintf("%-30s %-45s %-20s %-30s"," "," "," "," "));} {printf "%-30s %-45s %-20s %-30s\n",$1,$2,$3,$4;}'
cat <<'EOF'
Interpretation:
- Any POD where SERVICEACCOUNT == "default" is using the default ServiceAccount.
This is a review finding and usually NOT desired for application workloads.
- The AUTOMOUNT_TOKEN_EFFECTIVE? column:
- "true(pod-override)" means the Pod explicitly mounts a token (high risk).
- "false(pod-override)" means the Pod explicitly disables token mount (lower risk).
- "unknown(SA/default)" means the effective value comes from the ServiceAccount or cluster default.
Cross-check with section (1) above for that namespace.
Focus for review:
- Identify namespaces and Pods where:
- SERVICEACCOUNT == "default"
- and/or default SA in that namespace has automountServiceAccountToken true or null(inherited)
- Decide:
- Should this workload have a dedicated ServiceAccount with scoped RBAC?
- Should automountServiceAccountToken be set to false on:
- the Pod spec, and/or
- the default ServiceAccount in that namespace?
NOTE: This script is read-only and does NOT change any resources. Use the findings to
manually:
- create and bind least-privilege ServiceAccounts for workloads, and
- set automountServiceAccountToken: false on default ServiceAccounts where appropriate.
EOF