Ensure The Cluster-Admin Role Is Only Used Where Required
More Info:
The cluster-admin ClusterRole grants unrestricted access to the entire cluster. It should only be bound to principals that strictly require full administrative control.
Risk Level
Critical
Address
Security
Compliance Standards
- CIS AKS
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all cluster-admin bindings and their subjects
- Run on: any machine with kubectl access
- Command:
kubectl get clusterrolebindings -o wide | grep 'cluster-admin'kubectl get clusterrolebindings -o yaml | grep -A10 -B2 'name: cluster-admin'
- Purpose: identify every
ClusterRoleBindingthat grantscluster-adminand who/what it is bound to (users, groups, service accounts).
-
For each binding, understand what is using it and why
- For each binding name found (e.g.
cluster-admin,aks-cluster-admin-binding, etc.), inspect details:kubectl get clusterrolebinding <binding-name> -o yaml - Map each subject to:
- Human user / group (e.g. corporate IdP group)
- Service account (note its namespace and owning deployment/workload)
- System or add-on component (e.g. CNI, Ingress, monitoring)
- Check your internal documentation, IaC (Helm, Terraform, etc.), or platform docs to see why this binding was created.
- For each binding name found (e.g.
-
Decide if full cluster-admin is strictly necessary for each subject
For each subject in each binding, ask:- Does it require cluster-wide, unrestricted access (e.g. break-glass admin, platform SRE)?
- Is its actual function limited (e.g. namespaced app, read-only monitor, single-namespace operator)?
- Is there a documented support / vendor requirement for
cluster-admin, or can it be scoped down (namespace-scoped or specific API groups/resources/verbs)? - If multiple subjects are in one binding, evaluate each individually; you may need to split them into separate bindings with different roles.
-
Design and apply least-privilege alternatives where possible
- If
cluster-adminis not strictly required, create or reuse a less-privilegedClusterRole/Rolethat matches the real needs. Examples (adapt to your case):# Example: read-only cluster-wide rolekubectl apply -f - <<'EOF'apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:name: readonly-clusterrules:- apiGroups: [""]resources: ["pods","services","configmaps","namespaces"]verbs: ["get","list","watch"]EOF - Bind that role to the subject instead of
cluster-admin(adjustkind,name,namespaceas identified in step 2):kubectl apply -f - <<'EOF'apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:name: readonly-cluster-bindingsubjects:- kind: Username: <user-identifier>roleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: readonly-clusterEOF - For service accounts that only need namespace access, prefer
Role+RoleBindingin that namespace.
- If
-
Remove or narrow the cluster-admin bindings
- After confirming the replacement access works (functional tests, user confirmation, or workload logs), remove or adjust the
cluster-adminbinding:- To completely remove a no-longer-needed binding:
kubectl delete clusterrolebinding <binding-name>
- To keep the binding but drop unnecessary subjects, edit it interactively:
Remove only the subjects that no longer requirekubectl edit clusterrolebinding <binding-name>
cluster-admin, then save.
- To completely remove a no-longer-needed binding:
- For bindings managed by GitOps/IaC, make the equivalent changes in the source manifests instead of editing live objects directly, then apply/sync.
- After confirming the replacement access works (functional tests, user confirmation, or workload logs), remove or adjust the
-
Verify remaining usage of cluster-admin is minimal and intentional
- Run on: any machine with kubectl access
- Command:
kubectl get clusterrolebindings --no-headers | grep 'cluster-admin' || echo "No cluster-admin bindings found"
- For any remaining bindings, ensure you have documented justification (who, why, and approval) and that they are periodically re-reviewed.
Using kubectl
Using kubectl
Run these commands from any machine with kubectl access.
- List all ClusterRoleBindings that grant
cluster-admin:
kubectl get clusterrolebindings -o wide | grep 'cluster-admin'
If you get no output, there are no current bindings to cluster-admin (no problem for this check).
Any lines returned indicate a binding that must be reviewed.
- View full details of each binding found:
# Example: replace <binding-name> with each name from step 1
kubectl get clusterrolebinding <binding-name> -o yaml
Focus on:
.roleRef.name– must becluster-adminfor this control..subjects– whichUser,Group, orServiceAccountis getting cluster-admin.
Problem indicators:
- Bindings to broad identities such as:
system:authenticated,system:unauthenticated, or other large groups- Wildcard-like groups used by your IdP (e.g., “AllEmployees”)
- ServiceAccounts in application namespaces (e.g.,
default,dev-*,prod-*) that do not clearly require full cluster control. - External users or groups where a more specific, least-privilege ClusterRole could be used instead.
- (Optional) Quickly list just the subjects of all
cluster-adminbindings:
kubectl get clusterrolebindings -o json \
| jq -r '.items[]
| select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")
| .metadata.name as $b
| .subjects[]?
| [$b, .kind, .name, (.namespace // "-")]
| @tsv'
Output columns:
- ClusterRoleBinding name
- Subject kind (
User,Group,ServiceAccount) - Subject name
- Namespace (
-for non-namespaced subjects)
Problem indicators here:
- Many different subjects listed, especially generic groups.
- Any entry where you cannot clearly justify why that subject needs unrestricted cluster-wide admin access.
Automation
#!/usr/bin/env bash
# Report all uses of the cluster-admin ClusterRole and highlight likely risks.
# Run on: any machine with kubectl access and current-context set to target cluster.
set -euo pipefail
echo "== ClusterRoleBindings that grant cluster-admin =="
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "cluster-admin")
| [
.metadata.name,
.subjects // [] | map(.kind + "/" + .name + (if .namespace then " (ns:" + .namespace + ")" else "" end)) | join(", ")
]
| @tsv
' \
| awk 'BEGIN { printf "%-40s %-60s\n", "CLUSTERROLEBINDING", "SUBJECTS"; print gensub(/./,"-","g",sprintf("%-40s %-60s"," "," ")); } { printf "%-40s %-60s\n", $1, substr($0, index($0,$2)) }'
echo
echo "== ClusterRoleBindings with cluster-admin bound to ServiceAccounts (detail) =="
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "cluster-admin")
| {
name: .metadata.name,
subjects: (.subjects // [])
}
| . as $b
| $b.subjects[]
| select(.kind == "ServiceAccount")
| [
$b.name,
.name,
(.namespace // "<none>")
]
| @tsv
' \
| awk 'BEGIN { printf "%-40s %-40s %-20s\n", "CLUSTERROLEBINDING", "SERVICEACCOUNT", "NAMESPACE"; print gensub(/./,"-","g",sprintf("%-40s %-40s %-20s"," "," "," ")); } { printf "%-40s %-40s %-20s\n", $1, $2, $3 }'
echo
echo "== ClusterRoleBindings with cluster-admin bound to Users/Groups (detail) =="
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "cluster-admin")
| {
name: .metadata.name,
subjects: (.subjects // [])
}
| . as $b
| $b.subjects[]
| select(.kind == "User" or .kind == "Group")
| [
$b.name,
.kind,
.name
]
| @tsv
' \
| awk 'BEGIN { printf "%-40s %-10s %-60s\n", "CLUSTERROLEBINDING", "KIND", "IDENTITY"; print gensub(/./,"-","g",sprintf("%-40s %-10s %-60s"," "," "," ")); } { printf "%-40s %-10s %-60s\n", $1, $2, $3 }'
echo
echo "== Summary counts =="
kubectl get clusterrolebindings -o json \
| jq '
.items
| map(select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")) as $b
| {
total_clusterrolebindings_using_cluster_admin: ($b | length),
total_subjects: ($b | map(.subjects // []) | add | length),
by_kind: ($b | map(.subjects // []) | add | group_by(.kind) | map({kind: .[0].kind, count: length}))
}
'
How to interpret the output (what indicates a problem):
- Any entry in “ClusterRoleBindings that grant cluster-admin” is potentially risky and must be reviewed.
- Especially suspicious:
- Bindings where
SUBJECTSincludes generic or shared identities such as:Group/system:authenticated,Group/system:serviceaccounts,Group/system:masters(or similar broad groups).- User or group names that look like teams (
devs,qa,ops,developers, etc.) instead of specific admin identities.
- ServiceAccounts in application namespaces (e.g.,
default,prod,staging,app-*) rather than a dedicated admin namespace.
- Bindings where
- The JSON “Summary counts” at the end:
- A
total_clusterrolebindings_using_cluster_admingreater than a very small number (commonly >1–3) warrants closer review. - A large
total_subjectsorby_kindentries with manyUser/Groupsubjects indicate overuse of cluster-admin.
- A
Use this report to decide, per binding, whether the subject truly needs full cluster-admin. If not, remove that ClusterRoleBinding and replace it with a narrower ClusterRole/RoleBinding as appropriate.