Ensure Cluster Admin Role Is Only Used Where Required
More Info:
The RBAC role cluster-admin provides wide-ranging powers over the environment and should be used only where and when needed.
Risk Level
High
Address
Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CIS EKS
- 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
- Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all clusterrolebindings that grant
cluster-admin- Run on: any machine with kubectl access
kubectl get clusterrolebindings -o json | jq -r '.items[]| select(.roleRef.name == "cluster-admin")| .metadata.name' | sort -u -
Inspect each binding’s subjects and note which are non-system users/groups
- Run on: any machine with kubectl access
for crb in $(kubectl get clusterrolebindings -o json | jq -r '.items[]| select(.roleRef.name == "cluster-admin")| .metadata.name' | sort -u); doecho "=== $crb ==="kubectl get clusterrolebinding "$crb" -o yamldone- For each binding, identify subjects where:
kindis notGroup, orkindisGroupandnameis neithersystem:mastersnorsystem:nodes.
-
For each subject that should not have full cluster-admin, design or select a less-privileged role
- Run on: any machine with kubectl access
- Example: create a namespace-scoped admin role (adjust
namespaceand rules as needed):
cat << 'EOF' | kubectl apply -f -apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:name: limited-adminnamespace: defaultrules:- apiGroups: [""]resources: ["pods","services","configmaps"]verbs: ["get","list","watch","create","update","patch","delete"]EOF- Or create a
ClusterRoleif cluster-wide but still reduced permissions are required.
-
Bind affected subjects to the new lower-privilege role/clusterrole
- Run on: any machine with kubectl access
- Example for a
Role(namespace admin):
kubectl create rolebinding limited-admin-binding \--role=limited-admin \--user="alice@example.com" \--namespace=default- Example for a
ClusterRole:
kubectl create clusterrolebinding limited-cluster-access \--clusterrole=limited-cluster-role \--user="alice@example.com" -
Remove unnecessary
cluster-adminbindings once replacement access is in place- Run on: any machine with kubectl access
- To delete a whole binding that is no longer needed:
kubectl delete clusterrolebinding <clusterrolebinding-name>- If a binding mixes system groups with non-system subjects, recreate it without the non-system subjects:
# backupkubectl get clusterrolebinding <name> -o yaml > /tmp/<name>.yaml# edit out unwanted subjectssed -i '/name: alice@example.com/,+2 d' /tmp/<name>.yaml# reapplykubectl delete clusterrolebinding <name>kubectl apply -f /tmp/<name>.yaml
-
Verify that only allowed subjects retain
cluster-admin- Run 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" and .name != "system:nodes"))| "FOUND_CLUSTER_ADMIN_BINDING"' || echo "NO_CLUSTER_ADMIN_BINDINGS"- Confirm the output is
NO_CLUSTER_ADMIN_BINDINGSand no non-system subjects are reported.
Using kubectl
On any machine with kubectl access:
- List all ClusterRoleBindings that grant
cluster-admin
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| .metadata.name
' | sort -u
- Inspect each binding and its subjects to decide if
cluster-adminis really required
Replace<crb-name>with each name from step 1:
kubectl get clusterrolebinding <crb-name> -o yaml
- (Optional) Create or bind to a lower-privilege role instead of
cluster-admin
Example: create a constrained ClusterRole (edit rules as appropriate for your use case), save as limited-admin-clusterrole.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: limited-admin
rules:
- apiGroups: [""]
resources: ["pods","services","configmaps","secrets"]
verbs: ["get","list","watch","create","update","patch","delete"]
- apiGroups: ["apps"]
resources: ["deployments","daemonsets","statefulsets","replicasets"]
verbs: ["get","list","watch","create","update","patch","delete"]
Apply it:
kubectl apply -f limited-admin-clusterrole.yaml
Then bind a user/group/serviceaccount to this new role, e.g. user@example.com, as limited-admin-<name>.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: limited-admin-user-example-com
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: limited-admin
subjects:
- kind: User
name: user@example.com
apiGroup: rbac.authorization.k8s.io
Apply it:
kubectl apply -f limited-admin-user-example-com.yaml
- Remove unnecessary
cluster-adminClusterRoleBindings
After you have confirmed that affected identities work with reduced privileges (or no access, if appropriate), delete each unnecessary binding:
kubectl delete clusterrolebinding <crb-name>
Repeat for all ClusterRoleBindings that should no longer grant cluster-admin.
- Verification
Run the benchmark audit command again and ensure it prints NO_CLUSTER_ADMIN_BINDINGS (or only finds the allowed system groups):
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| .subjects[]?
| select(.kind != "Group" or (.name != "system:masters" and .name != "system:nodes"))
| "FOUND_CLUSTER_ADMIN_BINDING"
' || echo "NO_CLUSTER_ADMIN_BINDINGS"
Automation
#!/usr/bin/env bash
#
# Purpose:
# Identify and clean up risky clusterrolebindings that grant cluster-admin
# to subjects other than the default system:masters and system:nodes groups.
#
# Requirements:
# - Run on any machine with kubectl access and correct kubeconfig.
# - jq and kubectl must be installed and in PATH.
#
# Behavior:
# - For each offending ClusterRoleBinding to cluster-admin:
# * Prints the binding and its non-system subjects.
# * Prompts for confirmation before deletion (per binding).
# - Safe to re-run; deleted bindings stay deleted.
#
# NOTE:
# This script does NOT automatically create replacement lower-privilege roles.
# You must ensure that affected subjects either do not need cluster-admin
# or already have appropriate alternative bindings before confirming deletion.
set -euo pipefail
# Fail early if dependencies are missing
for bin in kubectl jq; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "ERROR: Required binary '$bin' not found in PATH" >&2
exit 1
fi
done
echo "Discovering ClusterRoleBindings that grant 'cluster-admin' to non-system subjects..."
echo
# Get all clusterrolebindings that reference cluster-admin
mapfile -t CRBS < <(
kubectl get clusterrolebindings -o json |
jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "cluster-admin")
| .metadata.name
' | sort -u
)
if [ "${#CRBS[@]}" -eq 0 ]; then
echo "No ClusterRoleBindings reference the 'cluster-admin' ClusterRole."
echo "Verification:"
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| .subjects[]?
| select(.kind != "Group" or (.name != "system:masters" and .name != "system:nodes"))
| "FOUND_CLUSTER_ADMIN_BINDING"
' || true
exit 0
fi
deleted_any=false
for crb in "${CRBS[@]}"; do
# Extract subjects of interest: anything except system:masters or system:nodes groups
subjects_json=$(kubectl get clusterrolebinding "$crb" -o json)
mapfile -t SUBJECTS < <(
jq -r '
.subjects[]? |
select(.kind != "Group" or (.name != "system:masters" and .name != "system:nodes")) |
"\(.kind) \(.apiGroup // "") \(.namespace // "") \(.name)"
' <<<"$subjects_json" | sed 's/ / /g'
)
# If this CRB has no non-system subjects, skip it
if [ "${#SUBJECTS[@]}" -eq 0 ]; then
continue
fi
echo "--------------------------------------------------------------------------------"
echo "ClusterRoleBinding: $crb"
echo "Grants 'cluster-admin' to the following non-system subjects:"
for s in "${SUBJECTS[@]}"; do
echo " - $s"
done
echo
echo "Per remediation guidance, these subjects should typically be bound to"
echo "less-privileged roles if they do not strictly require cluster-admin."
echo
# Confirm deletion interactively
read -r -p "Delete this ClusterRoleBinding '$crb'? [y/N]: " ans
case "$ans" in
y|Y|yes|YES)
echo "Deleting ClusterRoleBinding '$crb'..."
kubectl delete clusterrolebinding "$crb"
deleted_any=true
;;
*)
echo "Skipping deletion of '$crb'."
;;
esac
echo
done
echo "--------------------------------------------------------------------------------"
echo "Post-change verification:"
# Re-run the benchmark audit logic to prove compliance
audit_output=$(kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| .subjects[]?
| select(.kind != "Group" or (.name != "system:masters" and .name != "system:nodes"))
| "FOUND_CLUSTER_ADMIN_BINDING"
')
if [ -z "$audit_output" ]; then
echo "NO_CLUSTER_ADMIN_BINDINGS"
echo "No remaining non-system subjects are bound to 'cluster-admin'."
else
echo "$audit_output"
echo
echo "There are still ClusterRoleBindings granting 'cluster-admin' to non-system subjects."
echo "Review those bindings and re-run this script as needed."
fi
# Exit code reflects whether non-compliant bindings remain
if [ -z "$audit_output" ]; then
exit 0
else
exit 1
fi