Skip to main content

Restrict Use Of Cluster-Admin Role

More Info:

The cluster-admin role grants unrestricted access and should be bound only where absolutely required. Rebind subjects to lower-privileged roles and remove unnecessary cluster-admin bindings.

Risk Level

Critical

Address

Security

Compliance Standards

  • CIS OKE

Triage and Remediation

Remediation

Manual Steps
  1. List all cluster-admin bindings (any machine with kubectl access)

    kubectl get clusterrolebinding -o jsonpath='{range .items[?(@.roleRef.kind=="ClusterRole" && @.roleRef.name=="cluster-admin")]}{.metadata.name}{"\n"}{end}'
    kubectl get clusterrolebinding -o wide
  2. Inspect each binding’s subjects and usage context
    For each binding name from step 1:

    kubectl get clusterrolebinding <BINDING_NAME> -o yaml

    Review:

    • subjects: kind (User, Group, ServiceAccount), names, namespaces.
    • Any associated namespace or application these subjects belong to (e.g., by matching serviceAccount names to workloads).
  3. Decide if cluster-admin is truly required
    For each subject, answer:

    • Does it need cluster-wide permissions (across all namespaces, nodes, CRDs)?
    • Is it for a human user (often does not need full admin; consider namespace-scoped roles) or a system component (may need elevated but often more targeted rights)?
    • Is it used only for a specific app/namespace/action that could be covered by a custom ClusterRole or namespaced Role instead of cluster-admin?
  4. Design and apply least-privilege alternatives
    Where full cluster-admin is not justified:

    • Identify the minimal API groups, resources, and verbs needed.
    • Create a custom role/clusterrole manifest and apply it:
      kubectl apply -f <NEW_ROLE_OR_CLUSTERROLE_MANIFEST>.yaml
      kubectl apply -f <NEW_ROLEBINDING_OR_CLUSTERROLEBINDING_MANIFEST>.yaml
    • Ensure the new binding targets the same subject(s) (user/group/serviceaccount) as the old cluster-admin binding.
  5. Safely remove unnecessary cluster-admin bindings
    After confirming the replacement role works (e.g., subject can perform required operations but nothing more):

    kubectl delete clusterrolebinding <BINDING_NAME>

    If unsure, disable in stages (e.g., remove for non-production users first, or test in a staging cluster) before removing in production.

  6. Re-verify cluster-admin usage
    Confirm only justified bindings remain:

    kubectl get clusterrolebinding -o jsonpath='{range .items[?(@.roleRef.kind=="ClusterRole" && @.roleRef.name=="cluster-admin")]}{.metadata.name}{"\n"}{end}'

    Manually validate each remaining binding is documented and explicitly approved as requiring unrestricted cluster access.

Using kubectl
# 1. List all ClusterRoleBindings that grant cluster-admin
# Run on: any machine with kubectl access
kubectl get clusterrolebindings \
-o jsonpath='{range .items[?(@.roleRef.kind=="ClusterRole" && @.roleRef.name=="cluster-admin")]}{.metadata.name}{"\n"}{end}'

If this prints any names, those bindings grant cluster-admin and must be reviewed.

# 2. Inspect each suspicious ClusterRoleBinding in detail
# Replace BINDING_NAME with each name from the previous command
kubectl get clusterrolebinding BINDING_NAME -o yaml

In the subjects: section, look for:

  • Broad subjects such as:
    • kind: Group with names like system:authenticated, system:serviceaccounts, or any large team group.
    • kind: ServiceAccount without namespace scoping issues (e.g., generic default SAs).
  • Non-essential human users or CI/CD accounts that do not genuinely need full-cluster admin.

These indicate over-privileged access.

# 3. Get a concise view of who has cluster-admin
kubectl get clusterrolebinding \
-o custom-columns='NAME:.metadata.name,ROLE:.roleRef.name,SUBJECT_KIND:.subjects[*].kind,SUBJECT_NAME:.subjects[*].name,SUBJECT_NS:.subjects[*].namespace' \
| grep cluster-admin

Lines where ROLE is cluster-admin and SUBJECT_KIND is a broad group or a non-critical user/service account are likely problematic and should be reviewed for least privilege.

# 4. (Optional) See all subjects bound to any role with admin-like power
kubectl get clusterrolebindings -o yaml | \
egrep '^(kind: ClusterRoleBinding| name: | roleRef:| name: cluster-admin)' -n

Use this to visually confirm where cluster-admin appears and cross-check against your identity / RBAC design. Any unexpected or legacy bindings here are candidates for replacement with more restrictive roles and eventual deletion.

Automation
#!/usr/bin/env bash
#
# Report all uses of the cluster-admin role across the cluster.
# Run on: any machine with kubectl access and current-context pointing to the target cluster.

set -o errexit
set -o nounset
set -o pipefail

echo "=== ClusterRoleBindings referencing cluster-admin ==="
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")
| .metadata.name as $crb
| .subjects[]
| [$crb, .kind, (.namespace // "-"), .name]
| @tsv
' 2>/dev/null || {
echo "Failed to list clusterrolebindings or parse with jq." >&2
exit 1
}

echo
echo "Columns: CLUSTERROLEBINDING SUBJECT_KIND SUBJECT_NAMESPACE SUBJECT_NAME"
echo

echo "=== Namespaced RoleBindings that (possibly) escalate to cluster-admin via aggregation or misconfiguration ==="
echo "(These DO NOT necessarily grant cluster-admin, but should be reviewed if they reference a ClusterRole named cluster-admin.)"
kubectl get rolebindings --all-namespaces -o json \
| jq -r '
.items[]
| select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")
| .metadata.namespace as $ns
| .metadata.name as $rb
| .subjects[]
| [$ns, $rb, .kind, (.namespace // "-"), .name]
| @tsv
' 2>/dev/null || echo "No RoleBindings directly referencing a ClusterRole named cluster-admin found."

echo
echo "Columns: BINDING_NAMESPACE ROLEBINDING SUBJECT_KIND SUBJECT_NAMESPACE SUBJECT_NAME"
echo

echo "=== ServiceAccounts with cluster-admin via ClusterRoleBinding (quick summary) ==="
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")
| .metadata.name as $crb
| .subjects[]
| select(.kind=="ServiceAccount")
| [$crb, .namespace, .name]
| @tsv
' 2>/dev/null || echo "No ServiceAccounts bound to cluster-admin via ClusterRoleBinding."

echo
echo "Columns: CLUSTERROLEBINDING SA_NAMESPACE SA_NAME"
echo

cat <<'EOF'

HOW TO INTERPRET RESULTS

Problematic / needs review:
- Any ClusterRoleBinding listed above, especially where:
- SUBJECT_KIND is "User" or "Group" for broad identities (e.g., "system:authenticated", large SSO groups).
- SUBJECT_KIND is "ServiceAccount" in default or application namespaces that do not require full cluster control.

Lower risk (but still review):
- A small number of tightly controlled admin identities, with clear operational justification.

Next steps (manual review required):
- For each subject, decide if full cluster-admin is truly required.
- Where possible, create or reuse a less-privileged ClusterRole/Role and:
- Bind the subject to that role instead.
- Then delete the cluster-admin ClusterRoleBinding:
kubectl delete clusterrolebinding <CLUSTERROLEBINDING_NAME>
EOF