Ensure Default Service Accounts Are Not Actively Used
More Info:
Default service accounts should not be granted permissions or used by workloads. Their tokens should not be auto-mounted.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify all default service accounts and their automount setting
- Run on: any machine with kubectl access
kubectl get serviceaccount --all-namespaces --field-selector metadata.name=default -o wide -
Review workloads currently using default service accounts and decide whether to change them
- Run on: any machine with kubectl access
# Pods explicitly or implicitly using the default service account in each namespacefor ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); doecho "=== Namespace: $ns ==="kubectl get pods -n "$ns" -o wide --field-selector spec.serviceAccountName=default || truedone- For any pod that is not a system or vendor-managed component and still uses the default service account, plan to create and assign a dedicated ServiceAccount with only the permissions it needs.
-
Create explicit service accounts for affected workloads (per namespace)
- Run on: any machine with kubectl access
- Example for one namespace (replace
NAMESPACEandAPP-SAwith your values):
kubectl create serviceaccount app-sa -n NAMESPACE- Update the corresponding RBAC (Roles/ClusterRoles and RoleBindings/ClusterRoleBindings) to grant only the minimum required permissions to
app-sa. For example:
kubectl create rolebinding app-sa-view \--clusterrole=view \--serviceaccount=NAMESPACE:app-sa \-n NAMESPACE -
Update workloads to stop using the default service account
- Run on: any machine with kubectl access
- Edit each affected workload (Deployment/StatefulSet/DaemonSet/CronJob/Job/Pod) to use the new explicit service account, e.g.:
kubectl -n NAMESPACE edit deployment DEPLOYMENT_NAME- Under
spec.template.spec, set:
serviceAccountName: app-sa- Save and exit the editor; Kubernetes will roll out updated pods using the explicit service account.
-
Disable token automount on default service accounts
- Run on: any machine with kubectl access
- For each namespace that has a
defaultservice account:
NAMESPACE=example-namespacekubectl -n "$NAMESPACE" patch serviceaccount default \-p '{"automountServiceAccountToken": false}'- Repeat for all namespaces where you want to prevent automatic token mounting on the default service account.
-
Verify that default service accounts are no longer actively used and have automount disabled
- Run on: any machine with kubectl access
# Check automountServiceAccountToken on all default service accountskubectl 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 )"'# Confirm that no non-system pods are still using the default service accountfor ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); doecho "=== Namespace: $ns ==="kubectl get pods -n "$ns" -o json \| jq -r '.items[] | select(.spec.serviceAccountName=="default") | .metadata.name'done
Using kubectl
On any machine with kubectl access:
- Export all existing default ServiceAccounts to manifests (for review and backup)
kubectl get serviceaccount default --all-namespaces -o yaml > /tmp/default-serviceaccounts-backup.yaml
- Patch all existing default ServiceAccounts to disable token auto-mount
kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default \
-o json | \
kubectl patch -f - \
--type merge \
-p '{"automountServiceAccountToken": false}'
- (Optional) Enforce the setting declaratively for a specific namespace
Example manifest (save as sa-default-patch.yaml):
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: your-namespace
automountServiceAccountToken: false
Apply it:
kubectl apply -f sa-default-patch.yaml
- Ensure new or existing workloads do not rely on the default ServiceAccount
For each deployment/statefulset/cronjob/etc., set an explicit non-default ServiceAccount and (optionally) disable auto-mount at pod level. Example manifest snippet:
apiVersion: apps/v1
kind: Deployment
metadata:
name: example
namespace: your-namespace
spec:
template:
spec:
serviceAccountName: example-sa
automountServiceAccountToken: false
Apply:
kubectl apply -f example-deployment.yaml
- Verification
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
set -euo pipefail
# This script must run on any machine with kubectl and jq configured for the target cluster.
echo "[INFO] Discovering all 'default' ServiceAccounts in the cluster..."
mapfile -t DEFAULT_SAS < <(
kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default \
-o json | jq -r '.items[] | [.metadata.namespace, .metadata.name] | @tsv'
)
if [ "${#DEFAULT_SAS[@]}" -eq 0 ]; then
echo "[INFO] No 'default' ServiceAccounts found."
else
echo "[INFO] Found ${#DEFAULT_SAS[@]} 'default' ServiceAccount(s)."
fi
for entry in "${DEFAULT_SAS[@]}"; do
ns=$(echo "$entry" | awk '{print $1}')
name=$(echo "$entry" | awk '{print $2}')
echo "[INFO] Processing ServiceAccount '$name' in namespace '$ns'..."
# Dump current SA as YAML
tmpfile=$(mktemp)
kubectl get serviceaccount "$name" -n "$ns" -o yaml > "$tmpfile"
# Check current automountServiceAccountToken value
current=$(yq -r '.automountServiceAccountToken // "null"' "$tmpfile")
if [ "$current" = "false" ]; then
echo "[INFO] automountServiceAccountToken already false; no change needed."
rm -f "$tmpfile"
continue
fi
# Ensure top-level automountServiceAccountToken: false is set
# yq v4 syntax
yq -y '.automountServiceAccountToken = false' "$tmpfile" > "${tmpfile}.patched"
echo "[INFO] Applying patch to set automountServiceAccountToken: false ..."
kubectl apply -f "${tmpfile}.patched"
rm -f "$tmpfile" "${tmpfile}.patched"
done
echo "[INFO] Verifying all 'default' ServiceAccounts have automountServiceAccountToken=false ..."
kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default \
-o json | jq -r '
.items[] |
"namespace=\(.metadata.namespace) name=\(.metadata.name) automountServiceAccountToken=\(.automountServiceAccountToken // "notset")"
' | sed 's/^/[RESULT] /'
# Fail if any default SA is still not set to false
non_compliant_count=$(
kubectl get serviceaccount --all-namespaces \
--field-selector metadata.name=default \
-o json | jq '[.items[] | select((.automountServiceAccountToken // false) != false)] | length'
)
if [ "$non_compliant_count" -ne 0 ]; then
echo "[ERROR] $non_compliant_count 'default' ServiceAccount(s) still do not have automountServiceAccountToken=false."
exit 1
fi
echo "[INFO] All 'default' ServiceAccounts now have automountServiceAccountToken=false."