← Back to list

The Complete Guide to Passing the Kyverno Certified Associate (KCA) Exam

Kubernetes policy management has become a critical discipline as organizations scale their clusters and enforce security, compliance, and…

Rahul Rai · 2026-06-20 00:31 · 0 claps · 10.8 min read
#kyverno #kca #k8s-admission-controller
Open on Medium ↗
Wiki topics: BIZ · Business Strategy ☁️ · DevOps & Cloud 🔒 · Cybersecurity 🚀 · Self Improvement

The Complete Guide to Passing the Kyverno Certified Associate (KCA) Exam

Kubernetes policy management has become a critical discipline as organizations scale their clusters and enforce security, compliance, and operational standards. The Kyverno Certified Associate (KCA) exam tests whether you can design, implement, and troubleshoot policies using Kyverno — the Kubernetes-native policy engine built by Nirmata and now a CNCF incubating project.

This guide covers everything you need to pass: exam format, all major domains with annotated YAML examples, CLI usage, and a practical study strategy.

Exam at a glance:

  • 60 multiple-choice questions
  • 90 minutes
  • Passing score: 75/100
  • Entirely conceptual — no live cluster, but expects deep YAML and JMESPath fluency

Part 1: Understanding Kyverno’s Four Pillars

Before writing a single line of policy YAML, you need to internalize what Kyverno actually does. Every exam question traces back to one of these four capabilities.

1. Validation — Kyverno inspects incoming Kubernetes resources and either allows or blocks them based on rules you define. This is the most common use case — enforcing standards like required labels, disallowing privileged containers, or mandating resource limits.

2. Mutation — Kyverno intercepts resources before they reach etcd and modifies them. This is how you inject defaults — adding labels, setting security contexts, or appending sidecar containers — without requiring developers to remember to do it themselves.

3. Generation — When a resource is created, Kyverno can automatically create additional resources. The classic example is generating a default NetworkPolicy in every new namespace, or copying a ConfigMap from one namespace into another.

4. Image Verification — Kyverno integrates with Cosign and Sigstore to verify that container images have been signed by a trusted party before allowing them to run. This closes a significant supply chain security gap.

Part 2: How Kyverno Fits Into the Admission Controller Flow

Understanding where Kyverno sits in the Kubernetes request lifecycle is foundational — several exam questions hinge on this.

Client Request
      ↓
Authentication & Authorization
      ↓
Mutating Admission Webhooks  ← Kyverno mutation runs here
      ↓
Schema Validation (OpenAPI)
      ↓
Validating Admission Webhooks ← Kyverno validation runs here
      ↓
etcd (persisted)

Key implication: mutation always runs before validation. This means a mutating policy can add a field, and a validating policy can then check that field exists — they work together.

Part 3: Policy Structure Deep Dive

ClusterPolicy vs Policy

# ClusterPolicy — enforces rules across ALL namespaces
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: my-cluster-policy
---
# Policy — enforces rules only within the namespace it is deployed in
apiVersion: kyverno.io/v1
kind: Policy
metadata:
  name: my-namespaced-policy
  namespace: production   # only resources in 'production' are checked

The Core Policy Fields

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: annotated-policy-example
spec:
  validationFailureAction: Enforce  # Enforce = deny the request on violation
                                    # Audit   = allow but log to PolicyReport
  failurePolicy: Fail               # Fail   = block admission if Kyverno webhook is unreachable
                                    # Ignore = allow through if Kyverno is unavailable
  background: true                  # true  = scan existing resources periodically
                                    # false = only check new/updated resources
                                    # Must be false if the rule uses request.userInfo
  rules: []

Match and Exclude Logic

rules:
  - name: example-match-exclude
    match:
      any:              # any: = OR — resource must satisfy AT LEAST ONE of these blocks
        - resources:
            kinds:
              - Deployment
            namespaces:
              - production
        - resources:
            kinds:
              - StatefulSet
    exclude:
      any:              # any: = OR — resource matching ANY exclude block is skipped
        - resources:
            namespaces:
              - kube-system   # never touch system namespaces
        - subjects:
            - kind: ServiceAccount
              name: system-deployer  # exclude a specific service account

any: is OR logic — one match is enough. all: is AND logic — every condition must be true. This distinction comes up repeatedly in exam questions.

Part 4: Validation Policies

Pattern-Based Validation

The simplest form — describe what a valid resource looks like, and Kyverno blocks anything that doesn’t match.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: check-required-labels
      match:
        any:
          - resources:
              kinds:
                - Deployment
                - StatefulSet
      validate:
        message: "Resources must have 'app', 'env', and 'team' labels."
        pattern:
          metadata:
            labels:
              app: "?*"    # ?* = one or more characters (rejects empty string or missing key)
              env: "?*"
              team: "?*"

Deny-Based Validation with JMESPath

For conditions that can’t be expressed as a simple pattern, use deny: with JMESPath expressions.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationFailureAction: Enforce
  rules:
    - name: no-latest-tag
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Container images must not use the 'latest' tag. Pin to a specific version."
        deny:
          conditions:
            any:
              - key: "{{ request.object.spec.containers[?contains(image, 'latest')] | length(@) }}"
                # Step by step:
                # request.object.spec.containers          → the array of containers in this Pod
                # [?contains(image, 'latest')]            → filter: keep containers whose image
                #                                           string contains 'latest'
                # | length(@)                             → count how many matched
                # If the count > 0, at least one container uses latest → deny fires
                operator: GreaterThan
                value: 0
              - key: "{{ request.object.spec.initContainers[?contains(image, 'latest')] | length(@) }}"
                # Same check for initContainers — don't forget these on the exam
                operator: GreaterThan
                value: 0

Disallow Privileged Containers

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
spec:
  validationFailureAction: Enforce
  rules:
    - name: no-privileged
      match:
        any:
          - resources:
              kinds:
                - Pod
      validate:
        message: "Privileged containers are not allowed."
        pattern:
          spec:
            containers:
              - =(securityContext):      # =(...) = field is optional; only checked if present
                  =(privileged): false  # if privileged exists, it must be false
                                        # absent fields are NOT a violation

Part 5: Mutation Policies

patchStrategicMerge

Works like kubectl apply — merges your block into the existing resource, leaving unrelated fields untouched.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: inject-defaults
spec:
  rules:
    - name: set-resource-limits
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchStrategicMerge:
          spec:
            containers:
              - (name): "*"                   # (name): "*" = apply to every container in the list
                resources:
                  limits:
                    memory: "256Mi"           # added if missing; does NOT overwrite existing limits
                    cpu: "500m"
                  requests:
                    memory: "128Mi"
                    cpu: "250m"

patchesJson6902

For surgical edits — when you need to target a specific index, remove a field, or make a precise replacement.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: harden-security-context
spec:
  rules:
    - name: add-readonly-root
      match:
        any:
          - resources:
              kinds:
                - Pod
      mutate:
        patchesJson6902: |-
          - op: add                                                           # op: add | remove | replace
            path: /spec/containers/0/securityContext/readOnlyRootFilesystem  # JSON Pointer — /0 = first container
            value: true
          - op: add
            path: /spec/containers/0/securityContext/allowPrivilegeEscalation
            value: false
          - op: add
            path: /spec/securityContext/runAsNonRoot   # Pod-level security context
            value: true

Adding Annotations Dynamically

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-audit-annotation
spec:
  rules:
    - name: annotate-with-user
      match:
        any:
          - resources:
              kinds:
                - Deployment
      mutate:
        patchStrategicMerge:
          metadata:
            annotations:
              audit.kyverno.io/created-by: "{{ request.userInfo.username }}"
              # request.userInfo.username = the Kubernetes user who submitted the request
              # stamps every Deployment with who created it at admission time
              # NOTE: background: false is required when using request.userInfo

Part 6: Generation Policies

Generation fires when a matching resource is created and auto-produces a new resource.

Generate a NetworkPolicy in Every New Namespace

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: default-networkpolicy
spec:
  rules:
    - name: generate-default-deny
      match:
        any:
          - resources:
              kinds:
                - Namespace   # trigger: fires whenever any Namespace is created
      generate:
        apiVersion: networking.k8s.io/v1
        kind: NetworkPolicy
        name: default-deny-ingress
        namespace: "{{ request.object.metadata.name }}"  # target = the newly created namespace
        synchronize: true   # true  = Kyverno owns this resource; recreates it if deleted
                            # false = create once and let users manage it manually
        data:
          spec:
            podSelector: {}      # {} = selects all pods in the namespace
            policyTypes:
              - Ingress           # deny all ingress traffic by default

Clone a ConfigMap into New Namespaces

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: clone-registry-config
spec:
  rules:
    - name: copy-registry-credentials
      match:
        any:
          - resources:
              kinds:
                - Namespace
      generate:
        apiVersion: v1
        kind: ConfigMap
        name: registry-config
        namespace: "{{ request.object.metadata.name }}"
        synchronize: true
        clone:
          namespace: kyverno       # source namespace where the original ConfigMap lives
          name: registry-config    # source ConfigMap name to clone from

Part 7: Image Verification

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  background: false   # image verification requires the live admission context
  rules:
    - name: check-image-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "registry.example.com/*"    # glob — applies to all images from this registry
          required: true                  # true = image MUST have a valid signature
          attestors:
            - count: 1                    # at least 1 attestor must verify successfully
              entries:
                - keys:
                    publicKeys: |-        # Cosign public key (from `cosign generate-key-pair`)
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
                      -----END PUBLIC KEY-----

Part 8: JMESPath Reference

JMESPath is Kyverno’s query language for extracting and filtering data from resource specs. The exam tests it heavily.

# Pattern 1 — extract a single value
key: "{{ request.object.metadata.name }}"
# Returns: the resource's name as a string
# Pattern 2 — filter an array
key: "{{ request.object.spec.containers[?contains(image, 'myregistry')] }}"
# Returns: array of containers whose image references 'myregistry'
# Pattern 3 — count filtered results
key: "{{ request.object.spec.containers[?contains(image, 'latest')] | length(@) }}"
# | length(@) pipes the filtered array into the length function
# Returns: integer count of matching containers
# Pattern 4 — null-safe fallback
key: "{{ request.object.metadata.labels.env || '' }}"
# || '' = if the left side is null/missing, return empty string
# Prevents JMESPath errors when the field doesn't exist
# Pattern 5 — extract all values from an array field
key: "{{ request.object.spec.containers[].image }}"
# [] = wildcard projection — extracts 'image' from every element
# Returns: ["nginx:1.21", "redis:6"]
# Pattern 6 — reference data from the context block
rules:
  - name: lookup-example
    context:
      - name: allowedRegistries        # assigns the API call result to this variable name
        apiCall:
          urlPath: "/api/v1/namespaces/policy-data/configmaps/allowed-registries"
          jmesPath: "data.registries | split(@, ',')"
          # post-processes the API response
          # split(@, ',') turns a comma-separated string into an array
    validate:
      deny:
        conditions:
          any:
            - key: "{{ request.object.spec.containers[0].image }}"
              operator: AnyNotIn
              value: "{{ allowedRegistries }}"   # reference the context variable by name

Part 9: PolicyReport

Kyverno auto-generates PolicyReport (namespace-scoped) and ClusterPolicyReport (cluster-scoped) resources when policies run in Audit mode. You don't create these — you read them.

# Auto-generated by Kyverno — understand the structure, not how to write it
apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
  name: polr-ns-default
  namespace: default    # one PolicyReport per namespace
summary:
  pass: 47    # resources that satisfied all policies
  fail: 3     # resources that violated at least one policy
  warn: 0     # violations against warn-action policies
  error: 0    # Kyverno encountered an error evaluating the policy
  skip: 0     # policy did not apply (e.g. resource was excluded)
results:
  - policy: require-labels              # which ClusterPolicy triggered this result
    rule: check-required-labels         # which rule within that policy
    resources:
      - apiVersion: apps/v1
        kind: Deployment
        name: legacy-app
        namespace: default
    result: fail                        # pass | fail | warn | error | skip
    message: "Resources must have 'app', 'env', and 'team' labels."
    scored: true                        # true = counts toward pass/fail summary totals
    severity: medium                    # info | low | medium | high | critical

Part 10: PolicyException

When you need to exempt a specific resource from a policy without modifying the policy itself.

apiVersion: kyverno.io/v2beta1
kind: PolicyException
metadata:
  name: legacy-app-exception
  namespace: legacy               # PolicyException is namespace-scoped
spec:
  exceptions:
    - policyName: disallow-latest-tag   # exact name of the ClusterPolicy to exempt from
      ruleNames:
        - no-latest-tag                 # exempt only this rule, not the entire policy
  match:
    any:
      - resources:
          kinds:
            - Pod
          namespaces:
            - legacy              # exception only applies to Pods in the 'legacy' namespace
          names:
            - legacy-app-*        # glob — only Pods named 'legacy-app-something'

Part 11: ClusterCleanupPolicy

Background scans audit but never delete. For automated cleanup, use ClusterCleanupPolicy.

apiVersion: kyverno.io/v2alpha1
kind: ClusterCleanupPolicy
metadata:
  name: remove-completed-jobs
spec:
  match:
    any:
      - resources:
          kinds:
            - Job
          selector:
            matchLabels:
              cleanup: "enabled"   # only Jobs explicitly opted in to cleanup
  conditions:
    any:
      - key: "{{ status.conditions[?type=='Complete'] | length(@) }}"
        # filter status.conditions for entries where type == 'Complete'
        # count > 0 means the Job finished successfully
        operator: GreaterThan
        value: 0
      - key: "{{ time_since('', metadata.creationTimestamp, '') }}"
        # time_since returns elapsed duration since the timestamp
        operator: GreaterThan
        value: "1h"   # only delete Jobs older than 1 hour
  schedule: "0 * * * *"    # cron — runs at the top of every hour

Part 12: Auto-Generated Rules

When you write a policy targeting Pod, Kyverno automatically generates equivalent rules for higher-level controllers (Deployment, StatefulSet, DaemonSet, Job, CronJob) that produce Pods.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-memory-limits
  annotations:
    pod-policies.kyverno.io/autogen-controllers: "Deployment,StatefulSet,DaemonSet"
    # Controls which controllers get auto-generated rules.
    # Omit = Kyverno generates for all supported controllers (default behavior).
    # "none" = disable auto-gen entirely.
    # Comma-separated list = target only those controllers.
spec:
  rules:
    - name: check-memory-limits
      match:
        any:
          - resources:
              kinds:
                - Pod    # write for Pod; Kyverno clones this rule for listed controllers
      validate:
        message: "Memory limits must be set."
        pattern:
          spec:
            containers:
              - resources:
                  limits:
                    memory: "?*"

Part 13: Default Ports and TLS Defaults

These constants appear on the exam. Memorize them.

Default Ports:

PortPurpose9443Admission webhook traffic — most critical8000Prometheus metrics scrape endpoint8080Liveness and readiness probes6060pprof profiling (disabled by default)

TLS Certificate Defaults:

SettingDefault ValueCA certificate validity365 daysTLS certificate validity150 daysCertificate renewal check interval12 hours

Part 14: CLI Usage

The kyverno CLI lets you test policies locally without a cluster. Know the key commands and their flags.

# Apply a policy against a resource manifest (no cluster required)
kyverno apply policy.yaml --resource pod.yaml
# Apply multiple policies against multiple resources
kyverno apply ./policies/ --resource ./manifests/
# Show detailed output including passing results
kyverno apply policy.yaml --resource pod.yaml --detailed-results
# Run a structured test suite (uses kyverno-test.yaml descriptor)
kyverno test ./tests/
# Run tests with verbose output
kyverno test ./tests/ --detailed-results
# Test a JMESPath expression against a JSON file
kyverno jp query -i pod.json 'spec.containers[].image'
# Validate policy YAML syntax (catches schema errors before applying)
kyverno validate policy.yaml
# Validate all policies in a directory
kyverno validate ./policies/

kyverno-test.yaml Structure

# kyverno-test.yaml — lives in the test directory alongside policy and resource files
name: disallow-latest-tag-tests
policies:
  - ../policies/disallow-latest-tag.yaml    # relative path to the policy under test
resources:
  - resources/pod-with-latest.yaml          # resource that SHOULD be blocked
  - resources/pod-with-version.yaml         # resource that SHOULD pass
results:
  - policy: disallow-latest-tag
    rule: no-latest-tag
    resource: pod-with-latest               # matches metadata.name in the resource file
    result: fail                            # expected outcome: pass | fail | skip
  - policy: disallow-latest-tag
    rule: no-latest-tag
    resource: pod-with-version
    result: pass

Part 15: CEL Support

Kyverno supports Common Expression Language (CEL) as an alternative to JMESPath for writing rule conditions.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: cel-example
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-replicas-cel
      match:
        any:
          - resources:
              kinds:
                - Deployment
      validate:
        cel:
          expressions:
            - expression: "object.spec.replicas <= 10"
              # object = the incoming resource being admitted
              # Returns true = valid; false = violation
              message: "Replicas must not exceed 10."
            - expression: "has(object.metadata.labels) && 'app' in object.metadata.labels"
              # has() = checks if a field exists (CEL equivalent of ?* in patterns)
              # 'app' in object.metadata.labels = key membership check
              message: "An 'app' label is required."

Part 16: Study Strategy and Exam Tips

What to Focus On

High weight — spend most of your time here:

  • JMESPath filtering, counting, null handling, piping operators
  • validationFailureAction and failurePolicy differences
  • background field behavior and when to disable it
  • patchStrategicMerge vs patchesJson6902 — when to use each
  • synchronize in generate rules
  • any: vs all: match logic
  • Auto-gen rule behavior and the controlling annotation

Medium weight:

  • PolicyReport structure and result fields
  • PolicyException syntax and scope
  • Default ports (9443 especially)
  • TLS TTL defaults
  • CLI command flags

Don’t neglect:

  • ClusterCleanupPolicy — newer CRD, likely appears
  • CEL expressions — basic syntax
  • context: block for external data lookups
  • =(optional): value pattern operator

Practical Study Plan

Week 1 — Build policies by hand. Write validation, mutation, and generation policies against a local kind cluster. Don’t copy-paste — type every field until the structure is automatic.

Week 2 — Master JMESPath. Use kyverno jp query against real resource JSON. Practice every pattern in the table above until they're second nature.

Week 3 — Mock exams. Take all available practice tests. For every wrong answer, find the exact Kyverno docs section that covers it and re-read it.

Day before — Review constants. Ports, TLS TTLs, background scan behavior, auto-gen annotation. These are free points if memorized.

On Exam Day

  • Read every question twice — the difference between Enforce and Audit, or Fail and Ignore, is often the entire answer
  • For JMESPath questions, trace the expression step by step: what does the filter return, what does the pipe do, what is the final type and value
  • When two answers look identical, check the failurePolicy or background field — that's usually where the trap is

Summary

Kyverno’s power comes from composing these primitives: match the right resources, validate or mutate them with patterns or JMESPath, generate companion resources automatically, and verify image provenance. The KCA exam tests whether you can read and write this fluently — not just describe it abstractly.

The candidates who pass are the ones who have typed enough YAML that the structure is muscle memory. Build policies, break them intentionally, fix them, and test them with the CLI. That hands-on foundation is exactly what the exam is designed to surface.


메타데이터
post_id
ff7c12a30fc6
slug
the-complete-guide-to-passing-the-kyverno-certified-associate-kca-exam-ff7c12a30fc6
url
https://medium.com/@rahul.tolearn/the-complete-guide-to-passing-the-kyverno-certified-associate-kca-exam-ff7c12a30fc6
canonical_url
https://medium.com/@rahul.tolearn/the-complete-guide-to-passing-the-kyverno-certified-associate-kca-exam-ff7c12a30fc6
author_url
https://medium.com/@rahul.tolearn
status
ok
fetched_at
2026-06-26 03:39:16