Skip to content

Kyverno Policy Templates#

Admission-time enforcement: the rule is applied at cluster entry, not afterward via auditing. All three reject with validationFailureAction: Enforce. Switch to Audit to observe first, then enforce. Placeholders use <UPPER_CASE>. This page is the embedded form of the neighboring files; source files are in the same folder.

Files#

File What it enforces Category
disallow-latest-tag.yaml :latest forbidden, tag required Best Practices
require-image-signature.yaml Only cosign-signed images Supply Chain
require-labels.yaml Standard labels (cost/ownership) Best Practices

Policy-as-code depth: 08-Security/.

1️⃣ disallow-latest-tag.yaml#

:latest makes rollback impossible in prod (which image is running is unclear).

# Kyverno policy — reject images with the `:latest` tag
# Makes rollback impossible in production; immutable tag required.

apiVersion: kyverno.io/v2beta1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
  annotations:
    policies.kyverno.io/title: Disallow Latest Tag
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: validate-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "The `:latest` tag is forbidden in prod. Use a semantic version (`v1.2.3`) or SHA digest (`@sha256:...`)."
        pattern:
          spec:
            containers:
              - image: "!*:latest"

    - name: validate-tag-exists
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "An image cannot be tagless (Docker defaults to `:latest`)."
        pattern:
          spec:
            containers:
              - image: "*:*"

2️⃣ require-image-signature.yaml — supply chain gate#

The image you signed in CI (docker-build-push.yml → cosign keyless) is verified at cluster entry. The admission end of the supply-chain thread.

# Kyverno policy — only allow signed images to be deployed
# Verifies images signed with cosign keyless OIDC.
#
# Install: https://kyverno.io
#
# Test:
#   kubectl run pod-test --image=ghcr.io/<ORG>/<IMAGE>:<TAG>
#   (if unsigned: admission webhook reject)

apiVersion: kyverno.io/v2beta1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
  annotations:
    policies.kyverno.io/title: Verify Image Signature
    policies.kyverno.io/category: Supply Chain Security
    policies.kyverno.io/severity: high
    policies.kyverno.io/description: >-
      Only allows images from trusted registries and signed with cosign
      to be deployed to the cluster.
spec:
  validationFailureAction: Enforce       # Audit or Enforce
  background: false
  webhookTimeoutSeconds: 30
  failurePolicy: Fail
  rules:
    - name: verify-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      # Bypass: specific system namespaces (example)
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kyverno
                - cert-manager
      verifyImages:
        - imageReferences:
            - "ghcr.io/<ORG>/*"
            - "<REGISTRY>/<ORG>/*"
          attestors:
            - entries:
                - keyless:
                    subject: "https://github.com/<ORG>/*"
                    issuer: "https://token.actions.githubusercontent.com"
                    rekor:
                      url: https://rekor.sigstore.dev
          # Mutate (digest pin) — converts the image tag to a SHA digest
          mutateDigest: true
          required: true

3️⃣ require-labels.yaml — cost + ownership#

An unowned workload makes cost allocation and "who do I call" during an incident impossible.

# Kyverno policy — mandatory label check (for cost allocation, ownership)
# Every workload must have the following labels:
#   - app.kubernetes.io/name
#   - app.kubernetes.io/managed-by
#   - team
#   - cost-center

apiVersion: kyverno.io/v2beta1
kind: ClusterPolicy
metadata:
  name: require-standard-labels
  annotations:
    policies.kyverno.io/title: Require Standard Labels
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/severity: medium
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: check-labels
      match:
        any:
          - resources:
              kinds:
                - Deployment
                - StatefulSet
                - DaemonSet
                - Job
                - CronJob
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kube-public
                - kube-node-lease
      validate:
        message: |
          All workloads must have the following labels:
          - app.kubernetes.io/name
          - app.kubernetes.io/managed-by  (terraform | helm | argocd | flux)
          - team                          (e.g., payments, growth, platform)
          - cost-center                   (e.g., eng-1234)
        pattern:
          metadata:
            labels:
              app.kubernetes.io/name: "?*"
              app.kubernetes.io/managed-by: "?*"
              team: "?*"
              cost-center: "?*"

🚫 Anti-Pattern#

Anti-pattern Why it's bad Correct
Enforce from day 1 Existing workloads get rejected, the team disables the policy First Audit, see the violations, then Enforce
No signature verification Unsigned/forged images enter the cluster verifyImages + cosign keyless
No label enforcement Unowned resources, undistributable cost Baseline check via require-labels
failurePolicy: Ignore (in the signature policy) If the webhook goes down, the policy is silently bypassed Fail on a critical gate

"Policy-as-code turns the 'please do it this way' document into a 'or it won't get in' rule — a barrier, not an audit."