Skip to content

Detect a cluster-admin grant

The single most common privilege-escalation move on a Kubernetes cluster is a ClusterRoleBinding whose roleRef is cluster-admin and whose subjects include either a wildcard or a freshly-created ServiceAccount. This recipe sets up an end-to-end detection in about 5 minutes.

  • ugallu umbrella chart installed (audit-detection enabled, default webhook source).
  • The apiserver’s audit policy includes Metadata level for clusterrolebindings.rbac.authorization.k8s.io. On self-managed control planes this is the default; managed control planes usually need a flag flip on the API server.

Verify audit-detection is up:

Terminal window
kubectl -n ugallu-system get deploy ugallu-audit-detection
kubectl -n ugallu-system logs deploy/ugallu-audit-detection \
| grep -i 'webhook listener'
rules/cluster-admin-granted.yaml
apiVersion: security.ugallu.io/v1alpha1
kind: SigmaRule
metadata:
name: cluster-admin-granted
spec:
description: |
Detect creation or update of a ClusterRoleBinding whose
roleRef.name is cluster-admin.
match:
verb: [create, update, patch]
objectRef:
resource: clusterrolebindings
requestObjectGlob:
- jsonPath: "$.roleRef.name"
glob: ["cluster-admin"]
emit:
type: ClusterAdminGranted
severity: critical
class: Detection
rateLimit:
burst: 5
sustainedPerSec: 1
Terminal window
kubectl apply -f rules/cluster-admin-granted.yaml
kubectl get sigmarules cluster-admin-granted -o yaml

The rule’s Status.Compiled should flip to True within a reconcile tick (under 1 second). If Status.ParseError is set, fix the rule and re-apply - the operator hot-swaps the in-memory rule set on every CR write.

Terminal window
kubectl create clusterrolebinding test-attack \
--clusterrole=cluster-admin \
--serviceaccount=default:default

The SecurityEvent CR appears within a second or two:

Terminal window
kubectl get securityevents -A \
--sort-by=.metadata.creationTimestamp
# NAME AGE TYPE SEVERITY
# ugse-cluster-admin-...-xfb2 2s ClusterAdminGranted critical

Inspect the full evidence:

Terminal window
kubectl get securityevent <name> -o yaml | yq .spec

You should see:

  • type: ClusterAdminGranted
  • severity: critical
  • class: Detection
  • subject.kind: ClusterRoleBinding, subject.name: test-attack
  • evidence.requestObject.subjects carrying the SA you bound

If you want a forensics capture on this event, the default ForensicsConfig already includes Detection + severity high|critical in its trigger predicate. Add ClusterAdminGranted to whitelistedTypes:

apiVersion: security.ugallu.io/v1alpha1
kind: ForensicsConfig
metadata: { name: default }
spec:
trigger:
classes: [Detection]
minSeverities: [critical, high]
whitelistedTypes:
- ClusterAdminGranted
# ... your other types
requireAttested: true

Now every ClusterAdminGranted SE will be attested by attestor into an AttestationBundle, then forensics will start a capture pipeline against the granting subject if it is a Pod.

Terminal window
kubectl delete clusterrolebinding test-attack
kubectl delete sigmarules cluster-admin-granted

The SE CR is retained per TTLConfig.spec.defaults.securityEvent (default 168h for critical).