Kyverno Security Testing: Policy Bypass, Admission Webhook Misconfiguration, and Exception Abuse

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.

Table of Contents

  1. Webhook Configuration Security
  2. ClusterPolicy and Policy Coverage Audit
  3. Policy Bypass Techniques
  4. PolicyException Exploitation
  5. Generate Rule Security Testing
  6. Mutate Rule Abuse
  7. Background Scan Gap Analysis
  8. Kyverno Security Hardening

Webhook Configuration Security

# 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',[])}\")"

ClusterPolicy and Policy Coverage Audit

# 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

Policy Bypass Techniques

Audit Mode Exploitation

# 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 - <

Precondition and Match Bypass

# 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 - <

PolicyException Exploitation

# 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 - <

Generate Rule Security Testing

# 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

Mutate Rule Abuse

# 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 - <

Background Scan Gap Analysis

# 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

Kyverno Security Hardening

Kyverno Security Hardening Checklist:
  • Set failurePolicy: Fail on all Kyverno validating webhooks — downtime should block admissions
  • Set all security-critical policies to validationFailureAction: enforce not audit
  • Restrict PolicyException creation to security team only via RBAC
  • Require all PolicyExceptions to specify resource names — no wildcard exemptions
  • Review all generate rules for unintended ClusterRoleBinding or elevated permission creation
  • Apply policies to all operations (CREATE, UPDATE, DELETE) — not just CREATE
  • Review PolicyReports weekly — violations represent production drift from policy
  • Exempt kyverno system namespace only — not application namespaces
  • Test policy coverage after every new namespace creation
Security TestMethodRisk
failurePolicy: Ignore on webhookkubectl get validatingwebhookconfigurationCritical
All policies in audit modekubectl get clusterpolicies — check validationFailureActionCritical
SA can create PolicyExceptionskubectl auth can-i create policyexceptionsHigh
Label selector bypass (unlabeled pods)Deploy pod without required labelsHigh
UPDATE operation not coveredCreate compliant pod, patch to violate policyHigh
PolicyReport violations (existing drift)kubectl get policyreport — count fail resultsHigh
Generate rule creates elevated bindingsReview generate rules for RBAC resource creationMedium

Automate Kyverno Policy Security Testing

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