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

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. List all default service accounts and their token mounting setting
    Run on: any machine with kubectl access

    kubectl get serviceaccount --all-namespaces --field-selector metadata.name=default -o wide
    kubectl get serviceaccount --all-namespaces --field-selector metadata.name=default -o=json | jq -r '.items[] | "namespace: \(.metadata.namespace), automountServiceAccountToken: \(.automountServiceAccountToken // "notset")"'
  2. Review workloads that currently use a default service account
    Run on: any machine with kubectl access
    For each namespace, list pods and their service accounts:

    for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
    echo "=== Namespace: $ns ==="
    kubectl get pods -n "$ns" -o custom-columns='NAME:.metadata.name,SERVICEACCOUNT:.spec.serviceAccountName' --no-headers 2>/dev/null | grep -E 'default$' || true
    done

    Decide which workloads must access the API; plan to move those to explicit service accounts.

  3. Create explicit service accounts for workloads that need API access
    Run on: any machine with kubectl access
    For each namespace and workload that needs API access, create a dedicated service account, then bind appropriate RBAC (example for namespace my-namespace and service account app-sa):

    kubectl create serviceaccount app-sa -n my-namespace

    kubectl create role app-sa-role \
    --verb=get,list,watch \
    --resource=pods \
    -n my-namespace

    kubectl create rolebinding app-sa-rb \
    --role=app-sa-role \
    --serviceaccount=my-namespace:app-sa \
    -n my-namespace

    Adjust verbs/resources to your security requirements.

  4. Update workloads to stop using the default service account
    Run on: any machine with kubectl access
    For each deployment/statefulset/daemonset/etc. that should use a dedicated service account, patch the spec to set serviceAccountName explicitly (example for a deployment):

    kubectl patch deployment my-deployment -n my-namespace \
    --type merge \
    -p '{"spec":{"template":{"spec":{"serviceAccountName":"app-sa"}}}}'

    Repeat for all workloads currently using the default service account that require API access.

  5. Disable token auto-mounting on all default service accounts
    Run on: any machine with kubectl access
    For each namespace that has a default service account, patch it to set automountServiceAccountToken: false:

    for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
    if kubectl get serviceaccount default -n "$ns" >/dev/null 2>&1; then
    kubectl patch serviceaccount default -n "$ns" \
    --type merge \
    -p '{"automountServiceAccountToken":false}'
    fi
    done
  6. Verify that default service accounts are not actively used and have token mounting disabled
    Run on: any machine with kubectl access
    Confirm automountServiceAccountToken is false on all default service accounts:

    kubectl get serviceaccount --all-namespaces --field-selector metadata.name=default -o=json | \
    jq -r '.items[] | "namespace: \(.metadata.namespace), kind: \(.kind), name: \(.metadata.name), automountServiceAccountToken: \(.automountServiceAccountToken // "notset")"'

    Confirm no pods are using default where that is not an intentional decision:

    for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
    echo "=== Namespace: $ns ==="
    kubectl get pods -n "$ns" -o custom-columns='NAME:.metadata.name,SERVICEACCOUNT:.spec.serviceAccountName' --no-headers 2>/dev/null | grep -E 'default$' || true
    done
Using kubectl
# 1) Review current usage of default service accounts
# Run on: any machine with kubectl access

# List all default serviceaccounts and their automountServiceAccountToken setting
kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default -o=json | \
jq -r '.items[] | "namespace: \(.metadata.namespace), kind: \(.kind), name: \(.metadata.name), automountServiceAccountToken: \(.automountServiceAccountToken | if . == null then "notset" else . end )"' | \
xargs -L 1

# List workloads that currently use the default service account (per namespace)
# Example for one namespace; repeat per namespace as needed
NAMESPACE=default
kubectl get pods -n "$NAMESPACE" -o json | \
jq -r '.items[] | select(.spec.serviceAccountName == null or .spec.serviceAccountName == "default") |
"pod: \(.metadata.name), sa: \(.spec.serviceAccountName // "default")"'


# 2) Create explicit service accounts for workloads currently using "default"
# Run on: any machine with kubectl access

# Example: create a dedicated service account in a specific namespace
NAMESPACE=default
NEW_SA=my-app-sa

kubectl create serviceaccount "$NEW_SA" -n "$NAMESPACE"

# Patch a specific deployment (or other controller) to use the new service account
# Adjust kind/name/namespace as needed
kubectl patch deployment my-app-deployment -n "$NAMESPACE" \
--type merge \
-p "{\"spec\":{\"template\":{\"spec\":{\"serviceAccountName\":\"$NEW_SA\"}}}}"

# Repeat for all workloads that should NOT use the namespace's default service account.


# 3) Disable token automount on each default service account
# Run on: any machine with kubectl access

# Patch a single namespace's default service account
NAMESPACE=default
kubectl patch serviceaccount default -n "$NAMESPACE" \
--type merge \
-p '{"automountServiceAccountToken": false}'

# Optionally, disable automount at the pod level for extra safety
# Example for a deployment
kubectl patch deployment my-app-deployment -n "$NAMESPACE" \
--type merge \
-p '{"spec":{"template":{"spec":{"automountServiceAccountToken": false}}}}'

# Bulk patch: all namespaces' default service accounts
# (Be cautious; ensure workloads have explicit service accounts first.)
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
kubectl patch serviceaccount default -n "$ns" \
--type merge \
-p '{"automountServiceAccountToken": false}' || echo "skip ns=$ns"
done


# 4) Declarative manifest approach (example for one namespace)
# Run on: any machine with kubectl access

# Export the default serviceaccount manifest, edit, and apply
NAMESPACE=default

kubectl get serviceaccount default -n "$NAMESPACE" -o yaml > default-sa-$NAMESPACE.yaml

# Edit default-sa-$NAMESPACE.yaml and ensure under "metadata:" / "spec:" you have:
# ---
# apiVersion: v1
# kind: ServiceAccount
# metadata:
# name: default
# namespace: <your-namespace>
# automountServiceAccountToken: false
# ---

kubectl apply -f default-sa-$NAMESPACE.yaml


# 5) Verification
# Run on: any machine with kubectl access

kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default -o=json | \
jq -r '.items[] | "namespace: \(.metadata.namespace), kind: \(.kind), name: \(.metadata.name), automountServiceAccountToken: \(.automountServiceAccountToken | if . == null then "notset" else . end )"' | \
xargs -L 1
Automation
#!/usr/bin/env bash
# Automation for: Ensure Default Service Accounts Are Not Actively Used (CIS Kubernetes 5.1.5)
# Run on: any machine with kubectl access and jq installed

set -euo pipefail

echo "==> Checking connectivity and prerequisites"
kubectl version --short >/dev/null 2>&1
jq --version >/dev/null 2>&1

echo "==> Discovering all default service accounts"
DEFAULT_SAS_JSON=$(kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default -o json)

COUNT=$(echo "${DEFAULT_SAS_JSON}" | jq '.items | length')
echo "Found ${COUNT} default service account(s)"

if [ "${COUNT}" -eq 0 ]; then
echo "No default service accounts found. Nothing to change."
exit 0
fi

echo "==> Patching default service accounts to set automountServiceAccountToken=false"
# Idempotent patch; safe to re-run
kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' \
| while read -r NAMESPACE NAME; do
if [ -z "${NAMESPACE}" ] || [ -z "${NAME}" ]; then
continue
fi
echo "Patching serviceaccount '${NAME}' in namespace '${NAMESPACE}'"
kubectl patch serviceaccount "${NAME}" -n "${NAMESPACE}" \
--type=merge \
-p '{"automountServiceAccountToken": false}' >/dev/null
done

echo "==> Verification: listing default service accounts and automountServiceAccountToken"
kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default -o=json \
| jq -r '.items[]
| "namespace: \(.metadata.namespace), kind: \(.kind), name: \(.metadata.name), automountServiceAccountToken: \(.automountServiceAccountToken | if . == null then "notset" else . end )"' \
| xargs -L 1

echo "==> Check that all reported values for automountServiceAccountToken are 'false'"

Additional Reading: