Ensure The Cluster-Admin Role Is Only Used Where Required
More Info:
The cluster-admin role grants unrestricted access across the cluster. It should only be bound to subjects that genuinely require full administrative privileges.
Risk Level
Critical
Address
Security
Compliance Standards
- CIS EKS
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with kubectl access, list all ClusterRoleBindings that grant
cluster-adminand see which subjects they bind:kubectl get clusterrolebindings -o widekubectl get clusterrolebindings -o yaml | grep -A5 "roleRef:\s*name: cluster-admin" -
For each such ClusterRoleBinding, inspect full details and note the subjects (Users/Groups/ServiceAccounts):
kubectl get clusterrolebinding <binding-name> -o yamlDecide, with your application/cluster owners, which subjects truly require full cluster-wide admin rights and which can be reduced to more limited roles (for example, namespace‑scoped admin or a custom Role/ClusterRole).
-
For subjects that do not require full
cluster-admin, either:- Rebind them to an existing less‑privileged ClusterRole:
or for a ServiceAccount:kubectl create clusterrolebinding <new-binding-name> \--clusterrole=<less-privileged-clusterrole> \--user=<user-name>kubectl create clusterrolebinding <new-binding-name> \--clusterrole=<less-privileged-clusterrole> \--serviceaccount=<namespace>:<sa-name>
- Or, if appropriate, bind them only within a namespace using a Role/RoleBinding instead of a ClusterRoleBinding.
- Rebind them to an existing less‑privileged ClusterRole:
-
Once a subject has suitable alternative permissions, remove unnecessary ClusterRoleBindings to
cluster-adminas recommended:kubectl delete clusterrolebinding <binding-name>Only delete after confirming no remaining operational dependency on that binding.
-
Ensure that only the intended high‑privilege groups remain bound to
cluster-admin(typicallysystem:mastersor other tightly controlled admin groups), and avoid binding arbitrary users, service accounts, or broad groups (likesystem:authenticated) to this role. -
Verification (on any machine with kubectl access): confirm no unexpected subjects are bound to
cluster-admin: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"If the output is only
NO_CLUSTER_ADMIN_BINDINGS, all non‑approvedcluster-adminbindings have been removed.
Using kubectl
On any machine with kubectl access:
- List all ClusterRoleBindings that grant
cluster-admin:
kubectl get clusterrolebindings -o wide
- Inspect each binding’s subjects to decide who truly needs full admin:
kubectl get clusterrolebindings -o yaml | less
- For each subject that should have reduced privileges, create or bind a lower-privilege role (example: namespace-scoped admin instead of cluster-admin).
Example: create a namespace admin Role and RoleBinding (adjust metadata.name, metadata.namespace, and subjects as needed):
cat << 'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ns-admin
namespace: default
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
EOF
cat << 'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ns-admin-binding
namespace: default
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: ns-admin
apiGroup: rbac.authorization.k8s.io
EOF
- After validating that the alternative access works, delete unnecessary
cluster-adminClusterRoleBindings (do NOT delete those you still need, and typically retain default system bindings such as forsystem:masters):
kubectl delete clusterrolebinding NAME_OF_BINDING_TO_REMOVE
- Verification (expected output is
NO_CLUSTER_ADMIN_BINDINGSor only system groups excluded by the query):
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
set -euo pipefail
# Automation for: CISEKS 4.1.1
# Scope: run on any machine with kubectl access and appropriate RBAC privileges.
# This script:
# 1. Lists all ClusterRoleBindings to "cluster-admin" excluding:
# - Groups: system:masters, system:nodes (as per audit)
# 2. Prints them for human review and confirmation.
# 3. Optionally deletes selected ClusterRoleBindings.
# 4. Verifies the result using the benchmark audit logic.
#
# Idempotent: re-running will skip already-deleted bindings.
# Requirements: kubectl, jq
command -v kubectl >/dev/null 2>&1 || { echo "kubectl not found in PATH"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found in PATH"; exit 1; }
echo "Discovering ClusterRoleBindings referencing cluster-admin..."
# Collect all ClusterRoleBindings that reference cluster-admin and have at least
# one subject that is NOT the exempted system groups.
mapfile -t CRBS < <(
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| select(
(.subjects // [])[]
| ( .kind != "Group"
or ( .name != "system:masters" and .name != "system:nodes")
)
)
| .metadata.name
' | sort -u
)
if [[ ${#CRBS[@]} -eq 0 ]]; then
echo "No non-system ClusterRoleBindings to cluster-admin found."
else
echo
echo "The following ClusterRoleBindings reference cluster-admin and have non-exempt subjects:"
for crb in "${CRBS[@]}"; do
echo "---------------------------------------------------------------------"
echo "ClusterRoleBinding: ${crb}"
kubectl get clusterrolebinding "${crb}" -o yaml
done
echo
echo "Review the above bindings. For each, decide if full cluster-admin is truly required."
echo "Recommendation: create or bind to a lower-privilege Role/ClusterRole first where possible."
echo
read -r -p "Proceed to select and delete ClusterRoleBindings from the above list? (yes/no): " CONFIRM
if [[ "${CONFIRM}" == "yes" ]]; then
echo
echo "Enter names to delete, space-separated, or 'all' to delete all listed:"
echo "Available: ${CRBS[*]}"
read -r -p "Delete: " TO_DELETE
DELETE_LIST=()
if [[ "${TO_DELETE}" == "all" ]]; then
DELETE_LIST=("${CRBS[@]}")
else
# Validate user input against discovered CRBs
for item in ${TO_DELETE}; do
if printf '%s\n' "${CRBS[@]}" | grep -qx "${item}"; then
DELETE_LIST+=("${item}")
else
echo "Skipping unknown ClusterRoleBinding: ${item}"
fi
done
fi
if [[ ${#DELETE_LIST[@]} -eq 0 ]]; then
echo "No valid ClusterRoleBinding selected for deletion. Exiting without changes."
else
echo
echo "About to delete the following ClusterRoleBindings:"
printf ' %s\n' "${DELETE_LIST[@]}"
read -r -p "Confirm deletion (yes/no): " DEL_CONFIRM
if [[ "${DEL_CONFIRM}" == "yes" ]]; then
for crb in "${DELETE_LIST[@]}"; do
echo "Deleting ClusterRoleBinding: ${crb}"
kubectl delete clusterrolebinding "${crb}" || {
echo "Warning: failed to delete ${crb} (may already be removed or access denied)."
}
done
else
echo "Deletion canceled. No changes made."
fi
fi
else
echo "No deletions performed."
fi
fi
echo
echo "Verification (benchmark-style audit):"
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"