Kyverno¶
Kyverno is a policy engine designed specifically for Kubernetes. Unlike OPA/Gatekeeper which uses the Rego language, Kyverno policies are written as Kubernetes YAML - using the same resource model you already know - and managed through the same GitOps workflows as your other manifests.
The key distinction from OPA: Kyverno is not a general-purpose policy language. That constraint is also its strength - policies are readable by anyone who knows Kubernetes, don't require learning Rego, and integrate naturally with tools that speak Kubernetes YAML (Helm, Argo CD, kustomize).
Architecture¶
flowchart TD
APIServer[Kubernetes API Server] --> |ValidatingWebhook| KyvernoAdmission[Kyverno\nadmission controller]
APIServer --> |MutatingWebhook| KyvernoAdmission
KyvernoAdmission --> |evaluate| Engine[Policy engine]
Engine --> |ClusterPolicy / Policy| Rules[Validate / Mutate / Generate / VerifyImages]
subgraph Background controllers
Scanner[background-controller\naudit scan]
Generator[generate-controller\nmanage generated resources]
Updater[update-request-controller]
end
Engine --> Scanner
Engine --> Generator
Reports[PolicyReport / ClusterPolicyReport] --> Scanner
Kyverno runs four sub-controllers. The admission controller handles real-time enforcement. The background-controller scans existing resources against policies and writes PolicyReport objects. The generate-controller manages resources created by generate rules.
Policy types¶
Kyverno has four rule types in a single policy:
| Rule type | What it does |
|---|---|
validate |
Deny admission if condition is not met |
mutate |
Modify the resource before admission |
generate |
Create, clone, or sync other resources when a trigger resource is created |
verifyImages |
Verify container image signatures (Cosign, Notary) |
Validation¶
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
annotations:
policies.kyverno.io/title: Require Resource Limits
policies.kyverno.io/severity: medium
policies.kyverno.io/description: >
All containers must declare CPU and memory limits to prevent noisy-neighbor problems.
spec:
validationFailureAction: Enforce # or Audit
background: true # run against existing resources in audit mode
rules:
- name: check-container-limits
match:
any:
- resources:
kinds:
- Pod
exclude:
any:
- resources:
namespaces:
- kube-system
- monitoring
validate:
message: "CPU and memory limits are required for all containers."
pattern:
spec:
containers:
- name: "*"
resources:
limits:
cpu: "?*"
memory: "?*"
validationFailureAction: Enforce blocks admission. Audit logs violations in PolicyReport without blocking. Start with Audit for new policies.
?* matches any non-empty value. * matches any value including empty.
CEL-based validation (Kyverno 1.11+)¶
For complex logic, use CEL expressions instead of pattern matching:
validate:
cel:
expressions:
- expression: >
object.spec.containers.all(c,
has(c.resources) &&
has(c.resources.limits) &&
has(c.resources.limits.memory)
)
message: "All containers must have memory limits"
Mutation¶
Mutations run before validation. Use them to inject defaults so that validation rules succeed for users who don't specify everything:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-security-context
spec:
rules:
- name: add-readonly-root
match:
any:
- resources:
kinds: [Pod]
mutate:
patchStrategicMerge:
spec:
containers:
- (name): "*"
securityContext:
+(readOnlyRootFilesystem): true # + means: set only if not present
+(allowPrivilegeEscalation): false
+(runAsNonRoot): true
+(field) is the "add if absent" operator - it sets the value only if the field doesn't already exist. This allows workloads to override defaults explicitly while enforcing them for workloads that don't specify a value.
PatchesJSON6902¶
For precise, surgical mutations:
mutate:
patchesJSON6902:
- path: /spec/template/spec/automountServiceAccountToken
op: add
value: false
Generation¶
Generate creates, clones, or syncs resources when a trigger resource is created:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: generate-default-network-policy
spec:
rules:
- name: default-deny-all
match:
any:
- resources:
kinds: [Namespace]
generate:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: default-deny-all
namespace: "{{request.object.metadata.name}}"
synchronize: true # keep in sync - delete if policy is deleted
data:
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
When synchronize: true, Kyverno owns the generated resource - it will recreate it if deleted and update it if the policy changes. This is powerful for enforcing baseline resources (default network policies, resource quotas, LimitRanges) in every new namespace.
Clone from a source: copy a Secret or ConfigMap into every new namespace:
generate:
apiVersion: v1
kind: Secret
name: registry-credentials
namespace: "{{request.object.metadata.name}}"
clone:
namespace: kyverno
name: registry-credentials-template
synchronize: true
Image verification¶
Kyverno can verify container image signatures using Cosign or Notary at admission time:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: verify-signature
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences:
- "registry.mycompany.com/*"
attestors:
- count: 1
entries:
- keyless:
subject: "https://github.com/myorg/my-app/.github/workflows/build.yaml@refs/heads/main"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: https://rekor.sigstore.dev
mutateDigest: true # replace tag with digest after verification
required: true
mutateDigest: true replaces my-image:v1.2.0 with my-image@sha256:abc... after verifying the signature. This enforces immutability - the tag can't be swapped after deployment.
PolicyReport¶
Kyverno writes audit results to PolicyReport (namespaced) and ClusterPolicyReport (cluster-scoped) objects. Query them to find violations without enforcement:
kubectl get policyreport -A
kubectl describe policyreport -n production
# Find all violations across cluster
kubectl get policyreport -A -o json \
| jq '[.items[].results[] | select(.result == "fail")] | group_by(.policy) | map({policy: .[0].policy, count: length})'
PolicyReports integrate with Grafana (via the policy-reporter project) for dashboards and trending.
Policy exceptions¶
Exempt specific resources from specific policies without modifying the policy itself:
apiVersion: kyverno.io/v2
kind: PolicyException
metadata:
name: allow-privileged-debug-tool
namespace: debug
spec:
exceptions:
- policyName: disallow-privileged-containers
ruleNames:
- check-privileged
match:
any:
- resources:
kinds: [Pod]
namespaces: [debug]
selector:
matchLabels:
purpose: emergency-debug
Exceptions are scoped by namespace and label selector. This is safer than modifying the policy - the exception is explicit, reviewable, and can be removed independently.
Testing with kyverno test¶
The kyverno test CLI validates policies against test fixtures locally:
# kyverno-test.yaml
name: test-require-limits
policies:
- require-resource-limits.yaml
resources:
- pod-with-limits.yaml
- pod-without-limits.yaml
results:
- policy: require-resource-limits
rule: check-container-limits
resource: pod-without-limits
result: fail
- policy: require-resource-limits
rule: check-container-limits
resource: pod-with-limits
result: pass
Production rollout pattern¶
- Deploy policy in
Auditmode. Let background scanner run for 24 hours. - Review
PolicyReportresults. Identify violating resources. - Fix violations or add targeted
PolicyExceptionobjects. - Switch to
Enforcemode. Monitor admission webhook error rate. - Commit policies to Git. Sync via Argo CD or Flux.
Kyverno vs OPA/Gatekeeper¶
| Kyverno | OPA/Gatekeeper | |
|---|---|---|
| Policy language | YAML (Kubernetes-native) | Rego |
| Learning curve | Low (know K8s YAML? you're done) | Medium-High (Rego is a new language) |
| Flexibility | Medium (CEL helps with complex logic) | High (Rego is Turing-complete) |
| Mutation | First-class, patchStrategicMerge | Supported but separate |
| Generation | Built in | Not supported |
| Image verification | Built in (Cosign, Notary) | Requires external webhook |
| Audit/reporting | PolicyReport CRD | Status on Constraint object |
| Policy testing | kyverno test CLI |
conftest |
Use Kyverno when you want policy as YAML with low operational overhead and built-in generation and image verification. Use OPA/Gatekeeper when your policies are complex enough to need Rego's expressiveness or when you already have OPA in your stack for non-Kubernetes use cases.