Skip to main content

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

Manual Steps
  1. On any machine with kubectl access, list all ClusterRoleBindings that grant cluster-admin and see which subjects they bind:

    kubectl get clusterrolebindings -o wide
    kubectl get clusterrolebindings -o yaml | grep -A5 "roleRef:\s*name: cluster-admin"
  2. For each such ClusterRoleBinding, inspect full details and note the subjects (Users/Groups/ServiceAccounts):

    kubectl get clusterrolebinding <binding-name> -o yaml

    Decide, 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).

  3. For subjects that do not require full cluster-admin, either:

    • Rebind them to an existing less‑privileged ClusterRole:
      kubectl create clusterrolebinding <new-binding-name> \
      --clusterrole=<less-privileged-clusterrole> \
      --user=<user-name>
      or for a ServiceAccount:
      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.
  4. Once a subject has suitable alternative permissions, remove unnecessary ClusterRoleBindings to cluster-admin as recommended:

    kubectl delete clusterrolebinding <binding-name>

    Only delete after confirming no remaining operational dependency on that binding.

  5. Ensure that only the intended high‑privilege groups remain bound to cluster-admin (typically system:masters or other tightly controlled admin groups), and avoid binding arbitrary users, service accounts, or broad groups (like system:authenticated) to this role.

  6. 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‑approved cluster-admin bindings have been removed.

Using kubectl

On any machine with kubectl access:

  1. List all ClusterRoleBindings that grant cluster-admin:
kubectl get clusterrolebindings -o wide
  1. Inspect each binding’s subjects to decide who truly needs full admin:
kubectl get clusterrolebindings -o yaml | less
  1. 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
  1. After validating that the alternative access works, delete unnecessary cluster-admin ClusterRoleBindings (do NOT delete those you still need, and typically retain default system bindings such as for system:masters):
kubectl delete clusterrolebinding NAME_OF_BINDING_TO_REMOVE
  1. Verification (expected output is NO_CLUSTER_ADMIN_BINDINGS or 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"