Kyverno has overtaken OPA Gatekeeper as the most popular Kubernetes-native policy engine due to its YAML-based policy syntax and built-in generate/mutate capabilities. But its security model has distinct gaps: admission webhook failurePolicy defaults, namespace-level policy exceptions that can be self-approved, generate rules that create resources with elevated permissions, and ClusterPolicy vs Policy scope mismatches that leave namespaces unprotected. This guide covers systematic Kyverno security assessment.
# Kyverno registers ValidatingWebhookConfiguration and MutatingWebhookConfiguration
kubectl get validatingwebhookconfiguration | grep kyverno
kubectl get mutatingwebhookconfiguration | grep kyverno
# Check failurePolicy on all Kyverno webhooks
kubectl get validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg -o json | \
python3 -c "
import json,sys
wh = json.load(sys.stdin)
for hook in wh.get('webhooks',[]):
fp = hook.get('failurePolicy','Fail')
ns_sel = hook.get('namespaceSelector',{})
print(f\"Webhook: {hook['name']}\")
print(f\" failurePolicy: {fp}\")
if fp == 'Ignore':
print(' [!] failurePolicy=Ignore: Kyverno downtime bypasses ALL policies')
print(f\" namespaceSelector: {ns_sel}\")
"
# Check timeout — if Kyverno is slow, timeout can trigger failurePolicy
kubectl get validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg \
-o jsonpath='{.webhooks[*].timeoutSeconds}'
# Default: 10s — under heavy load Kyverno may timeout → failurePolicy behavior
# Verify webhook applies to target namespaces
kubectl get validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg -o json | \
python3 -c "
import json,sys
wh = json.load(sys.stdin)
for hook in wh.get('webhooks',[]):
ns_sel = hook.get('namespaceSelector',{})
me = ns_sel.get('matchExpressions',[])
for expr in me:
if expr.get('operator') == 'NotIn':
print(f\"Exempt namespaces (NotIn): {expr.get('values',[])}\")"
# Enumerate all policies and their scope
kubectl get clusterpolicies -o json | python3 -c "
import json,sys
pols = json.load(sys.stdin)['items']
for p in pols:
meta = p['metadata']
spec = p.get('spec',{})
rules = spec.get('rules',[])
validation_failure = spec.get('validationFailureAction','audit')
print(f\"ClusterPolicy: {meta['name']}\")
print(f\" validationFailureAction: {validation_failure}\")
if validation_failure == 'audit':
print(' [!] audit mode: violations logged but NOT blocked')
for rule in rules:
print(f\" Rule: {rule['name']} type: {'validate' if 'validate' in rule else 'mutate' if 'mutate' in rule else 'generate'}\")
"
# Check namespace-scoped policies (apply only to their namespace)
kubectl get policies --all-namespaces -o json | python3 -c "
import json,sys
pols = json.load(sys.stdin)['items']
for p in pols:
meta = p['metadata']
spec = p.get('spec',{})
print(f\"Policy: {meta['namespace']}/{meta['name']} action: {spec.get('validationFailureAction','audit')}\")
"
# Find namespaces with no policies applying to them
# ClusterPolicies apply everywhere unless selector excludes
# Check if any namespaces are completely unprotected
kubectl get namespaces -o jsonpath='{.items[*].metadata.name}' | \
tr ' ' '\n' | while read ns; do
exempt=$(kubectl get validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg \
-o json 2>/dev/null | python3 -c "
import json,sys
wh = json.load(sys.stdin)
for hook in wh.get('webhooks',[]):
ns_sel = hook.get('namespaceSelector',{})
for expr in ns_sel.get('matchExpressions',[]):
if expr.get('operator') == 'NotIn' and '$ns' in expr.get('values',[]):
print('exempt')
" 2>/dev/null)
[ "$exempt" = "exempt" ] && echo "Kyverno exempt: $ns"
done
# Policies in audit mode log violations but don't block them
# An attacker can deploy policy-violating resources when validationFailureAction=audit
# Test: deploy a privileged pod despite a policy claiming to block it
kubectl apply -f - <
# Kyverno policies use match/exclude blocks and preconditions
# Test if match conditions can be circumvented
# Example: policy only applies to pods with a specific label
# match:
# resources:
# kinds: [Pod]
# selector:
# matchLabels:
# app: monitored
# Bypass: create pod without the 'app: monitored' label
kubectl apply -f - <
# Kyverno PolicyException allows specific resources to bypass policies
# If users can create PolicyExceptions, they can self-exempt their workloads
# Check who can create PolicyExceptions
kubectl auth can-i create policyexceptions \
--as=system:serviceaccount:default:default -n default
kubectl auth can-i create policyexceptions \
--as=system:serviceaccount:app-team:app-service-account -n app-team
# If allowed: create a PolicyException to bypass a security policy
kubectl apply -f - <
# Kyverno generate rules create resources automatically when a trigger occurs
# Security risk: generated resources inherit Kyverno's permissions, not the requester's
# Check generate rules for resource creation scope
kubectl get clusterpolicies -o json | python3 -c "
import json,sys
pols = json.load(sys.stdin)['items']
for p in pols:
for rule in p.get('spec',{}).get('rules',[]):
if 'generate' in rule:
gen = rule['generate']
print(f\"Generate rule: {p['metadata']['name']}/{rule['name']}\")
print(f\" Generates: {gen.get('kind','?')} in {gen.get('namespace','same')}\")
print(f\" Data: {str(gen.get('data',{}))[:100]}\")
# Risk: generates ClusterRoleBindings, ServiceAccounts, Secrets
# User triggers generate by creating a labeled namespace
"
# Test: create a namespace to trigger generate rules
# Some policies generate NetworkPolicies, ResourceQuotas, or RoleBindings for new namespaces
kubectl create namespace generate-test
# Check what was generated
kubectl get rolebindings,networkpolicies,resourcequotas -n generate-test
# Exploit: if a generate rule creates RoleBindings with cluster-level scope:
# Trigger the rule to create a binding that grants your SA elevated permissions
# Kyverno mutate rules modify resources at admission time
# Unintended mutations can create security gaps
# Check mutate rules — what gets modified
kubectl get clusterpolicies -o json | python3 -c "
import json,sys
pols = json.load(sys.stdin)['items']
for p in pols:
for rule in p.get('spec',{}).get('rules',[]):
if 'mutate' in rule:
mut = rule['mutate']
print(f\"Mutate rule: {p['metadata']['name']}/{rule['name']}\")
patchstr = str(mut.get('patchStrategicMerge',{}))[:200]
patchjsonpath = str(mut.get('patchesJson6902',''))[:200]
print(f\" Patch: {patchstr or patchjsonpath}\")
"
# Test mutate rule interaction: submit a pod that relies on mutation
# If a mutate rule adds securityContext.runAsNonRoot=true to all pods,
# test if a pod with explicit runAsRoot=true overrides the mutation
kubectl apply -f - <
# Kyverno background scans check existing resources against policies
# But background scans don't block — they only report violations
# Check background scan results
kubectl get policyreport --all-namespaces -o json | python3 -c "
import json,sys
reports = json.load(sys.stdin)['items']
for r in reports:
meta = r['metadata']
results = r.get('results',[])
fails = [res for res in results if res.get('result') == 'fail']
if fails:
print(f\"PolicyReport: {meta['namespace']}/{meta['name']} - {len(fails)} failures\")
for f in fails[:3]:
print(f\" Policy: {f.get('policy','?')} rule: {f.get('rule','?')}\")
print(f\" Resource: {f.get('resource',{}).get('name','?')}\")
print(f\" Message: {f.get('message','?')[:100]}\")
"
# Check cluster-wide policy report
kubectl get clusterpolicyreport -o json | python3 -c "
import json,sys
reports = json.load(sys.stdin)['items']
total_fails = sum(len([r for r in rep.get('results',[]) if r.get('result')=='fail']) for rep in reports)
print(f'Total cluster-wide policy violations: {total_fails}')
"
# Critical: violations in policy reports = existing workloads NOT compliant with policies
# These are not blocked (admission already passed) — they represent drift
failurePolicy: Fail on all Kyverno validating webhooks — downtime should block admissionsvalidationFailureAction: enforce not audit| Security Test | Method | Risk |
|---|---|---|
| failurePolicy: Ignore on webhook | kubectl get validatingwebhookconfiguration | Critical |
| All policies in audit mode | kubectl get clusterpolicies — check validationFailureAction | Critical |
| SA can create PolicyExceptions | kubectl auth can-i create policyexceptions | High |
| Label selector bypass (unlabeled pods) | Deploy pod without required labels | High |
| UPDATE operation not covered | Create compliant pod, patch to violate policy | High |
| PolicyReport violations (existing drift) | kubectl get policyreport — count fail results | High |
| Generate rule creates elevated bindings | Review generate rules for RBAC resource creation | Medium |
Ironimo tests Kyverno deployments for webhook failurePolicy gaps, audit-mode policy coverage, PolicyException scope abuse, generate rule privilege escalation paths, and policy drift in existing workloads via PolicyReport analysis.
Start free scan