Ensure Cluster Admin Role Is Only Used Where Required
More Info:
Container-Optimized OS is an operating system image that is designed for quick, secure deployment on Compute Engine VMs.
Risk Level
Medium
Address
Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS AKS
- CIS Critical Security Controls v8
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- DPDPA
- Digital Operational Resilience Act (EU)
- Essential 8
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIS2 Directive
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all bindings to
cluster-admin
Run on: any machine with kubectl accesskubectl get clusterrolebindings -o wide | grep 'cluster-admin'For each binding name returned, record:
NAME,SUBJECTS(users/groups/serviceaccounts), andAGE. -
Inspect each binding’s full specification and subjects
Run on: any machine with kubectl access
For each<binding-name>from step 1:kubectl get clusterrolebinding <binding-name> -o yamlNote for each subject:
kind(User, Group, ServiceAccount)nameand, if ServiceAccount,namespace- The human/system purpose (e.g., “CI/CD pipeline”, “SRE break-glass”, “addon X controller”).
-
Determine if
cluster-adminis strictly required for each subject
For each subject identified in step 2, answer:- What actions does it actually perform (create/update/delete which resources, in which namespaces)?
- Could it work with:
- a namespaced
Role+RoleBinding, or - a reduced-scope
ClusterRole(e.g., limited to specific API groups/resources/verbs)?
If you cannot justify cluster-wide, full-privilege access for a subject, mark that binding as over-privileged.
- a namespaced
-
Design or select a least-privilege alternative where possible
For each over-privileged subject:- Prefer existing, narrower
ClusterRoles if they match needs:kubectl get clusterroles - If none fit, draft a custom
Role/ClusterRolemanifest with only required resources/verbs and a correspondingRoleBinding/ClusterRoleBindingtargeting the same subject. Apply with:kubectl apply -f <new-role-or-clusterrole>.yamlkubectl apply -f <new-binding>.yaml
Confirm the workload or user still functions correctly using this reduced role.
- Prefer existing, narrower
-
Remove unnecessary
cluster-adminbindings
Once a subject has a working least-privilege role (or is no longer needed), delete itscluster-adminbinding. Run on: any machine with kubectl accesskubectl delete clusterrolebinding <binding-name>Only keep
cluster-adminbindings that you have explicitly justified as operationally necessary (e.g., tightly controlled admin groups, break-glass users). -
Verify the current use of
cluster-admin
Run on: any machine with kubectl accesskubectl get clusterrolebindings -o wide | grep 'cluster-admin' || echo "No cluster-admin bindings found"For any remaining bindings, confirm you have documented justification and that no automated systems (e.g., IaC, operators) will recreate removed
cluster-adminbindings.
Using kubectl
Using kubectl
Run these commands from any machine with kubectl access.
- List all ClusterRoleBindings referencing
cluster-admin:
kubectl get clusterrolebindings -o wide | grep 'cluster-admin'
Problem indication:
- Any line returned here represents a principal (user/group/service account) that has full cluster-wide admin rights.
- Multiple or unexpected bindings (e.g., for generic groups like
system:authenticatedor app service accounts) are red flags.
- See the full YAML for each binding to review who is bound:
# Example for one binding; repeat for each NAME from the previous command
kubectl get clusterrolebinding <binding-name> -o yaml
Review guidance:
- Look at
.subjects:kind: User/Group/ServiceAccount- Confirm each subject truly requires full cluster-wide admin for normal operations, not just for one namespace or a single app.
- Look at
.roleRef:kindshould beClusterRolenameshould becluster-adminfor this check.
Problem indication:
- Service accounts used by workloads (e.g., app pods) appearing here.
- Wide groups like:
system:authenticatedsystem:unauthenticatedsystem:serviceaccountssystem:serviceaccounts:<namespace>
- Identities that only need limited access (e.g., CI/CD tools that operate in one namespace) but are given
cluster-admin.
- Optional: map bindings to service account usage in workloads (helps decide necessity):
# List all service accounts
kubectl get serviceaccounts --all-namespaces -o wide
# For each suspect service account, find workloads using it:
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}' \
| sort | uniq
Problem indication:
- Any application pod (non-platform, non-operator) using a service account that you saw bound to
cluster-admin.
- After manual decisions and any changes, re-verify:
kubectl get clusterrolebindings -o wide | grep 'cluster-admin' || echo "No cluster-admin bindings found"
If you still see bindings returned, confirm that each remaining binding is explicitly approved to have full cluster-wide administrative access.
Automation
#!/usr/bin/env bash
# Report all uses of the cluster-admin role for manual review
set -euo pipefail
echo "=== ClusterRoleBindings granting cluster-admin (direct) ==="
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "cluster-admin")
| .metadata.name as $crb
| .subjects[]
| "\($crb)\t\(.kind)\t\(.namespace // "-")\t\(.name)"
' 2>/dev/null \
| awk 'BEGIN {print "BINDING_NAME\tSUBJECT_KIND\tSUBJECT_NAMESPACE\tSUBJECT_NAME"}1' \
|| echo "None found or jq not installed."
echo
echo "=== RoleBindings granting cluster-admin (namespaced, indirect) ==="
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)\t\($rb)\t\(.kind)\t\(.namespace // "-")\t\(.name)"
' 2>/dev/null \
| awk 'BEGIN {print "RB_NAMESPACE\tRB_NAME\tSUBJECT_KIND\tSUBJECT_NAMESPACE\tSUBJECT_NAME"}1' \
|| echo "None found or jq not installed."
echo
echo "=== Summary counts ==="
echo "- ClusterRoleBindings with cluster-admin:"
kubectl get clusterrolebindings -o json \
| jq '[.items[] | select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")] | length' 2>/dev/null \
|| echo "jq not installed."
echo "- RoleBindings referencing cluster-admin ClusterRole (namespaced):"
kubectl get rolebindings --all-namespaces -o json \
| jq '[.items[] | select(.roleRef.kind=="ClusterRole" and .roleRef.name=="cluster-admin")] | length' 2>/dev/null \
|| echo "jq not installed."
Run this on any machine with kubectl configured for the cluster.
What indicates a problem:
- Any line in the “ClusterRoleBindings granting cluster-admin (direct)” section is a subject (user/group/serviceaccount) with full cluster-wide admin.
- Any line in the “RoleBindings granting cluster-admin (namespaced, indirect)” section is a subject with full cluster-admin rights via a namespaced binding.
Each of these entries must be manually reviewed to decide if cluster-admin is truly required; if not, remove the binding and/or replace it with a less-privileged ClusterRole.