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