Skip to content
Back to skills

Kyverno

ASecurity

Kyverno is a Kubernetes-native policy engine that validates, mutates, and generates resources and verifies image signatures using YAML policies (no Rego required). Use when a user asks to enforce Pod security or resource limits with admission policies, inject defaults, create per-namespace NetworkPolicies or quotas, verify cosign signatures, test policies with the kyverno CLI, or migrate ClusterPolicy to ValidatingPolicy before Kyverno 1.20.

  • 142 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added May 29, 2026
devopsgoshellbashreactnodeexpresskubernetestestinggitapi

Works with

  • terminal
  • cli
  • api

Security analysis

A96/100
  • mediumUses curl or wget to download content

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill kyverno --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Kyverno?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Kyverno
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-kyverno/badge)](https://www.skillsdirectory.com/skills/terminalskills-kyverno)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: kyverno
description: Kyverno is a Kubernetes-native policy engine that validates, mutates, and generates resources and verifies image signatures using YAML policies (no Rego required). Use when a user asks to enforce Pod security or resource limits with admission policies, inject defaults, create per-namespace NetworkPolicies or quotas, verify cosign signatures, test policies with the kyverno CLI, or migrate ClusterPolicy to ValidatingPolicy before Kyverno 1.20.
license: Apache-2.0
compatibility: Kyverno 1.19 on Kubernetes 1.33–1.35 (Helm 3 to install); the kyverno CLI tests policies without a cluster
metadata:
  author: terminal-skills
  version: 1.1.0
  category: devops
  repository: https://github.com/kyverno/kyverno
  tags:
  - kubernetes
  - policy
  - admission-control
  - mutation
  - validation
---

# Kyverno — Kubernetes Native Policy Engine

## Overview

Kyverno runs as an admission controller plus background scanner. Policies are ordinary Kubernetes resources written in YAML, with the logic in CEL expressions (the language Kubernetes itself uses for `ValidatingAdmissionPolicy`).

**Write new policies with the CEL policy types** in `policies.kyverno.io/v1` (stable since 1.18). `ClusterPolicy`, `Policy` and `CleanupPolicy` (`kyverno.io`) are deprecated in 1.19 and scheduled for removal in 1.20 (estimated November 2026). Each new type has a namespaced twin (`NamespacedValidatingPolicy`, …) that namespace owners can manage.

| Kind (short name) | Does | Replaces `ClusterPolicy` rule |
| --- | --- | --- |
| `ValidatingPolicy` (`vpol`) | Allow, audit or deny resources | `validate` |
| `MutatingPolicy` (`mpol`) | Patch new or existing resources | `mutate` |
| `GeneratingPolicy` (`gpol`) | Create or clone resources on a trigger | `generate` |
| `ImageValidatingPolicy` (`ivpol`) | Verify image signatures and attestations | `verifyImages` |
| `DeletingPolicy` (`dpol`) | Delete matching resources on a schedule | `CleanupPolicy` |

## Instructions

### Install

```bash
helm repo add kyverno https://kyverno.github.io/kyverno/ && helm repo update
# Production: three admission replicas (a single replica is fine for a test cluster)
helm install kyverno kyverno/kyverno -n kyverno --create-namespace \
  --set admissionController.replicas=3 --set backgroundController.replicas=2 \
  --set cleanupController.replicas=2 --set reportsController.replicas=2
# Optional: the Pod Security Standards as policies, Audit by default (more: https://kyverno.io/policies/)
helm install kyverno-policies kyverno/kyverno-policies -n kyverno --set podSecurityStandard=baseline
# CLI for local testing: package manager …
brew install kyverno                 # or: kubectl krew install kyverno  ->  kubectl kyverno
# … or a release archive, verified against the published checksums
VERSION=v1.19.1
curl -fsSLO https://github.com/kyverno/kyverno/releases/download/$VERSION/kyverno-cli_${VERSION}_linux_x86_64.tar.gz
curl -fsSLO https://github.com/kyverno/kyverno/releases/download/$VERSION/checksums.txt
sha256sum --check --ignore-missing checksums.txt     # kyverno-cli_v1.19.1_linux_x86_64.tar.gz: OK
tar -xzf kyverno-cli_${VERSION}_linux_x86_64.tar.gz kyverno && install -D -m 0755 kyverno ~/.local/bin/kyverno
```

### Validation Policies

```yaml
# pod-hardening.yaml — limits required, no privileged containers, no :latest
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
  name: pod-hardening
spec:
  validationActions: [Audit]          # Audit = report only, Warn = kubectl warning, Deny = block
  matchConstraints:
    resourceRules:
      - {apiGroups: [''], apiVersions: [v1], operations: [CREATE, UPDATE], resources: [pods]}
  variables:
    - name: containers
      expression: object.spec.containers + object.spec.?initContainers.orValue([])
  validations:
    - message: Every container needs CPU and memory limits.
      expression: >-
        variables.containers.all(c, has(c.resources) && has(c.resources.limits)
          && 'cpu' in c.resources.limits && 'memory' in c.resources.limits)
    - message: Privileged containers are not allowed.
      expression: variables.containers.all(c, !c.?securityContext.?privileged.orValue(false))
    - messageExpression: "'Pin a version tag or digest instead of latest: ' + variables.containers.map(c, c.image).join(', ')"
      expression: variables.containers.map(c, image(c.image)).all(i, i.containsDigest() || i.tag() != 'latest')
```

- A policy that matches only `pods` is extended automatically to Deployments, ReplicaSets, StatefulSets, DaemonSets, Jobs and CronJobs (autogen); narrow it with `spec.autogen.podControllers.controllers`.
- Optional fields need `has()` or the `?` operator; a missing field otherwise raises an evaluation error. `image()` comes from Kyverno's CEL libraries (also `resource.Get()`, `http.Get()`); an untagged image counts as `latest`.
- Background scans of existing resources are on by default (`spec.evaluation.background.enabled`); results land in PolicyReports: `kubectl get policyreport -A`.

### Mutation Policies

```yaml
# pod-defaults.yaml — security defaults, default requests, pull secret for the private registry
apiVersion: policies.kyverno.io/v1
kind: MutatingPolicy
metadata:
  name: pod-defaults
spec:
  autogen: {podControllers: {controllers: []}}     # mutate Pods only (see the note below)
  matchConstraints:
    resourceRules:
      - {apiGroups: [''], apiVersions: [v1], operations: [CREATE], resources: [pods]}
  mutations:
    - patchType: ApplyConfiguration   # merge-style; list entries are matched by name
      applyConfiguration:
        expression: >-
          Object{spec: Object.spec{
            securityContext: Object.spec.securityContext{
              runAsNonRoot: true,
              seccompProfile: Object.spec.securityContext.seccompProfile{type: "RuntimeDefault"}
            },
            containers: object.spec.containers.map(c, Object.spec.containers{
              name: c.name,
              securityContext: Object.spec.containers.securityContext{allowPrivilegeEscalation: false},
              resources: Object.spec.containers.resources{requests: Object.spec.containers.resources.requests{
                cpu: c.?resources.?requests.?cpu.orValue(c.?resources.?limits.?cpu.orValue("100m")),
                memory: c.?resources.?requests.?memory.orValue(c.?resources.?limits.?memory.orValue("256Mi"))
              }}
            })
          }}
    - patchType: JSONPatch            # RFC 6902; required for atomic lists such as capabilities.drop (keeps an existing add list)
      jsonPatch:
        expression: >-
          object.spec.containers.map(c, JSONPatch{op: "add",
            path: "/spec/containers/" + string(object.spec.containers.indexOf(c)) + "/securityContext/capabilities",
            value: {"drop": dyn(["ALL"]), "add": dyn(c.?securityContext.?capabilities.?add.orValue([]))}})
    - patchType: JSONPatch
      jsonPatch:
        expression: >-
          object.spec.containers.exists(c, c.image.startsWith("ghcr.io/northwind/"))
          && !has(object.spec.imagePullSecrets)
          ? [JSONPatch{op: "add", path: "/spec/imagePullSecrets", value: [{"name": "ghcr-northwind"}]}]
          : []
```

`ApplyConfiguration` cannot change atomic lists or maps (the error is `may not mutate atomic arrays, maps or structs`), so use `JSONPatch` for those. Autogen rewrites `ApplyConfiguration` expressions for pod controllers but not `JSONPatch` paths: a Pod policy with a `JSONPatch` either disables autogen as above or fails on Deployments with `no such key: containers`. To patch resources that already exist, set `spec.evaluation.mutateExisting.enabled: true` and grant the background controller RBAC on the targets. Mutations run in order inside one policy; the order across policies is not guaranteed.

### Generation Policies

```yaml
# namespace-defaults.yaml — default-deny NetworkPolicy and a ResourceQuota in every new namespace
apiVersion: policies.kyverno.io/v1
kind: GeneratingPolicy
metadata:
  name: namespace-defaults
spec:
  evaluation:
    synchronize: {enabled: true}      # revert manual edits; follow policy changes
    generateExisting: {enabled: true} # also cover namespaces that already exist
  matchConstraints:
    resourceRules:
      - {apiGroups: [''], apiVersions: [v1], operations: [CREATE], resources: [namespaces]}
  matchConditions:
    - name: skip-system-namespaces
      expression: "!(object.metadata.name in ['kube-system', 'kube-public', 'kyverno'])"
  variables:
    - name: ns
      expression: object.metadata.name
    - name: downstream
      expression: >-
        [
          {"apiVersion": dyn("networking.k8s.io/v1"), "kind": dyn("NetworkPolicy"),
           "metadata": dyn({"name": "default-deny-ingress"}),
           "spec": dyn({"podSelector": dyn({}), "policyTypes": dyn(["Ingress"])})},
          {"apiVersion": dyn("v1"), "kind": dyn("ResourceQuota"),
           "metadata": dyn({"name": "default-quota"}),
           "spec": dyn({"hard": dyn({"requests.cpu": "4", "requests.memory": "8Gi",
             "limits.cpu": "8", "limits.memory": "16Gi", "pods": "50"})})}
        ]
  generate:
    - expression: generator.Apply(variables.ns, variables.downstream)
```

To clone instead of template, fetch the source with `resource.Get("v1", "secrets", "platform", "ghcr-northwind")` and pass it to `generator.Apply(variables.ns, [variables.source])`. With `synchronize`, deleting the trigger deletes the generated resources. The background controller needs RBAC for every kind it creates.

### Verify Image Signatures

```yaml
# verify-images.yaml — only images signed by the release workflow (cosign keyless) may run
apiVersion: policies.kyverno.io/v1
kind: ImageValidatingPolicy
metadata:
  name: verify-northwind-images
spec:
  validationActions: [Deny]
  webhookConfiguration: {timeoutSeconds: 15}       # default 10, maximum 30
  matchConstraints:
    resourceRules:
      - {apiGroups: [''], apiVersions: [v1], operations: [CREATE, UPDATE], resources: [pods]}
  matchImageReferences:
    - glob: ghcr.io/northwind/*
  attestors:
    - name: ci
      cosign:
        keyless:
          identities:
            - subject: https://github.com/northwind/checkout-api/.github/workflows/release.yml@refs/heads/main
              issuer: https://token.actions.githubusercontent.com
        ctlog: {url: https://rekor.sigstore.dev}
  validations:
    - message: Image is not signed by the northwind release workflow.
      expression: >-   # every matched image must be verified, init containers included, or the Pod is rejected
        (images.containers + images.initContainers + images.ephemeralContainers)
          .all(i, verifyImageSignatures(i, [attestors.ci]) > 0)
```

Use `subjectRegExp` to accept several workflows, or `cosign.key.data` with a PEM public key. In the cluster the tag is rewritten to the verified digest (`validationConfigurations.mutateDigest`, default true). Private registries: list pull secrets under `spec.credentials.secrets`; they must live in the Kyverno namespace, and the Pod's own `imagePullSecrets` are not used. On the CLI, verification needs digest references (`image:tag@sha256:…`) and network access to the registry and Rekor.

### Exceptions and Local Testing

```yaml
# exception.yaml — needs the chart values features.policyExceptions.enabled=true and
# features.policyExceptions.namespace=policy-exceptions (exceptions are off by default)
apiVersion: policies.kyverno.io/v1
kind: PolicyException
metadata:
  name: allow-node-exporter
  namespace: policy-exceptions
spec:
  policyRefs:
    - {name: pod-hardening, kind: ValidatingPolicy}
  matchConditions:
    - name: node-exporter-pods
      expression: object.metadata.namespace == 'monitoring' && object.metadata.name.startsWith('node-exporter')
  expiresAt: "2027-03-31T00:00:00Z"
```

```bash
kyverno apply pod-hardening.yaml --resource manifests/ --table        # dry run; exit code 1 on any failure
helm template charts/checkout-api | kyverno apply policies/ --resource -      # rendered charts via stdin
kyverno apply pod-hardening.yaml --resource manifests/ --exception exception.yaml
kyverno apply policies/ --cluster --policy-report                     # scan the current kubectl context
kyverno test tests/                    # runs every kyverno-test.yaml below tests/
```

```yaml
# tests/kyverno-test.yaml — expected results, checked by `kyverno test`
apiVersion: cli.kyverno.io/v1alpha1
kind: Test
metadata: {name: pod-hardening}
policies: [../pod-hardening.yaml]
resources: [pods.yaml]
results:
  - {policy: pod-hardening, isValidatingPolicy: true, kind: Pod, resources: [checkout-api], result: pass}
  - {policy: pod-hardening, isValidatingPolicy: true, kind: Pod, resources: [debug-shell], result: fail}
```

## Examples

### Example 1: Stop `:latest` and missing limits without blocking anyone yet

**User request:** "Pods keep landing in our cluster with `:latest` and no limits. Add a Kyverno policy, but only report for now."

Save `pod-hardening.yaml` from above with `validationActions: [Audit]`, then check the repository's manifests before touching the cluster:

```
$ kyverno apply pod-hardening.yaml --resource k8s/
Applying 1 policy rule(s) to 3 resource(s)...
policy pod-hardening -> resource payments/Pod/debug-shell failed:
1 -  Pin a version tag or digest instead of latest: ghcr.io/northwind/debug-shell:latest
policy pod-hardening -> resource payments/Deployment/checkout-api failed:
1 -  Every container needs CPU and memory limits.

pass: 1, fail: 2, warn: 0, error: 0, skip: 0
```

Then `kubectl apply -f pod-hardening.yaml`, confirm it with `kubectl get vpol`, read violations of running workloads with `kubectl get policyreport -A`, and change the action to `[Deny]` once the reports are clean.

### Example 2: Migrate a ClusterPolicy before upgrading to 1.20

**User request:** "We still have this `require-labels` ClusterPolicy with a `pattern`. Convert it so the upgrade does not break us."

The legacy rule `validate.pattern.metadata.labels: {team: "?*", app: "?*"}` with `validationFailureAction: Enforce` becomes:

```yaml
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
  name: require-team-labels
spec:
  validationActions: [Deny]           # was validationFailureAction: Enforce
  matchConstraints:                   # was match.any[].resources.kinds
    resourceRules:
      - {apiGroups: [apps], apiVersions: [v1], operations: [CREATE, UPDATE], resources: [deployments, statefulsets]}
  validations:                        # was validate.pattern
    - message: Deployments and StatefulSets need non-empty 'team' and 'app' labels.
      expression: "['team', 'app'].all(l, object.metadata.?labels[l].orValue('') != '')"
```

Other mappings: `preconditions` → `matchConditions`, `context` → `variables`, `{{ request.object }}` → `object`, `patchStrategicMerge` → `ApplyConfiguration`, `exclude` → `matchConstraints.excludeResourceRules`. Run `kyverno apply policies/ --resource k8s/ --warnings-as-errors` in CI: it fails while any legacy policy remains (`Error: found 1 deprecation warning(s) and --warnings-as-errors is set`).

## Guidelines

1. **Audit before Deny** — start with `validationActions: [Audit]`, read the PolicyReports, then enforce.
2. **Migrate before 1.20** — legacy `ClusterPolicy`, `CleanupPolicy` and `kyverno.io` PolicyExceptions stop working when the types are removed; `kubectl get cpol,pol,cleanpol,ccleanpol -A` lists what is left in a cluster, and `sum(kyverno_deprecated_api_requests_total) by (kind)` shows which legacy kinds are still being created or updated. Read the release notes of every minor version and upgrade with Helm, not by re-applying `install.yaml`.
3. **A fail-closed webhook can block the cluster** — policies fail closed by default; give Kyverno its own namespace, run three admission replicas and keep that namespace excluded (the chart default). If the API server is stuck because Kyverno is down, delete `kyverno-resource-validating-webhook-cfg` and `kyverno-resource-mutating-webhook-cfg`; Kyverno recreates them.
4. **Test in CI** — `kyverno apply` and `kyverno test` need no cluster; keep a `kyverno-test.yaml` next to each policy.
5. **Exceptions are a bypass** — restrict them to one namespace only platform admins can write to and give each an `expiresAt`.
6. **Not a runtime sensor** — Kyverno acts at admission and in background scans; it does not watch running processes (use Falco or Tetragon), and it complements rather than replaces RBAC.

Files in this skill

  • SKILL.md9.1 KB
  • _scores.json1.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.