Skip to main content

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​

Manual Steps
  1. From any machine with kubectl access, list all namespaces and identify which have a default service account using tokens:

    kubectl get sa --all-namespaces
    kubectl get sa default --all-namespaces -o yaml | grep -E 'name: default$|automountServiceAccountToken'
  2. For each namespace, identify workloads that are implicitly or explicitly using the default service account:

    kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}' | sort

    Review pods that show an empty serviceAccountName (implicitly default) or serviceAccountName: default to determine whether they truly need API access.

  3. For pods using the default service account, inspect their specs for explicit token mounting or inherited defaults:

    # Example for one pod
    kubectl -n <namespace> get pod <pod-name> -o yaml | grep -A3 serviceAccount
    kubectl -n <namespace> get pod <pod-name> -o yaml | grep -i automountServiceAccountToken -n

    Decide whether each workload needs Kubernetes API access and, if so, what minimum permissions it needs.

  4. Where workloads require API access, design explicit, least-privilege service accounts and RBAC:

    # Example skeleton to adapt per app
    kubectl -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=pods
    kubectl -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: true there instead of relying on defaults.

  5. 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.

  6. Verify that:

    • No workloads are using the default service 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
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 default ServiceAccount; 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 automountServiceAccountToken means 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 default is 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:

  • true means the pod is set to mount a token.
  • null means 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: default AND
  • spec.automountServiceAccountToken: true (or absent when SA/cluster default is true) AND
  • A projected service account token volume in spec.volumes or 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 RoleBinding or ClusterRoleBinding whose subjects include:
    • kind: ServiceAccount
    • name: 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 default in pod specs.
  • Where automountServiceAccountToken: false should 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