Ensure Default Service Accounts Are Not Actively Used
More Info:
Default service accounts should not be granted permissions or actively used by workloads. Create explicit service accounts and disable token automounting on default accounts.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
From any machine with kubectl access, list all namespaces and identify which have a
defaultservice account using tokens:kubectl get sa --all-namespaceskubectl get sa default --all-namespaces -o yaml | grep -E 'name: default$|automountServiceAccountToken' -
For each namespace, identify workloads that are implicitly or explicitly using the
defaultservice account:kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}' | sortReview pods that show an empty
serviceAccountName(implicitly default) orserviceAccountName: defaultto determine whether they truly need API access. -
For pods using the default service account, inspect their specs for explicit token mounting or inherited defaults:
# Example for one podkubectl -n <namespace> get pod <pod-name> -o yaml | grep -A3 serviceAccountkubectl -n <namespace> get pod <pod-name> -o yaml | grep -i automountServiceAccountToken -nDecide whether each workload needs Kubernetes API access and, if so, what minimum permissions it needs.
-
Where workloads require API access, design explicit, least-privilege service accounts and RBAC:
# Example skeleton to adapt per appkubectl -n <namespace> create serviceaccount <sa-name># Then bind minimal roles as appropriate (example only):kubectl -n <namespace> create role <role-name> --verb=get,list,watch --resource=podskubectl -n <namespace> create rolebinding <rb-name> --role=<role-name> --serviceaccount=<namespace>:<sa-name>Update the corresponding Deployment/StatefulSet/Job manifests to set
spec.template.spec.serviceAccountName: <sa-name>and, if needed,automountServiceAccountToken: truethere instead of relying on defaults. -
After migrating workloads off the default service account in a namespace, disable token automount on that default account:
kubectl -n <namespace> patch serviceaccount default -p $'automountServiceAccountToken: false'If using GitOps/manifests, ensure the same setting is defined in the ServiceAccount manifest rather than patching only live state.
-
Verify that:
- No workloads are using the
defaultservice account except where consciously accepted, and - All default service accounts have token automount disabled:
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}' | sort | grep ' default$' || echo "No pods using default SA"kubectl get sa default --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.automountServiceAccountToken}{"\n"}{end}' | sort - No workloads are using the
Using kubectl
# 1. List all namespaces and see which ones have a default ServiceAccount
# Run on: any machine with kubectl access
kubectl get serviceaccount default --all-namespaces -o wide
Problem indication:
- Every namespace will have a
defaultServiceAccount; that alone is not an issue. - Note any namespaces where workloads exist and you expect them to use dedicated service accounts instead of
default.
# 2. Inspect whether default ServiceAccounts auto-mount tokens
# Run on: any machine with kubectl access
kubectl get serviceaccount default --all-namespaces -o yaml
Problem indication:
automountServiceAccountToken: true(explicit) on a default ServiceAccount is a concern.- Missing
automountServiceAccountTokenmeans it inherits from pod spec / cluster defaults; treat as needing review. - Tokens listed under
secrets:confirm token creation, but not necessarily active use.
# 3. Find pods that are using the default ServiceAccount
# 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}' \
| sort
Problem indication:
- Any pod where the third column is
defaultis actively using the default ServiceAccount. - Focus on namespaces with sensitive workloads or where API access should be tightly scoped.
# 4. For pods using default SA, check if tokens are being auto-mounted
# Example for all pods, filtering to those with default SA
# Run on: any machine with kubectl access
kubectl get pods --all-namespaces -o json \
| jq -r '.items[]
| select(.spec.serviceAccountName=="default")
| "\(.metadata.namespace)\t\(.metadata.name)\t\(.spec.automountServiceAccountToken)"'
Problem indication:
truemeans the pod is set to mount a token.nullmeans it may inherit from the ServiceAccount (or cluster default); requires manual review of both pod and SA.
If jq is not available, inspect specific pods:
# Inspect a specific pod using default SA
# Run on: any machine with kubectl access
kubectl get pod <pod-name> -n <namespace> -o yaml
Problem indication in pod YAML:
spec.serviceAccountName: defaultANDspec.automountServiceAccountToken: true(or absent when SA/cluster default is true) AND- A projected service account token volume in
spec.volumesor a token under/var/run/secrets/kubernetes.io/serviceaccount.
# 5. Review access granted via default ServiceAccounts (ClusterRoleBinding/RoleBinding)
# Run on: any machine with kubectl access
kubectl get rolebinding,clusterrolebinding --all-namespaces -o yaml \
| grep -A5 -n "kind: ServiceAccount" | grep -B2 "name: default"
Problem indication:
- Any
RoleBindingorClusterRoleBindingwhosesubjectsinclude:kind: ServiceAccountname: default- plus a
namespace:value
- Especially concerning if bound to powerful
Role/ClusterRole(e.g.,cluster-admin, broad*verbs/resources).
# 6. (Optional) Summarize which namespaces have default SA bound to roles
# Run on: any machine with kubectl access
kubectl get rolebinding --all-namespaces -o json \
| jq -r '.items[]
| . as $rb
| .subjects[]
| select(.kind=="ServiceAccount" and .name=="default")
| "\($rb.metadata.namespace)\t\($rb.metadata.name)\t\($rb.roleRef.kind)\t\($rb.roleRef.name)"'
kubectl get clusterrolebinding -o json \
| jq -r '.items[]
| . as $crb
| .subjects[]
| select(.kind=="ServiceAccount" and .name=="default")
| "CLUSTER\t\($crb.metadata.name)\t\($crb.roleRef.kind)\t\($crb.roleRef.name)\tNS:\(.namespace)"'
Problem indication:
- Any line returned indicates a default ServiceAccount is granted explicit RBAC permissions.
- The more privileged the referenced
Role/ClusterRole, the higher the concern.
Use these outputs to decide:
- Where new, explicit ServiceAccounts should be created.
- Where to stop using
defaultin pod specs. - Where
automountServiceAccountToken: falseshould be set on default ServiceAccounts and/or pods.
Automation
#!/usr/bin/env bash
# Report use and configuration of default service accounts across the cluster.
# Run on: any machine with kubectl access and current-context pointing to the target cluster.
set -o errexit
set -o nounset
set -o pipefail
echo "=== 1) Default ServiceAccount configuration in all namespaces ==="
echo "Namespace,SA,automountServiceAccountToken"
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)
]
| @csv
'
cat <<'EOF'
Interpretation:
- default SA with "true" => RISK: explicitly enabled token automount.
- default SA with "null(...)" => RISK: inherits behavior; confirm namespace/pod-level automount.
- default SA with "false" => Preferred: token automount disabled at SA level.
EOF
echo "=== 2) Pods currently using the default ServiceAccount ==="
echo "Namespace,Pod,ServiceAccount,SA_automount,pod.spec.automountServiceAccountToken"
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| select(.spec.serviceAccountName=="default" or .spec.serviceAccount==null)
| . as $pod
| ($pod.spec.serviceAccountName // "default") as $sa
| $pod.metadata.namespace as $ns
| (
([
.items[]
| select(.kind=="ServiceAccount")? # safeguard, usually not present in pod list
] | length) as $dummy # no-op
) // empty
' >/dev/null 2>&1 || true
# We need SA data; fetch separately and join via jq.
pods_json="$(kubectl get pods --all-namespaces -o json)"
sas_json="$(kubectl get sa --all-namespaces -o json)"
jq -rn --argfile pods <(printf '%s\n' "$pods_json") --argfile sas <(printf '%s\n' "$sas_json") '
$pods.items[]
| select(.spec.serviceAccountName=="default" or (.spec.serviceAccountName==null and .spec.serviceAccount==null))
| . as $p
| ($p.spec.serviceAccountName // "default") as $sa
| ($sas.items[]
| select(.metadata.namespace==$p.metadata.namespace and .metadata.name==$sa)
| .automountServiceAccountToken) as $sa_auto
| [
$p.metadata.namespace,
$p.metadata.name,
$sa,
(if $sa_auto==true then "true"
elif $sa_auto==false then "false"
else "null(inherited)" end),
(if $p.spec.automountServiceAccountToken==true then "true"
elif $p.spec.automountServiceAccountToken==false then "false"
else "null(unset)" end)
]
| @csv
'
cat <<'EOF'
Interpretation (Pods):
- Any row listed here means a pod is using the "default" ServiceAccount.
- Risk increases when:
- SA_automount == "true", or
- SA_automount == "null(inherited)" and pod/spec defaults to automounting, or
- pod.spec.automountServiceAccountToken == "true".
- Review these workloads and migrate them to explicit ServiceAccounts with least-privilege RBAC.
EOF
echo "=== 3) Namespaces that still bind RBAC directly to the default ServiceAccount ==="
echo "Namespace,RoleBinding,Kind,SubjectKind,SubjectName"
kubectl get rolebindings,clusterrolebindings --all-namespaces -o json \
| jq -r '
.items[]
| . as $rb
| ($rb.subjects // [])[]
| select(.kind=="ServiceAccount" and .name=="default")
| [
($rb.metadata.namespace // "cluster-scope"),
$rb.metadata.name,
$rb.kind,
.kind,
.name
]
| @csv
'
cat <<'EOF'
Interpretation (RBAC):
- Any line here indicates the "default" ServiceAccount is explicitly granted roles.
- These are high-priority for remediation:
- Create dedicated ServiceAccounts.
- Update bindings to reference those instead of "default".
- Optionally set automountServiceAccountToken: false on default SAs.
EOF
echo "=== 4) Quick summary indicators ==="
echo "- Count of namespaces where default SA does NOT have automountServiceAccountToken=false:"
kubectl get sa --all-namespaces -o json \
| jq -r '
[ .items[]
| select(.metadata.name=="default")
| select(.automountServiceAccountToken!=false)
| .metadata.namespace
] | unique | length
'
echo "- Count of pods using the default ServiceAccount:"
kubectl get pods --all-namespaces -o json \
| jq -r '
[ .items[]
| select(.spec.serviceAccountName=="default" or (.spec.serviceAccountName==null and .spec.serviceAccount==null))
] | length
'
cat <<'EOF'
Cluster is closer to compliant when:
- All default ServiceAccounts show automountServiceAccountToken = false.
- The count of pods using the default ServiceAccount is 0 (or only known, accepted exceptions).
- No RoleBinding/ClusterRoleBinding subjects the default ServiceAccount.
EOF