Skip to content

Kyverno Policy Enforcement

Enforce best practices, validate resources, and mutate configurations automatically using Kyverno admission controller.

Time: ~15 minutes Difficulty: Intermediate

  • Kyverno ClusterPolicy resources: validating, mutating, and generating policies
  • Validating admission: block resources that violate policies
  • Mutating admission: automatically fix or add configuration
  • Policy enforcement vs audit mode
  • Testing policies with compliant and non-compliant resources

Kyverno must be installed before running this demo:

Terminal window
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace

Wait for Kyverno to be ready:

Terminal window
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=kyverno -n kyverno --timeout=120s

Navigate to the demo directory:

Terminal window
cd demos/kyverno

Create the namespace and apply the policies:

Terminal window
kubectl apply -f manifests/namespace.yaml
kubectl apply -f manifests/policy-require-labels.yaml
kubectl apply -f manifests/policy-add-default-resources.yaml
kubectl apply -f manifests/policy-disallow-latest.yaml

Wait for policies to be ready:

Terminal window
kubectl get clusterpolicies

All policies should show READY: true.

Terminal window
kubectl apply -f manifests/test-good-pod.yaml

This pod should be created successfully because it:

  • Has a team label
  • Uses a specific image tag (nginx:1.27, not :latest)
  • Has resource requests and limits

Verify the pod is running:

Terminal window
kubectl get pod good-pod -n kyverno-demo
Terminal window
kubectl apply -f manifests/test-bad-pod-no-label.yaml

This pod is blocked with an error message:

Error from server: error when creating "manifests/test-bad-pod-no-label.yaml":
admission webhook "validate.kyverno.svc-fail" denied the request:
policy Pod/kyverno-demo/bad-pod-no-label for resource violation:
require-team-label:
check-team-label: Pod is missing required label 'team'. Add metadata.labels.team to your pod spec.
Terminal window
kubectl apply -f manifests/test-bad-pod-latest.yaml

This pod is blocked because it uses the :latest tag:

Error from server: error when creating "manifests/test-bad-pod-latest.yaml":
admission webhook "validate.kyverno.svc-fail" denied the request:
policy Pod/kyverno-demo/bad-pod-latest-tag for resource violation:
disallow-latest-tag:
require-image-tag: Container images must have a specific tag. Do not use :latest or omit the tag.

Create a pod without resource limits to see the mutating policy add them:

Terminal window
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: mutated-pod
namespace: kyverno-demo
labels:
team: platform
spec:
containers:
- name: app
image: nginx:1.27
EOF

Check that Kyverno added default resource limits:

Terminal window
kubectl get pod mutated-pod -n kyverno-demo -o jsonpath='{.spec.containers[0].resources}' | jq

Output shows the mutation added default values:

{
"limits": {
"cpu": "200m",
"memory": "128Mi"
},
"requests": {
"cpu": "100m",
"memory": "64Mi"
}
}
manifests/
namespace.yaml # kyverno-demo namespace
policy-require-labels.yaml # Validates all Pods have a 'team' label
policy-add-default-resources.yaml # Mutates Pods to add default resource limits
policy-disallow-latest.yaml # Blocks containers using :latest tag
test-good-pod.yaml # Compliant pod (passes all policies)
test-bad-pod-no-label.yaml # Non-compliant (missing team label, blocked)
test-bad-pod-latest.yaml # Non-compliant (uses :latest tag, blocked)

Kyverno policies work at admission time. When you try to create a resource, Kyverno webhooks intercept the request and apply policies before the resource is stored in etcd.

Three policy types:

TypeWhat It Does
ValidateChecks if a resource meets criteria. Blocks it if not (Enforce mode) or logs a warning (Audit mode).
MutateModifies the resource automatically (adds labels, sets defaults, injects sidecars).
GenerateCreates additional resources (like NetworkPolicies or ConfigMaps) when a resource is created.

validationFailureAction:

  • Enforce: Blocks non-compliant resources (used in policy-require-labels.yaml and policy-disallow-latest.yaml)
  • Audit: Logs violations but allows the resource to be created

Policy patterns:

  • "?*" means “any value, but must be present”
  • "!*:latest" means “any value except :latest”
  • "*:*" means “must contain a colon” (enforces explicit tag)
  • +(field) in mutate patches means “add this field only if it does not exist”
  1. List all Kyverno policy violations across the cluster:

    Terminal window
    kubectl get policyreport -A
  2. Change a policy to audit mode instead of enforce:

    Terminal window
    kubectl edit clusterpolicy require-team-label
    # Change validationFailureAction to Audit

    Now the bad-pod-no-label will be created with a warning instead of being blocked.

  3. Write a custom policy to require all Services to have a contact annotation:

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
    name: require-contact-annotation
    spec:
    validationFailureAction: Enforce
    rules:
    - name: check-contact
    match:
    any:
    - resources:
    kinds:
    - Service
    namespaces:
    - kyverno-demo
    validate:
    message: "All services must have a 'contact' annotation"
    pattern:
    metadata:
    annotations:
    contact: "?*"
  4. Create a generate policy that automatically adds a default NetworkPolicy when a namespace is created:

    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
    name: add-networkpolicy
    spec:
    rules:
    - name: default-deny-ingress
    match:
    any:
    - resources:
    kinds:
    - Namespace
    generate:
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    name: default-deny-ingress
    namespace: "{{request.object.metadata.name}}"
    synchronize: true
    data:
    spec:
    podSelector: {}
    policyTypes:
    - Ingress
  5. Use policy exceptions to exclude specific resources from a policy:

    apiVersion: kyverno.io/v2
    kind: PolicyException
    metadata:
    name: allow-latest-for-dev
    namespace: kyverno-demo
    spec:
    exceptions:
    - policyName: disallow-latest-tag
    ruleNames:
    - require-image-tag
    match:
    any:
    - resources:
    kinds:
    - Pod
    namespaces:
    - kyverno-demo
    names:
    - dev-*

Delete the namespace (removes all test pods) and the ClusterPolicies:

Terminal window
kubectl delete namespace kyverno-demo
kubectl delete clusterpolicy require-team-label add-default-resources disallow-latest-tag

To remove Kyverno itself:

Terminal window
helm uninstall kyverno -n kyverno
kubectl delete namespace kyverno

See docs/deep-dive.md for a detailed explanation of Kyverno architecture, policy reports, background scanning, webhook configuration, and advanced patterns like variable substitution and external data sources.

Move on to Velero Backup and Restore to learn how to back up and restore cluster resources and persistent volumes.