Initializing Enclave...

How to Fix Kubernetes RBAC cluster-admin Privilege Escalation and Wildcard Verb Misconfigurations

Threat/Impact Level: CRITICAL | Exploitability/Downtime Risk: HIGH | Time to Fix: 15 mins

TL;DR

  • What broke: A ClusterRoleBinding is binding cluster-admin to a service account that should not have it, and one or more Role/ClusterRole definitions contain verbs: ['*'], granting unrestricted API access cluster-wide.
  • How to fix it: Delete the offending ClusterRoleBinding, create a scoped Role in the target namespace with explicit verb allowlists, and bind it via a RoleBinding — never a ClusterRoleBinding — to the service account.
  • Shortcut: Use our Client-Side Sandbox above to auto-refactor this — paste your RBAC manifest and get a least-privilege replacement generated locally without your configs leaving the browser.

The Incident (What does the error mean?)

Your RBAC audit surfaced two distinct critical findings:

Finding 1 — Unauthorized cluster-admin binding:

SECURITY ALERT: ClusterRoleBinding 'pipeline-admin-binding' grants
ClusterRole 'cluster-admin' to ServiceAccount 'ci-runner'
in namespace 'build'. Severity: CRITICAL.

Finding 2 — Wildcard verbs in Role definition:

SECURITY ALERT: ClusterRole 'app-operator-role' contains
wildcard verbs ['*'] on apiGroups ['*'] resources ['*'].
This is functionally equivalent to cluster-admin. Severity: CRITICAL.

cluster-admin is Kubernetes's root. A service account holding it can read secrets across all namespaces, create/delete nodes, modify RBAC itself, and exec into any pod. A wildcard-verb ClusterRole is identical in blast radius — the label cluster-admin is irrelevant when verbs: ['*'] on resources: ['*'] is present.


The Attack Vector / Blast Radius

This is a privilege escalation path, not a theoretical risk. The attack chain is straightforward:

  1. Initial foothold: Attacker exploits any vulnerability in the ci-runner pod — a supply chain compromise in a build dependency, an SSRF in a build step, or a leaked KUBECONFIG.
  2. Token harvest: Every pod in Kubernetes automounts its service account token at /var/run/secrets/kubernetes.io/serviceaccount/token by default. The attacker reads it in seconds.
  3. Cluster-wide API access: With the cluster-admin token, the attacker runs kubectl get secrets -A — every secret, every namespace, instantly. Database passwords, TLS private keys, cloud provider credentials stored as Secrets are all exposed.
  4. Persistence: The attacker creates a new ClusterRoleBinding binding cluster-admin to a backdoor service account, then deletes evidence. You no longer own your cluster.
  5. Lateral movement to cloud: If your nodes run with an IAM instance profile (EKS node role, GKE Workload Identity, AKS managed identity), the attacker pivots from Kubernetes to your cloud account via the metadata API.

Wildcard verbs compound this: Even if you revoke the cluster-admin binding, a ClusterRole with verbs: ['*'] on resources: ['*'] is an identical attack surface with a different name.


How to Fix It (The Solution)

Basic Fix — Remove the cluster-admin ClusterRoleBinding immediately

# Identify all ClusterRoleBindings granting cluster-admin to non-system accounts
kubectl get clusterrolebindings -o json | \
  jq '.items[] | select(.roleRef.name=="cluster-admin") | {name: .metadata.name, subjects: .subjects}'

# Delete the offending binding
kubectl delete clusterrolebinding pipeline-admin-binding

This is your immediate stop-the-bleeding action. Do it before writing replacement manifests.


Enterprise Best Practice — Least-Privilege Scoped RBAC

Replace the ClusterRoleBinding + cluster-admin pattern with a namespace-scoped Role + RoleBinding that grants only what the CI runner actually needs.

The bad configuration:

- apiVersion: rbac.authorization.k8s.io/v1
- kind: ClusterRoleBinding
- metadata:
-   name: pipeline-admin-binding
- roleRef:
-   apiGroup: rbac.authorization.k8s.io
-   kind: ClusterRole
-   name: cluster-admin          # ROOT ACCESS — never for CI runners
- subjects:
- - kind: ServiceAccount
-   name: ci-runner
-   namespace: build

The wildcard ClusterRole:

- apiVersion: rbac.authorization.k8s.io/v1
- kind: ClusterRole
- metadata:
-   name: app-operator-role
- rules:
- - apiGroups: ["*"]            # All API groups — unacceptable
-   resources: ["*"]            # All resources — unacceptable  
-   verbs: ["*"]                # All verbs — functionally cluster-admin

The hardened replacement:

+ # Step 1: Scoped Role — only in the 'build' namespace, only what CI needs
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: Role
+ metadata:
+   name: ci-runner-role
+   namespace: build              # Namespace-scoped, not cluster-wide
+ rules:
+ - apiGroups: [""]              # Core API group only
+   resources: ["pods", "pods/log"]
+   verbs: ["get", "list", "watch", "create", "delete"]
+ - apiGroups: ["apps"]
+   resources: ["deployments"]
+   verbs: ["get", "list", "patch", "update"]  # No 'delete', no '*'
+ ---
+ # Step 2: RoleBinding — NOT ClusterRoleBinding
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: RoleBinding
+ metadata:
+   name: ci-runner-binding
+   namespace: build
+ roleRef:
+   apiGroup: rbac.authorization.k8s.io
+   kind: Role
+   name: ci-runner-role
+ subjects:
+ - kind: ServiceAccount
+   name: ci-runner
+   namespace: build
+ ---
+ # Step 3: Disable automount on the ServiceAccount unless explicitly needed
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+   name: ci-runner
+   namespace: build
+ automountServiceAccountToken: false  # Mount manually in pod spec only when required

Key principles enforced here:

  • Role + RoleBinding instead of ClusterRole + ClusterRoleBinding — blast radius is now one namespace
  • Explicit verb allowlist — get, list, watch, create, delete, patch, update — never *
  • automountServiceAccountToken: false at the ServiceAccount level; opt-in per pod
  • Zero access to secrets, nodes, clusterrolebindings, or namespaces resources

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

Manual audits are insufficient. Gate this in your pipeline.

1. OPA/Gatekeeper Policy — Block cluster-admin bindings at admission

# ConstraintTemplate: deny ClusterRoleBindings to cluster-admin for non-system subjects
package k8sclusteradminbinding

violation[{"msg": msg}] {
  input.review.kind.kind == "ClusterRoleBinding"
  input.review.object.roleRef.name == "cluster-admin"
  subject := input.review.object.subjects[_]
  not startswith(subject.name, "system:")
  msg := sprintf("ClusterRoleBinding grants cluster-admin to non-system subject: %v", [subject.name])
}

2. Checkov — Scan manifests in PR pipelines

# Install and run against your manifests directory
pip install checkov
checkov -d ./k8s-manifests --framework kubernetes \
  --check CKV_K8S_49  # Ensure no wildcard verbs
  --check CKV_K8S_155 # No cluster-admin bindings to service accounts

3. Kyverno Policy — Deny wildcard verbs

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: deny-wildcard-verbs
spec:
  validationFailureAction: Enforce
  rules:
  - name: deny-wildcard-verbs-in-roles
    match:
      resources:
        kinds: ["Role", "ClusterRole"]
    validate:
      message: "Wildcard verbs are not permitted in Role definitions."
      deny:
        conditions:
          any:
          - key: "{{ request.object.rules[].verbs[] | contains(@, '*') }}"
            operator: Equals
            value: true

4. Audit existing cluster continuously

# Run rbac-tool or kubectl-who-can on a schedule
kubectl-who-can '*' '*'  # Shows all subjects with wildcard permissions

# Or use rbac-lookup
kubectl rbac-lookup ci-runner --kind ServiceAccount --output wide

Integrate these checks into your GitOps pipeline (ArgoCD pre-sync hooks, Flux validation, or GitHub Actions) so misconfigured RBAC manifests are rejected before they ever reach the cluster.

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →