Skip to main content

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

Manual Steps
  1. List all bindings to cluster-admin
    Run on: any machine with kubectl access

    kubectl get clusterrolebindings -o wide | grep 'cluster-admin'

    For each binding name returned, record: NAME, SUBJECTS (users/groups/serviceaccounts), and AGE.

  2. 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 yaml

    Note for each subject:

    • kind (User, Group, ServiceAccount)
    • name and, if ServiceAccount, namespace
    • The human/system purpose (e.g., “CI/CD pipeline”, “SRE break-glass”, “addon X controller”).
  3. Determine if cluster-admin is 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.
  4. 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/ClusterRole manifest with only required resources/verbs and a corresponding RoleBinding/ClusterRoleBinding targeting the same subject. Apply with:
      kubectl apply -f <new-role-or-clusterrole>.yaml
      kubectl apply -f <new-binding>.yaml

    Confirm the workload or user still functions correctly using this reduced role.

  5. Remove unnecessary cluster-admin bindings
    Once a subject has a working least-privilege role (or is no longer needed), delete its cluster-admin binding. Run on: any machine with kubectl access

    kubectl delete clusterrolebinding <binding-name>

    Only keep cluster-admin bindings that you have explicitly justified as operationally necessary (e.g., tightly controlled admin groups, break-glass users).

  6. Verify the current use of cluster-admin
    Run on: any machine with kubectl access

    kubectl 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-admin bindings.

Using kubectl

Using kubectl

Run these commands from any machine with kubectl access.

  1. 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:authenticated or app service accounts) are red flags.
  1. 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:
    • kind should be ClusterRole
    • name should be cluster-admin for this check.

Problem indication:

  • Service accounts used by workloads (e.g., app pods) appearing here.
  • Wide groups like:
    • system:authenticated
    • system:unauthenticated
    • system:serviceaccounts
    • system:serviceaccounts:<namespace>
  • Identities that only need limited access (e.g., CI/CD tools that operate in one namespace) but are given cluster-admin.
  1. 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.
  1. 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.

Additional Reading: