Ensure The Cluster-Admin Role Is Only Used Where Required
More Info:
The cluster-admin ClusterRole grants unrestricted superuser access. Bind it only to subjects that genuinely require full cluster control.
Risk Level
Critical
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all ClusterRoleBindings to
cluster-admin- Run on: any machine with kubectl access
kubectl get clusterrolebindings -o yaml | grep -B5 -A5 "name: cluster-admin"kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].name --no-headers -
Review each subject’s actual access needs (manual decision)
- Run on: any machine with kubectl access
For each ClusterRoleBinding identified in step 1, inspect details and note the subjects:
kubectl get clusterrolebinding <clusterrolebinding-name> -o yamlManually determine, based on your org’s policies and the subject’s responsibilities, whether they truly require full cluster-wide admin, or only subset permissions (e.g., namespace admin, read-only, ops).
- Run on: any machine with kubectl access
-
Identify or design least-privilege roles for subjects that do NOT need cluster-admin
- Run on: any machine with kubectl access
If a subject should have reduced permissions, either use an existing ClusterRole/Role or draft one. For example, create a more limited ClusterRole manifest file (edit rules according to your needs):
cat > restricted-admin-clusterrole.yaml << 'EOF'apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:name: restricted-adminrules:# TODO: Fill in with the minimal set of resources, verbs, and API groups actually required# Example:# - apiGroups: [""]# resources: ["pods","services","configmaps"]# verbs: ["get","list","watch","create","update","delete"]EOFkubectl apply -f restricted-admin-clusterrole.yaml - Run on: any machine with kubectl access
-
Create appropriate RoleBindings/ClusterRoleBindings to the reduced-privilege role
- Run on: any machine with kubectl access
For each subject that should no longer usecluster-admin, bind them to the least-privilege role you chose or created. For example, to bind a user to therestricted-adminClusterRole cluster-wide:
cat > restricted-admin-binding-<subject>.yaml << 'EOF'apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:name: restricted-admin-binding-<subject>subjects:- kind: User # or ServiceAccount/Group as appropriatename: <subject-name>apiGroup: rbac.authorization.k8s.ioroleRef:kind: ClusterRolename: restricted-adminapiGroup: rbac.authorization.k8s.ioEOFkubectl apply -f restricted-admin-binding-<subject>.yamlReplace
<subject>and<subject-name>with the actual subject identifier. - Run on: any machine with kubectl access
-
Remove unnecessary
cluster-adminClusterRoleBindings- Run on: any machine with kubectl access
After confirming the subject has appropriate alternative access and no longer needscluster-admin, delete the old binding:
kubectl delete clusterrolebinding <clusterrolebinding-name>Only retain
cluster-adminbindings for subjects that you explicitly decided must keep full cluster control. - Run on: any machine with kubectl access
-
Verification
- Run on: any machine with kubectl access
Re-run the audit logic and confirm no non-cluster-adminrole names are bound tocluster-admin:
kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].name --no-headers | while read -r role_name role_binding subjectdoif [[ "${role_name}" != "cluster-admin" && "${role_binding}" == "cluster-admin" ]]; thenis_compliant="false"elseis_compliant="true"fi;echo "**role_name: ${role_name} role_binding: ${role_binding} subject: ${subject} is_compliant: ${is_compliant}"doneManually check that any remaining
cluster-adminbindings are only for subjects you intentionally approved for full cluster-admin access. - Run on: any machine with kubectl access
Using kubectl
On any machine with kubectl access:
- List all ClusterRoleBindings that reference
cluster-admin
kubectl get clusterrolebindings -o wide
kubectl get clusterrolebindings -o yaml | grep -C4 "name: cluster-admin"
- Inspect each non‑default binding to
cluster-adminand its subjects
kubectl get clusterrolebinding <BINDING_NAME> -o yaml
- For each subject that does not truly need full cluster‑admin, create or use a less‑privileged ClusterRole/Role and bind that instead. Example – if a subject only needs namespace‑scoped access:
Create a namespace Role (edit rules as needed):
cat << 'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-namespace-admin
namespace: default
rules:
- apiGroups: [""]
resources: ["pods","services","configmaps","secrets"]
verbs: ["get","list","watch","create","update","patch","delete"]
EOF
Bind the subject to the lower‑privileged Role (fill in actual subject kind/name):
cat << 'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-namespace-admin-binding
namespace: default
subjects:
- kind: User # or Group/ServiceAccount
name: alice # replace with real subject
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: app-namespace-admin
apiGroup: rbac.authorization.k8s.io
EOF
- Once all necessary replacement bindings are in place and validated, delete the unneeded
cluster-adminClusterRoleBindings:
kubectl delete clusterrolebinding <BINDING_NAME_TO_REMOVE>
Repeat for each unnecessary cluster-admin binding.
- Verification (adapted from the audit):
kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].name --no-headers | while read -r role_name role_binding subject
do
if [[ "${role_name}" != "cluster-admin" && "${role_binding}" == "cluster-admin" ]]; then
is_compliant="false"
else
is_compliant="true"
fi
echo "**role_name: ${role_name} role_binding: ${role_binding} subject: ${subject} is_compliant: ${is_compliant}"
done
Automation
#!/usr/bin/env bash
#
# Remediation for:
# "Ensure The Cluster-Admin Role Is Only Used Where Required"
#
# Scope: any machine with kubectl access to the cluster
#
# This script:
# - Lists all ClusterRoleBindings that grant the cluster-admin ClusterRole
# to non-cluster-admin subjects.
# - For each, it INTERACTIVELY asks whether to delete the binding.
# - Optionally backs up each binding manifest before deletion.
# - Re-runs the audit logic at the end.
#
# Idempotent: safe to re-run; already-deleted bindings are skipped.
set -euo pipefail
BACKUP_DIR="./clusterrolebinding_backups_$(date +%Y%m%d_%H%M%S)"
mkdir -p "${BACKUP_DIR}"
echo "Finding ClusterRoleBindings that bind ClusterRole 'cluster-admin' to non-'cluster-admin' role names..."
echo
# Get all clusterrolebindings with their roleRef.name and subjects[*].name
mapfile -t CRB_LINES < <(kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].name --no-headers)
if [ "${#CRB_LINES[@]}" -eq 0 ]; then
echo "No ClusterRoleBindings found."
exit 0
fi
# Track non-compliant bindings for summary and verification
declare -a NON_COMPLIANT_BINDINGS=()
for line in "${CRB_LINES[@]}"; do
# shellcheck disable=SC2206
arr=($line)
crb_name="${arr[0]}"
role_name="${arr[1]}"
subject="${arr[@]:2}"
# Condition from benchmark:
# is_compliant is false if rolename is not cluster-admin and rolebinding is cluster-admin.
if [[ "${role_name}" != "cluster-admin" ]]; then
# This binding grants cluster-admin ClusterRole to a subject while the binding name is not 'cluster-admin'
NON_COMPLIANT_BINDINGS+=("${crb_name}")
echo "Non-compliant ClusterRoleBinding found:"
echo " NAME: ${crb_name}"
echo " ROLE: ${role_name} (grants ClusterRole 'cluster-admin')"
echo " SUBJECT: ${subject}"
echo
# Show full YAML for review
echo "YAML definition:"
kubectl get clusterrolebinding "${crb_name}" -o yaml
echo
# Confirm backup
read -r -p "Backup this ClusterRoleBinding to ${BACKUP_DIR}/${crb_name}.yaml before any change? [y/N]: " backup_answer
backup_answer="${backup_answer:-N}"
if [[ "${backup_answer}" =~ ^[Yy]$ ]]; then
kubectl get clusterrolebinding "${crb_name}" -o yaml > "${BACKUP_DIR}/${crb_name}.yaml"
echo " Backed up to ${BACKUP_DIR}/${crb_name}.yaml"
fi
echo
echo "Review question:"
echo " Does this subject truly require full cluster-admin privileges?"
echo " If not, you should:"
echo " 1) Create or bind a lower-privilege Role/ClusterRole as appropriate."
echo " 2) Then delete this ClusterRoleBinding."
echo
read -r -p "Delete ClusterRoleBinding '${crb_name}' now? [y/N]: " delete_answer
delete_answer="${delete_answer:-N}"
if [[ "${delete_answer}" =~ ^[Yy]$ ]]; then
echo "Deleting ClusterRoleBinding '${crb_name}'..."
kubectl delete clusterrolebinding "${crb_name}"
echo " Deleted."
else
echo " Skipping deletion of '${crb_name}'."
fi
echo "------------------------------------------------------------"
fi
done
echo
echo "Verification: re-running compliance evaluation..."
echo
kubectl get clusterrolebindings -o=custom-columns=NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT:.subjects[*].name --no-headers | while read -r role_name role_binding subject
do
if [[ "${role_name}" != "cluster-admin" && "${role_binding}" == "cluster-admin" ]]; then
is_compliant="false"
else
is_compliant="true"
fi;
echo "**role_name: ${role_name} role_binding: ${role_binding} subject: ${subject} is_compliant: ${is_compliant}"
done
echo
echo "Review the lines above: any entry with 'is_compliant: false' still needs manual analysis and, if appropriate, further remediation."
echo "Backups (if created) are stored in: ${BACKUP_DIR}"