Skip to main content

Ensure Default Service Accounts 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 EKS
  • 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

Manual Steps
  1. List all default service accounts and identify ones still auto-mounting tokens (run on any machine with kubectl access):

    kubectl get serviceaccounts --all-namespaces -o json | jq -r '
    .items[]
    | select(.metadata.name=="default" and (.automountServiceAccountToken != false))
    | "\(.metadata.namespace)"'
  2. Disable token auto-mount for the default service account in each affected namespace (run on any machine with kubectl access; repeat for each NAMESPACE from step 1):

    NAMESPACE=default # replace with each namespace name
    kubectl patch serviceaccount default -n "$NAMESPACE" --type merge -p \
    '{"automountServiceAccountToken": false}'
  3. Identify pods currently using the default service account (run on any machine with kubectl access):

    kubectl get pods --all-namespaces -o json | jq -r '
    .items[]
    | select(.spec.serviceAccountName == "default")
    | "\(.metadata.namespace) \(.metadata.name)"'
  4. For each workload using the default service account, create a dedicated service account (run on any machine with kubectl access; repeat per namespace as needed):

    NAMESPACE=default # namespace of the workload
    SA_NAME=app-sa-example # choose a name per application
    kubectl create serviceaccount "$SA_NAME" -n "$NAMESPACE"
  5. Update workloads (Deployments/StatefulSets/etc.) to use the new service account (run on any machine with kubectl access; repeat per workload):

    NAMESPACE=default # namespace of the workload
    WORKLOAD=your-deployment-name # name of Deployment/StatefulSet/Job/etc.

    # Edit the object and set spec.template.spec.serviceAccountName to the new SA
    kubectl -n "$NAMESPACE" edit deployment "$WORKLOAD"
    # In the editor, under spec.template.spec, ensure:
    # serviceAccountName: app-sa-example
  6. Verify that default service accounts are not auto-mounting tokens and not used by pods (run on any machine with kubectl access):

    default_sa_count=$(kubectl get serviceaccounts --all-namespaces -o json | jq '
    [.items[] | select(.metadata.name == "default" and (.automountServiceAccountToken != false))] | length')
    pods_using_default_sa=$(kubectl get pods --all-namespaces -o json | jq '
    [.items[] | select(.spec.serviceAccountName == "default")] | length')

    echo "default_sa_count=$default_sa_count"
    echo "pods_using_default_sa=$pods_using_default_sa"
Using kubectl
# 1) Disable token automount on all existing default ServiceAccounts
# Run on: any machine with kubectl access

for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
kubectl patch serviceaccount default \
-n "$ns" \
--type='merge' \
-p '{"automountServiceAccountToken": false}'
done


# 2) Identify Pods currently using the default ServiceAccount
# (for each such Pod/Deployment/Job/etc, update its spec to use a
# dedicated ServiceAccount you create for that workload)

kubectl get pods --all-namespaces \
-o jsonpath='{range .items[?(@.spec.serviceAccountName=="default")]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}'


# Example manifest: create an explicit ServiceAccount and use it in a Deployment
# Save as app-sa.yaml and apply with `kubectl apply -f app-sa.yaml`

apiVersion: v1
kind: ServiceAccount
metadata:
name: app-sa
namespace: default
automountServiceAccountToken: true
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
serviceAccountName: app-sa
containers:
- name: my-app
image: nginx:stable

# Apply the manifest
kubectl apply -f app-sa.yaml


# 3) Verification
# Run the same logic as the audit (no output means pass)

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 "default_sa_not_auto_mounted"
fi
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 ]; then
echo "default_sa_used_in_pods"
fi
Automation
#!/usr/bin/env bash
set -euo pipefail

# This script:
# 1. Sets automountServiceAccountToken: false on all existing "default" ServiceAccounts.
# 2. Identifies pods using the default ServiceAccount for manual follow‑up.
# 3. Verifies the setting across the cluster.
#
# Run from any machine with kubectl access and a current context.

echo "[*] Ensuring all 'default' ServiceAccounts have automountServiceAccountToken: false"

# Get all namespaces
namespaces=$(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}')

for ns in $namespaces; do
# Check if default SA exists in this namespace
if ! kubectl get sa default -n "$ns" >/dev/null 2>&1; then
continue
fi

# Check current value; may be null/absent/true/false
current=$(kubectl get sa default -n "$ns" -o jsonpath='{.automountServiceAccountToken}' 2>/dev/null || echo "")

if [ "$current" = "false" ]; then
echo "[=] Namespace '$ns': default SA already has automountServiceAccountToken=false"
continue
fi

echo "[+] Patching default ServiceAccount in namespace '$ns' to set automountServiceAccountToken=false"
kubectl patch serviceaccount default -n "$ns" --type merge -p '{"automountServiceAccountToken": false}' >/dev/null
done

echo
echo "[*] Listing pods that currently use the 'default' ServiceAccount (for manual remediation)"

kubectl get pods --all-namespaces -o json | jq -r '
.items[]
| select(.spec.serviceAccountName == "default")
| [.metadata.namespace, .metadata.name, .spec.serviceAccountName]
| @tsv' | awk 'BEGIN{printf "%-30s %-40s %-20s\n","NAMESPACE","POD","SERVICEACCOUNT";print substr("--------------------------------------------------------------------------------------------------------------------------------",1,95)} {printf "%-30s %-40s %-20s\n",$1,$2,$3}' || true

echo
echo "[*] Verification: re-running benchmark-style checks"

# Check for any default SAs that do NOT have automountServiceAccountToken=false
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 "[!] VERIFICATION FAILED: Some 'default' ServiceAccounts still do not have automountServiceAccountToken=false"
echo " Count: $default_sa_count"
echo " Details:"
kubectl get serviceaccounts --all-namespaces -o json | jq -r '
.items[]
| select(.metadata.name == "default" and (.automountServiceAccountToken != false))
| [.metadata.namespace, .metadata.name, (.automountServiceAccountToken // "null")]
| @tsv' | awk 'BEGIN{printf "%-30s %-20s %-10s\n","NAMESPACE","SERVICEACCOUNT","AUTO_MOUNT";print substr("------------------------------------------------------------",1,60)} {printf "%-30s %-20s %-10s\n",$1,$2,$3}'
exit 1
else
echo "[+] VERIFICATION PASSED: All 'default' ServiceAccounts have automountServiceAccountToken=false"
fi

# Also echo the pod-usage condition per the audit logic
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 ]; then
echo "[!] There are still pods using the 'default' ServiceAccount: $pods_using_default_sa"
echo " Create explicit ServiceAccounts and update these workloads to use them."
else
echo "[+] No pods are using the 'default' ServiceAccount."
fi

Additional Reading: