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
Remediation
Manual Steps
-
List all default service accounts and their token mounting setting
Run on: any machine with kubectl accesskubectl get serviceaccount --all-namespaces --field-selector metadata.name=default -o widekubectl get serviceaccount --all-namespaces --field-selector metadata.name=default -o=json | jq -r '.items[] | "namespace: \(.metadata.namespace), automountServiceAccountToken: \(.automountServiceAccountToken // "notset")"' -
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}'); doecho "=== Namespace: $ns ==="kubectl get pods -n "$ns" -o custom-columns='NAME:.metadata.name,SERVICEACCOUNT:.spec.serviceAccountName' --no-headers 2>/dev/null | grep -E 'default$' || truedoneDecide which workloads must access the API; plan to move those to explicit service accounts.
-
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 namespacemy-namespaceand service accountapp-sa):kubectl create serviceaccount app-sa -n my-namespacekubectl create role app-sa-role \--verb=get,list,watch \--resource=pods \-n my-namespacekubectl create rolebinding app-sa-rb \--role=app-sa-role \--serviceaccount=my-namespace:app-sa \-n my-namespaceAdjust verbs/resources to your security requirements.
-
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 setserviceAccountNameexplicitly (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
defaultservice account that require API access. -
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 setautomountServiceAccountToken: false:for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); doif kubectl get serviceaccount default -n "$ns" >/dev/null 2>&1; thenkubectl patch serviceaccount default -n "$ns" \--type merge \-p '{"automountServiceAccountToken":false}'fidone -
Verify that default service accounts are not actively used and have token mounting disabled
Run on: any machine with kubectl access
ConfirmautomountServiceAccountTokenis 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
defaultwhere that is not an intentional decision:for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); doecho "=== Namespace: $ns ==="kubectl get pods -n "$ns" -o custom-columns='NAME:.metadata.name,SERVICEACCOUNT:.spec.serviceAccountName' --no-headers 2>/dev/null | grep -E 'default$' || truedone
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'"