← Back

2026

Kyverno: Policy Enforcement for Kubernetes

A cluster will accept almost anything you submit to it. Kyverno sits at the admission layer and changes that. Policies written as plain Kubernetes resources validate, mutate and generate objects before they are stored, keeping configuration drift from ever reaching etcd.

A Kubernetes cluster is easy to misconfigure. A developer pushes a deployment with no resource limits and a noisy pod starts consuming memory that neighbouring workloads needed. Someone pulls from a public registry without pinning a digest and a changed image tag silently breaks production. A new namespace gets created without the NetworkPolicy that should have been there from the start.

These are not exotic failure modes. They happen in most clusters eventually and they share a common cause: the cluster accepted resources it should have questioned. Kubernetes has no built-in mechanism to enforce organisational standards at admission time beyond its own schema validation. That gap is exactly what Kyverno fills.

Kyverno is a policy engine built specifically for Kubernetes. It runs as an admission controller inside the cluster and intercepts every resource before it is stored. Policies are written as Kubernetes resources themselves, which means there is no separate policy language to learn. You define what you want using the same YAML structure you already use for everything else and Kyverno takes care of evaluating it on every applicable request.

How Kyverno intercepts requests

Kyverno works through Kubernetes admission webhooks. When you install it, two webhook configurations are registered with the API server: one for mutation and one for validation. From that point forward, any request to create or update a resource that matches a policy gets routed through Kyverno before the API server decides whether to accept it.

The order matters. Mutation runs before validation. A mutate rule can add a missing field, then a validate rule can require that field to be present. Together they let you combine sensible defaults with hard requirements: inject a resource limit if none is set, then reject any resource where the limit is still absent after mutation has had its chance to add one.

01

Request arrives at the API server

A user or controller submits a create, update or delete request. The API server authenticates it and begins processing

02

Mutating webhooks run

Kyverno's mutating webhook fires first. Any mutate rules that match the resource are applied. The modified object continues through the pipeline

03

Object is validated by the API server

Kubernetes validates the (now potentially mutated) object against its own schema

04

Validating webhooks run

Kyverno's validating webhook fires. All matching validate rules are evaluated. If any rule fails, the request is rejected with the configured message

05

Object is stored in etcd

Only resources that passed every check reach persistent storage. Generate rules then fire in the background to create any dependent resources

Rule types

A Kyverno policy is made up of one or more rules. Each rule has a type that determines what it does when it matches a resource. The four types cover the main things you want to do with policy: block bad configurations, fix missing ones, create dependent resources automatically and verify image provenance.

validate

Checks whether a resource meets a condition. If the check fails, Kubernetes rejects the request and returns an error to whoever submitted it. Validation rules are the most common type. They enforce things like required labels, forbidden image registries and minimum resource limits.

mutate

Modifies a resource before it is stored. If a pod is submitted without a memory limit, a mutate rule can add one automatically. If a deployment is missing a standard label, mutation can inject it. The user submitting the resource never sees the change.

generate

Creates additional resources in response to an event. When a new namespace appears, a generate rule can automatically create a NetworkPolicy or a default ResourceQuota inside it. This is useful for enforcing baseline configuration that every namespace should have.

verifyImages

Checks container image signatures before a pod is allowed to run. Works with tools like Cosign. If an image was not signed by a trusted key, the pod is rejected. This closes the gap between building an image in CI and knowing it has not been tampered with before it runs in production.

ClusterPolicy and Policy

Kyverno ships two custom resource kinds for writing policies. The difference between them is scope. One applies across the entire cluster, the other is namespaced. Choosing between them is mostly a question of who owns the policy and how broadly it should apply.

ClusterPolicy

Cluster-wide

Applies rules across all namespaces. Use this for baseline security controls that should be consistent everywhere, like blocking privileged containers or requiring image digests.

Policy

Namespaced

Applies rules only within the namespace where it is created. Useful when different teams have different requirements or when you want to give namespace owners the ability to define their own constraints.

What policies look like

Policies are plain Kubernetes manifests. You apply them with kubectl apply just like any other resource. The match block narrows which resources the rule applies to. The rule body describes what to check, change or create. There is no separate CLI tool required to deploy them.

Validate — require resource limits on all containers

apiVersion: kyverno.io/v1

kind: ClusterPolicy

metadata:

name: require-resource-limits

spec:

validationFailureAction: Enforce

rules:

- name: check-limits

match:

resources:

kinds:

- Pod

validate:

message: "All containers must define resource limits"

pattern:

spec:

containers:

- resources:

limits:

memory: "?*"

cpu: "?*"

Mutate — add a default label to pods that are missing it

apiVersion: kyverno.io/v1

kind: ClusterPolicy

metadata:

name: add-default-labels

spec:

rules:

- name: add-team-label

match:

resources:

kinds:

- Pod

mutate:

patchStrategicMerge:

metadata:

labels:

+(managed-by): kyverno

Generate — create a default NetworkPolicy in every new namespace

apiVersion: kyverno.io/v1

kind: ClusterPolicy

metadata:

name: default-network-policy

spec:

rules:

- name: generate-network-policy

match:

resources:

kinds:

- Namespace

generate:

kind: NetworkPolicy

name: default-deny-ingress

namespace: {{request.object.metadata.name}}

data:

spec:

podSelector: {}

policyTypes:

- Ingress

The +(field) syntax in the mutate example is a conditional patch anchor. It adds the field only when it does not already exist. Without it, the mutation would overwrite values that were deliberately set by the submitter, which is usually not what you want from a default-injection rule.

Audit and Enforce modes

Every validate policy has a validationFailureAction field that controls what happens when a resource fails the check. Setting it to Enforce rejects the request. Setting it to Audit allows the request through but records a policy report showing that the resource violated the rule.

This distinction is genuinely useful when rolling out new policies. Switching directly to Enforce on a cluster with existing workloads risks blocking legitimate deployments the moment a CI pipeline runs. Starting in Audit mode lets you see which resources would have been rejected, fix the violations at your own pace and switch to Enforce only once you are confident that nothing legitimate will be blocked. The policy reports Kyverno generates are standard Kubernetes resources you can query with kubectl.

Checking policy reports

kubectl get policyreport -A

Lists all namespaced policy reports. Each row shows how many results pass or fail.

kubectl get clusterpolicyreport

Cluster-scoped resources like namespaces appear in ClusterPolicyReports instead.

kubectl describe policyreport -n my-namespace

Shows the full detail: which policy failed, which resource triggered it and the message from the rule.

Testing policies before you apply them

Kyverno ships a CLI that evaluates policies against resource manifests locally, without a running cluster. This is valuable in a CI pipeline where you want to know whether new policies would block existing manifests before merging either change to the main branch.

Running the Kyverno CLI

kyverno apply ./policies/ --resource ./manifests/

Apply all policies in the policies directory against all resources in manifests. Results show pass, fail and skip for each rule and resource pair.

kyverno test .

Runs structured tests defined alongside your policies. Each test specifies an input resource and the expected outcome, making it easy to verify that validate rules reject what they should reject.

The test command is especially worth building into a pull request workflow. Policies checked into Git, tested in CI with known-good manifests, then applied to the cluster through a GitOps pipeline is a setup that prevents a whole category of incidents where a new policy accidentally breaks an unrelated deployment.

Where it fits

Kyverno sits at the admission layer of a Kubernetes cluster. It is not a runtime security tool. It does not watch what pods are doing after they start. Its job is to prevent misconfigured or non-compliant resources from ever reaching etcd in the first place. The resources that do get stored have already passed your policy checks.

The natural comparison is with OPA Gatekeeper, which fills the same role. Gatekeeper uses Rego as its policy language, which is more expressive but also has a steeper learning curve. Kyverno uses YAML patterns that map directly to the Kubernetes resource model. Teams already comfortable writing Kubernetes manifests tend to find Kyverno policies more approachable. The right choice depends on how complex your policy logic needs to be and how much of your team already knows Rego.

In practice, Kyverno tends to show up alongside tools that handle the parts of security it does not: Falco for runtime threat detection, HashiCorp Vault for secrets, network policies for traffic isolation. Kyverno handles the configuration guardrails. Getting a baseline set of validate policies in place takes a day. The harder work is deciding exactly where to draw the line: strict enough to prevent real problems, permissive enough that teams can still ship without raising a ticket every time they need an exception.