Ensure Cluster-Admin Role Is Only Used Where Required
More Info:
The cluster-admin ClusterRole grants superuser access over the entire cluster. Bindings to it should be restricted to the minimum set of principals that strictly require it.
Risk Level
Critical
Address
Security
Compliance Standards
- CIS GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with kubectl access, list all ClusterRoleBindings that use the cluster-admin role and see their subjects:
kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECTS:.subjects[*].name | grep cluster-admin -
For each binding identified in step 1, inspect full details (including subject kinds and namespaces):
kubectl get clusterrolebinding <binding-name> -o yamlManually determine whether each non-system subject (Users, Groups, ServiceAccounts) truly requires cluster-wide superuser rights. Do not modify bindings whose name or subjects are system-prefixed (e.g., starting with
system:) and are required for core components. -
For each subject that does not strictly require cluster-admin, design or select a least-privileged Role/ClusterRole that covers only the permissions it needs. For example, create a custom ClusterRole on any machine with kubectl access:
cat << 'EOF' | kubectl apply -f -apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:name: limited-admin-examplerules:- apiGroups: [""]resources: ["pods"]verbs: ["get", "list", "watch"]EOF -
Rebind each affected subject to the least-privileged (Cluster)Role instead of cluster-admin, using a new (Cluster)RoleBinding. Example for a ClusterRole and a user:
cat << 'EOF' | kubectl apply -f -apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:name: limited-admin-example-bindingsubjects:- kind: Username: alice@example.comapiGroup: rbac.authorization.k8s.ioroleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: limited-admin-exampleEOF -
After confirming that alternative bindings are in place and tested for each subject, remove the excessive cluster-admin bindings that are no longer required:
kubectl delete clusterrolebinding <binding-name> -
Verify that no non-system principals remain bound to cluster-admin, on any machine with kubectl access:
kubectl get clusterrolebindings -o json | jq -r '[.items[]| select(.roleRef.name == "cluster-admin")| .subjects[]?| select(.kind != "Group" or .name != "system:masters")]| if length == 0then "NO_CLUSTER_ADMIN_BINDINGS"else "FOUND_CLUSTER_ADMIN_BINDING"end'
Using kubectl
On any machine with kubectl access:
- List all ClusterRoleBindings that use
cluster-adminand review subjects
kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECTS:.subjects[*].name \
| grep 'cluster-admin'
- For each non‑system binding you determine does NOT strictly require
cluster-admin, remove the binding
(Example using a placeholder name; replace with the real binding name you decided to remove.)
kubectl delete clusterrolebinding <binding-name-to-remove>
- (Optional) Create a least‑privilege ClusterRole and bind it instead of
cluster-admin
Example: read‑only access to pods in all namespaces.
Create a manifest file, e.g. readonly-pods-clusterrole.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: readonly-pods
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
Apply it:
kubectl apply -f readonly-pods-clusterrole.yaml
Then bind it to the principal that previously had cluster-admin, for example a user:
kubectl create clusterrolebinding readonly-pods-binding \
--clusterrole=readonly-pods \
--user=<user-identifier>
(or use --group or --serviceaccount <namespace>:<sa-name> as appropriate.)
- Verification (on any machine with kubectl access)
kubectl get clusterrolebindings -o json | jq -r '
[
.items[]
| select(.roleRef.name == "cluster-admin")
| .subjects[]?
| select(.kind != "Group" or .name != "system:masters")
]
| if length == 0
then "NO_CLUSTER_ADMIN_BINDINGS"
else "FOUND_CLUSTER_ADMIN_BINDING"
end
'
Automation
#!/usr/bin/env bash
#
# Purpose:
# Audit and interactively clean up non-system ClusterRoleBindings
# that grant the "cluster-admin" ClusterRole.
#
# Scope:
# Run on any machine with kubectl access to the cluster and
# appropriate permissions to list and delete ClusterRoleBindings.
#
# Idempotency:
# - Read‑only until you confirm deletions.
# - Deleting an already‑deleted binding is not attempted.
# - Safe to re‑run; it will only act on currently existing bindings.
#
# IMPORTANT:
# - This script CANNOT know which principals truly require cluster-admin.
# - For each non‑system binding it shows details and asks you to decide.
# - Before confirming deletion, ensure an appropriate least‑privilege
# Role/ClusterRoleBinding has been created separately.
set -euo pipefail
# Ensure needed tools are present
for cmd in kubectl jq; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "ERROR: Required command '$cmd' not found in PATH." >&2
exit 1
fi
done
echo "=== Step 1: Discover ClusterRoleBindings to 'cluster-admin' ==="
# List all ClusterRoleBindings that reference cluster-admin
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| .metadata.name
' | sort -u > /tmp/clusteradmin_bindings.$$ || true
if [[ ! -s /tmp/clusteradmin_bindings.$$ ]]; then
echo "No ClusterRoleBindings found that reference ClusterRole 'cluster-admin'."
rm -f /tmp/clusteradmin_bindings.$$
exit 0
fi
echo "ClusterRoleBindings referencing 'cluster-admin':"
cat /tmp/clusteradmin_bindings.$$
echo
echo "=== Step 2: Exclude required system bindings (system:*) from modification ==="
echo "NOTE: Bindings whose name starts with 'system:' will be SKIPPED."
to_review=()
while read -r crb; do
[[ -z "$crb" ]] && continue
if [[ "$crb" == system:* ]]; then
echo "Skipping system binding: $crb"
continue
fi
to_review+=("$crb")
done < /tmp/clusteradmin_bindings.$$
if [[ ${#to_review[@]} -eq 0 ]]; then
echo
echo "All ClusterRoleBindings to cluster-admin are system:* bindings."
echo "No non-system principals to remediate."
rm -f /tmp/clusteradmin_bindings.$$
exit 0
fi
echo
echo "Non-system ClusterRoleBindings to review:"
for crb in "${to_review[@]}"; do
echo " - $crb"
done
echo
echo "=== Step 3: Review each non-system binding and decide whether to delete ==="
echo "For each binding you will see its subjects. You must decide if it should"
echo "continue to have cluster-admin, or be removed (after ensuring a suitable"
echo "least-privilege Role/ClusterRole exists and is bound)."
echo
deleted=()
for crb in "${to_review[@]}"; do
echo "------------------------------------------------------------"
echo "Binding: $crb"
echo
# Show full YAML for detailed inspection
kubectl get clusterrolebinding "$crb" -o yaml
echo
echo "This binding grants ClusterRole 'cluster-admin' to the above subjects."
echo "If these subjects do NOT strictly require cluster-admin, you should:"
echo " 1) Create/assign a more limited Role/ClusterRoleBinding separately."
echo " 2) Then remove this excessive binding."
echo
# Prompt for action
while true; do
read -r -p "Delete this ClusterRoleBinding '$crb'? [y/N] " ans
ans=${ans:-n}
case "$ans" in
[Yy]*)
echo "Deleting ClusterRoleBinding '$crb'..."
if kubectl delete clusterrolebinding "$crb"; then
deleted+=("$crb")
else
echo "WARN: Failed to delete ClusterRoleBinding '$crb' (it may already be gone or you lack permissions)." >&2
fi
break
;;
[Nn]*)
echo "Keeping ClusterRoleBinding '$crb'."
break
;;
*)
echo "Please answer y or n."
;;
esac
done
echo
done
rm -f /tmp/clusteradmin_bindings.$$
echo "=== Step 4: Verification (re-run audit logic) ==="
echo "Running the benchmark audit expression to confirm remaining bindings..."
audit_result="$(
kubectl get clusterrolebindings -o json | jq -r '
[
.items[]
| select(.roleRef.name == "cluster-admin")
| .subjects[]?
| select(.kind != "Group" or .name != "system:masters")
]
| if length == 0
then "NO_CLUSTER_ADMIN_BINDINGS"
else "FOUND_CLUSTER_ADMIN_BINDING"
end
'
)"
echo "Audit result: $audit_result"
echo
if [[ "$audit_result" == "NO_CLUSTER_ADMIN_BINDINGS" ]]; then
echo "SUCCESS: No non-system principals are currently bound to ClusterRole 'cluster-admin'."
else
echo "NOTICE: There are still non-system principals bound to 'cluster-admin'."
echo "Review remaining ClusterRoleBindings and repeat this script as needed."
fi
if [[ ${#deleted[@]} -gt 0 ]]; then
echo
echo "Bindings deleted in this run:"
for d in "${deleted[@]}"; do
echo " - $d"
done
fi