Skip to main content

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 Critical Security Controls v8
  • CIS GKE
  • 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. Identify default service accounts and workloads using them (run on any machine with kubectl access)

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

    kubectl get pods --all-namespaces \
    -o jsonpath='{range .items[?(@.spec.serviceAccountName=="default")]}{@.metadata.namespace}{"\t"}{@.metadata.name}{"\n"}{end}'
  2. Create explicit service accounts to replace default where needed (run on any machine with kubectl access; repeat per namespace)

    # Example: create a dedicated service account for a workload in namespace my-namespace
    kubectl create serviceaccount app-sa -n my-namespace

    # If workloads need specific RBAC, bind roles to the new service account, e.g.:
    kubectl create rolebinding app-sa-view \
    --clusterrole=view \
    --serviceaccount=my-namespace:app-sa \
    -n my-namespace
  3. Update pods/workloads to stop using the default ServiceAccount (run on any machine with kubectl access; repeat per workload)

    • For Deployments, StatefulSets, DaemonSets, Jobs, etc., edit the manifest and set spec.template.spec.serviceAccountName:
    # Example for a Deployment
    kubectl -n my-namespace edit deployment my-deployment
    • In the editor, under spec.template.spec, add or change:
    serviceAccountName: app-sa
    • Save and exit to trigger a rollout using the new service account.
  4. Disable token automount on each default ServiceAccount (run on any machine with kubectl access; repeat per namespace that has an active default SA)

    # Example for namespace my-namespace
    kubectl -n my-namespace patch serviceaccount default \
    -p '{"automountServiceAccountToken": false}'
  5. Optionally enforce pod-level override for any remaining pods (run on any machine with kubectl access; repeat where needed)

    • For any pod spec that must explicitly ensure no token automount (and still uses default or no SA set), edit and set:
    kubectl -n my-namespace edit deployment my-deployment
    • Under spec.template.spec, add:
    automountServiceAccountToken: false
  6. Verify remediation (run on any machine with kubectl access)

    echo "๐Ÿ”น Default Service Accounts with automountServiceAccountToken enabled:"
    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"
    else
    echo "OK: all default serviceaccounts have automountServiceAccountToken=false"
    fi

    echo "\n๐Ÿ”น Pods using default ServiceAccount:"
    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"
    else
    echo "OK: no pods are using the default serviceaccount"
    fi
Using kubectl

Using kubectlโ€‹

  1. Identify default ServiceAccounts with token automount enabled
    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)"' \
    | sort -u
  2. Disable token automount on each default ServiceAccount
    Replace <NAMESPACE> with each namespace from the previous command.
    Run on: any machine with kubectl access

    kubectl patch serviceaccount default \
    -n <NAMESPACE> \
    --type merge \
    -p '{"automountServiceAccountToken": false}'

    To apply this for all current namespaces in one go (requires bash + jq):

    for ns in $(kubectl get serviceaccounts --all-namespaces -o json \
    | jq -r '.items[]
    | select(.metadata.name=="default" and (.automountServiceAccountToken != false))
    | .metadata.namespace' | sort -u); do
    kubectl patch serviceaccount default \
    -n "$ns" \
    --type merge \
    -p '{"automountServiceAccountToken": false}'
    done
  3. Ensure workloads do not use the default ServiceAccount
    For each deployment/statefulset/cronjob/etc. that currently uses serviceAccountName: default, update its manifest to use an explicit ServiceAccount, for example:

    Example ServiceAccount manifest (apply per namespace as needed):

    apiVersion: v1
    kind: ServiceAccount
    metadata:
    name: my-app-sa
    namespace: my-namespace
    automountServiceAccountToken: true

    Example Deployment manifest snippet:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: my-app
    namespace: my-namespace
    spec:
    template:
    spec:
    serviceAccountName: my-app-sa

    Apply:

    kubectl apply -f my-app-serviceaccount.yaml
    kubectl apply -f my-app-deployment.yaml
  4. Verification
    Run on: any machine with kubectl access

    echo "๐Ÿ”น Default Service Accounts with automountServiceAccountToken enabled:"
    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"
    else
    echo "OK: all default ServiceAccounts have automountServiceAccountToken=false"
    fi

    echo
    echo "๐Ÿ”น Pods using default ServiceAccount:"
    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"
    else
    echo "OK: no pods are using the default ServiceAccount"
    fi
Automation
#!/usr/bin/env bash
set -euo pipefail

# This script:
# 1. Ensures all *default* ServiceAccounts have automountServiceAccountToken: false
# 2. Identifies Pods still using the default ServiceAccount (for follow-up)
#
# Run on: any machine with kubectl access and jq installed.

# Fail fast if required tools are missing
for cmd in kubectl jq; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "ERROR: $cmd not found in PATH" >&2
exit 1
fi
done

echo "๐Ÿ”ง Disabling automountServiceAccountToken on all default ServiceAccounts..."

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

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

# Patch to ensure automountServiceAccountToken: false (idempotent)
kubectl patch sa default -n "$ns" \
--type merge \
-p '{"automountServiceAccountToken": false}' >/dev/null

echo " - Patched default ServiceAccount in namespace: $ns"
done

echo
echo "โœ… Verification: checking default ServiceAccounts' automountServiceAccountToken..."

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 "โŒ Some default ServiceAccounts still have automountServiceAccountToken enabled:"
kubectl get serviceaccounts --all-namespaces -o json | jq -r '
.items[]
| select(.metadata.name == "default" and (.automountServiceAccountToken != false))
| "\(.metadata.namespace)/\(.metadata.name): automountServiceAccountToken=\(.automountServiceAccountToken)"'
exit 1
else
echo "โœ… All default ServiceAccounts now have automountServiceAccountToken: false"
fi

echo
echo "๐Ÿ”Ž Pods currently using the default ServiceAccount (for manual review/migration):"

pods_using_default_sa=$(kubectl get pods --all-namespaces -o json | jq '
[.items[] | select(.spec.serviceAccountName == "default")]')

pods_count=$(echo "$pods_using_default_sa" | jq 'length')

if [ "$pods_count" -gt 0 ]; then
echo "โš ๏ธ Found $pods_count Pod(s) using the default ServiceAccount:"
echo "$pods_using_default_sa" | jq -r '.[] | "\(.metadata.namespace)/\(.metadata.name)"'
echo
echo "NOTE:"
echo "- Create explicit ServiceAccounts per workload with the required permissions."
echo "- Update each Pod/Deployment/etc. spec to set spec.serviceAccountName to the new account."
else
echo "โœ… No Pods are currently using the default ServiceAccount."
fi

Additional Reading:โ€‹