Skip to main content

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

Manual Steps
  1. 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
  2. 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 yaml

    Manually 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).

  3. 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/v1
    kind: ClusterRole
    metadata:
    name: restricted-admin
    rules:
    # 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"]
    EOF

    kubectl apply -f restricted-admin-clusterrole.yaml
  4. Create appropriate RoleBindings/ClusterRoleBindings to the reduced-privilege role

    • Run on: any machine with kubectl access
      For each subject that should no longer use cluster-admin, bind them to the least-privilege role you chose or created. For example, to bind a user to the restricted-admin ClusterRole cluster-wide:
    cat > restricted-admin-binding-<subject>.yaml << 'EOF'
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
    name: restricted-admin-binding-<subject>
    subjects:
    - kind: User # or ServiceAccount/Group as appropriate
    name: <subject-name>
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    kind: ClusterRole
    name: restricted-admin
    apiGroup: rbac.authorization.k8s.io
    EOF

    kubectl apply -f restricted-admin-binding-<subject>.yaml

    Replace <subject> and <subject-name> with the actual subject identifier.

  5. Remove unnecessary cluster-admin ClusterRoleBindings

    • Run on: any machine with kubectl access
      After confirming the subject has appropriate alternative access and no longer needs cluster-admin, delete the old binding:
    kubectl delete clusterrolebinding <clusterrolebinding-name>

    Only retain cluster-admin bindings for subjects that you explicitly decided must keep full cluster control.

  6. Verification

    • Run on: any machine with kubectl access
      Re-run the audit logic and confirm no non-cluster-admin role names are bound to cluster-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 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

    Manually check that any remaining cluster-admin bindings are only for subjects you intentionally approved for full cluster-admin access.

Using kubectl

On any machine with kubectl access:

  1. List all ClusterRoleBindings that reference cluster-admin
kubectl get clusterrolebindings -o wide
kubectl get clusterrolebindings -o yaml | grep -C4 "name: cluster-admin"
  1. Inspect each non‑default binding to cluster-admin and its subjects
kubectl get clusterrolebinding <BINDING_NAME> -o yaml
  1. 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
  1. Once all necessary replacement bindings are in place and validated, delete the unneeded cluster-admin ClusterRoleBindings:
kubectl delete clusterrolebinding <BINDING_NAME_TO_REMOVE>

Repeat for each unnecessary cluster-admin binding.

  1. 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}"