alt text

Kyverno Overview: The "Kubernetes Native" Policy Engine

If you are preparing for the KCA (Kyverno Certified Associate) exam or just getting started with policy-as-code, you need to grasp the core concepts before diving into the syntax. Here is the high-level overview.

1. What is Kyverno? (Keyword: Kubernetes Native)

Kyverno is the engine (Controller), and ClusterPolicy/Policy are the configuration formats (CRDs) that it uses.

Unlike OPA Gatekeeper (which requires learning the complex Rego language), Kyverno is Kubernetes Native.

  • The Key Differentiator: Policies are written in standard YAML. If you can read Kubernetes manifests (Deployment, Service, etc.), you can understand Kyverno policies immediately.
  • Role: It acts as a "Gatekeeper" (Admission Controller) sitting right at the API Server door.

2. Core Logic (How it works)

When you execute kubectl apply -f pod.yaml, the flow is as follows:

  • The request hits the API Server.
  • Kyverno (acting as a Webhook) intercepts the request.
  • It checks if the request matches any defined Policy.
  • If matched, it executes one of the 4 main actions (this is the backbone of the KCA exam):

3. Key Capabilities (The 4 Pillars)

Validation (Most Common)

  • Goal: "Allowed" or "Denied".
  • Example: A Pod must have the label team: dev. If missing -> Block (Enforce) or Warn (Audit).

Mutation (Modify)

  • Goal: Modify the YAML content before it is persisted to the database (etcd).
  • Example: A user creates a Pod but forgets the imagePullPolicy. Kyverno automatically injects imagePullPolicy: Always.

Generation (Create New) - The "Killer Feature"

  • Goal: Automatically generate additional resources based on a trigger.
  • Example: When a new Namespace is created -> Kyverno automatically generates a default NetworkPolicy and ResourceQuota inside that Namespace.

Verify Images (Supply Chain Security)

  • Goal: Check container image signatures.
  • Example: Only allow images signed by the company's private key (integrates with Cosign/Sigstore)

4. Anatomy of a Policy

A basic Policy file follows this nested structure:

  • Policy / ClusterPolicy:
Policy: Scoped to a specific Namespace.
ClusterPolicy: Applied to the entire cluster (Global).
  • Rules: A Policy can contain multiple Rules
  • Match / Exclude: Selects the resources to target (e.g., match all Pods, exclude Pods in kube-system).
  • Action: What to do (validate, mutate, generate, verifyImages).

Quick YAML Structure Example:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-labels
spec:
  validationFailureAction: Enforce # Enforce (Block) or Audit (Log report)
  rules:
  - name: check-team-label
    match:
      resources:
        kinds:
        - Pod # Apply to Pods
    validate: # Validation Action
      message: "The 'team' label is required!"
      pattern:
        metadata:
          labels:
            team: "?*" # Check for existence

5. Exam Tips (Must Know)

Models:

  • Audit: Violations are allowed but logged in a PolicyReport. This is the safe default for Production to avoid breaking existing workflows.
  • Enforce: Violations are blocked immediately (Deny).

JMESPath: Kyverno uses JMESPath for logic processing (handling if/else, contains, sum, etc.). Expect questions on filtering JSON data using JMESPath.

Auto-gen Rules: If you write a rule for Pod, Kyverno is smart enough to automatically generate rules for Deployment, StatefulSet, DaemonSet, etc. (unless you explicitly disable this feature).

Summary: Kyverno is an Admission Controller that uses YAML to Validate, Mutate, Generate resources, and Verify Images.


Sections need to be understood

Kyverno Match/Exclude

Purpose: Identifying and filtering resources for rule evaluation.

Basic Structure:

match:
  any:   # OR logic - satisfy at least one condition
  - resources:
      kinds: [...]        # REQUIRED
      names: [...]        # Optional
      namespaces: [...]   # Optional
      operations: [...]   # Optional (default: CREATE, UPDATE)
      selector: {...}     # Optional (label selector)
match:
  all:   # AND logic - must satisfy ALL conditions
  - resources:
      kinds: [...]

Example Match Pod or Deployment with specific label: Applies to Deployment OR StatefulSet with label app=critical

match:
  any:
  - resources:
      kinds:
      - Deployment
      - StatefulSet
      operations:
      - CREATE
      - UPDATE
      selector:
        matchLabels:
          app: critical

Exclude specific namespace: Applies to ALL Pods EXCEPT those in prod-alpha namespace

match:
  any:
  - resources:
      kinds:
      - Pod
exclude:
  any:
  - resources:
      namespaces:
      - prod-alpha

Match by namespace label: Only applies to Deployments in namespaces with label type=connector or type=compute

match:
  any:
  - resources:
      kinds:
      - Deployment
      namespaceSelector:
        matchExpressions:
        - key: type
          operator: In
          values:
          - connector
          - compute

Exclude user/role: Applies to Pods with label app=critical EXCEPT those created by cluster-admin or user John

match:
  any:
  - resources:
      kinds:
      - Pod
      selector:
        matchLabels:
          app: critical
exclude:
  any:
  - clusterRoles:
    - cluster-admin
  - subjects:
    - kind: User
      name: John

Important Logic:

Within resources block:

  • AND across types (kinds AND namespaces)
  • OR within lists (Pod OR Deployment)

Between match and exclude: AND logic (must satisfy match AND not satisfy exclude)

Tips:

  • operations is optional, defaults to [CREATE, UPDATE]
  • Wildcards * supported in kinds, names, namespaces
  • Use any: for OR logic, all: for AND logic
  • exclude must be a subset of match

request.object Variable

Documentation: https://kyverno.io/docs/policy-types/cluster-policy/variables/

What is request.object?

request.object is the incoming resource configuration (the new state) that is being submitted to Kubernetes API server.

Practical Example 1 - Access resource name: If you create namespace

prod -> {{request.object.metadata.name}} = "prod"

validate:
  message: "Creating namespace: {{request.object.metadata.name}}"
  pattern:
    metadata:
      labels:
        team: "?*"

Practical Example 2 - Generate Secret in new namespace: When creating namespace staging -> Secret is generated in staging namespace

generate:
  kind: Secret
  name: regcred
  namespace: "{{request.object.metadata.name}}"  # Uses incoming namespace name
  clone:
    namespace: default
    name: regcred

Practical Example 3 - Validate based on labels: Only runs if the incoming resource has label app=critical

preconditions:
  any:
  - key: "{{request.object.metadata.labels.app}}"
    operator: Equals
    value: "critical"

Quick Comparison:

# User submits this Pod:
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: web
spec:
  containers:
  - name: nginx
    image: nginx:1.20
  • {{request.object.metadata.name}} -> "nginx"
  • {{request.object.metadata.labels.app}} -> "web"
  • {{request.object.spec.containers[0].image}} -> "nginx:1.20"

What is JMESPath used for in Kyverno?

In Kyverno, JMESPath (pronounced "James Path") is a JSON query language used to select, filter, and transform data from Kubernetes resources.

Think of it as the logic layer that allows your policies to "read" and "process" YAML manifests.

It is primarily used for:

  • Variable Substitution: Extracting values from a request to use elsewhere (e.g., getting a namespace name: {{ request.object.metadata.namespace }}).
  • Condition Logic: Writing complex preconditions to decide if a rule should run (e.g., "only run this if the image tag is NOT 'latest'").
  • Filtering: Selecting specific items from arrays (e.g., "find all containers that have port 80 open").
  • Data Transformation: Modifying data formats before validation or mutation.

Example: To check if a label exists, you might use a JMESPath expression like: contains(request.object.metadata.labels, 'production')

Let's go for scenario:

  • We want to enforce a rule that says: "If a Pod has the label env set to production, it must also include a contact-email annotation."
  • We use the JMESPath contains function in the preconditions block to target only specific resources.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-annotation-for-production
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: check-production-annotation
      match:
        any:
        - resources:
            kinds:
              - Pod

      # 1. PRECONDITIONS: The JMESPath Logic
      # This block ensures the rule ONLY runs if the label 'env' contains 'production'
      preconditions:
        all:
        - key: true
          operator: Equals
          # Note: In JMESPath, `contains` returns true/false. 
          # We check if the 'env' label value contains the string 'production'.
          # || '' is the fucking fallback, because contains(null, 'production') will error
          # But contains('', 'production') will not!!!
          value: "{{ contains(request.object.metadata.labels.env || '', 'production') }}"

      # 2. VALIDATION: The actual check to enforce
      validate:
        message: "Production pods must have the 'contact-email' annotation."
        pattern:
          metadata:
            annotations:
              contact-email: "?*"

This will be blocked duo to missing contact-email annotation.

apiVersion: v1
kind: Pod
metadata:
  name: bad-prod-pod
  labels:
    env: production
spec:
  containers:
  - name: nginx
    image: nginx

This will be allowed because env is not production haha

apiVersion: v1
kind: Pod
metadata:
  name: dev-pod
  labels:
    env: development
spec:
  containers:
  - name: nginx
    image: nginx

What is PolicyReport?

The PolicyReport is essentially a "Health Check Report Card" for your Kubernetes resources.

While ClusterPolicy defines the rules, the PolicyReport stores the results of those rules.

Purpose of PolicyReport: Its main goal is Observability & Auditing.

Without PolicyReports, if a policy is set to Audit mode (non-blocking), you would have no easy way to know which resources are violating rules—you would have to dig through Kyverno controller logs.

  • Audit Results: It lists resources that exist in your cluster but violate a policy.
  • Standardization: It is not unique to Kyverno! It is a standard CRD defined by the Kubernetes Policy Working Group (WG-Policy). This means other tools (like Falco, Trivy, or UI Dashboards) can also read/write these reports.
  • Scoping: It keeps results close to the application. Developers can check kubectl get policyreport -n my-app to see if their app is compliant, without needing cluster-admin access.

Example output of command get policyreport:

NAME              PASS   FAIL   WARN   ERROR   SKIP   AGE
polr-ns-default   0      1      0      0       0      5m

The PolicyReport YAML output of command: kubectl get policyreport -n my-app polr-ns-default -oyaml

apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
  name: polr-ns-default
  namespace: default   # It lives next to the Pod, not at the cluster level
  labels:
    app.kubernetes.io/managed-by: kyverno
summary:
  pass: 0
  fail: 1
  warn: 0
  error: 0
  skip: 0
results:
  - policy: require-labels           # The name of the ClusterPolicy responsible
    rule: check-for-team-label       # The specific rule name
    category: Best Practices
    severity: medium
    result: fail                     # The outcome (fail, pass, warn, error, skip)
    message: "Validation error: label 'team' is required" 
    source: kyverno
    resources:                       # The specific object that failed
      - apiVersion: v1
        kind: Pod
        name: nginx
        namespace: default
        uid: a1b2c3d4-e5f6...

Validate Rules

This field sits directly under the spec section of your Policy.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  # ---------------------------------------------------------
  # THIS IS THE FIELD
  # Options: 
  #   - Enforce (BLOCK the request)
  #   - Audit   (ALLOW the request, but report it as a fail)
  # ---------------------------------------------------------
  validationFailureAction: Enforce

  background: true
  rules:
    - name: require-image-tag
      match:
        any:
        - resources:
            kinds:
              - Pod
      validate:
        message: "Using the ':latest' tag is not allowed."
        pattern:
          spec:
            containers:
            - image: "!*:latest"

Important Note on Scoping: You can also set this field per rule if you want mixed behaviors in a single policy (available in newer Kyverno versions):

spec:
  validationFailureAction: Audit  # Default for the whole policy
  rules:
    - name: critical-rule
      validationFailureAction: Enforce # This specific rule will BLOCK
      ...
    - name: minor-rule
      # Inherits Audit (ALLOW)
      ...

Example: What happens in "Audit" mode

You apply a "bad" Pod.

$ kubectl apply -f bad-pod.yaml
pod/bad-pod created   <-- Look! It says created.

Kubernetes API Audit Log (file):

{
  "verb": "create",
  "user": "kubernetes-admin",
  "objectRef": { "resource": "pods", "name": "bad-pod" },
  "responseStatus": { "code": 201 }  <-- Success! No mention of error.
}

Kyverno Policy Report (Where the truth is):

$ kubectl get policyreports
NAME              FAIL
polr-ns-default   1     <-- Here is your violation log.

So, simple summary:

  • Enforce: Stops the bad thing from entering.
  • Audit: Lets the bad thing in, but writes a ticket (PolicyReport) so you can fix it later. It does not rely on the standard API Server audit log for reporting violations.

Background Scans

Document: https://kyverno.io/docs/policy-reports/background/

It's purpose: Periodically checking existing resources in the cluster against policies to detect violations. It will just create a ticket (PolicyReport).

By default, Kyverno never deletes an existing resource during a Background Scan, even if your policy is set to Enforce. This is a safety mechanism to prevent accidentally killing production workloads.

What if I want to delete pods that violated policies? You will need Cleanup policy: https://kyverno.io/docs/policy-types/cleanup-policy/

Example: "Delete any Pod older than 7 days"

apiVersion: kyverno.io/v2beta1
kind: ClusterCleanupPolicy
metadata:
  name: cleanup-old-pods
spec:
  match:
    any:
    - resources:
        kinds:
          - Pod
  conditions:
    all:
    - key: "{{ time_since('', request.object.metadata.creationTimestamp, '') }}"
      operator: GreaterThan
      value: "168h" # 7 days
  schedule: "*/5 * * * *" # Run check every 5 mins

Example: ClusterCleanupPolicy that will automatically DELETE any Pod that is missing the team label.

apiVersion: kyverno.io/v2beta1
kind: ClusterCleanupPolicy
metadata:
  name: delete-pods-missing-team-label
spec:
  # 1. SCHEDULE
  # Run this check every 5 minutes
  schedule: "*/5 * * * *"

  # 2. TARGET RESOURCES
  match:
    any:
    - resources:
        kinds:
          - Pod
        namespaces:
          - default  # SAFEGUARD: Only delete pods in 'default' for now

  # 3. DELETE LOGIC (JMESPath)
  # "If this condition is TRUE, then DELETE the resource."
  conditions:
    all:
    - key: "{{ request.object.metadata.labels.team || '' }}"
      operator: Equals
      value: ""  
      # Logic: If the 'team' label is missing (empty), it matches this rule -> DELETE.

When not to use Background Scan or background: false. When policy use following variables

  • request.userInfo.*
  • request.operation
  • request.dryRun
  • serviceAccountName in admission context

Because background scan doesn't has admission request context, only resource data!

What is failurePolicy?

This setting defines how Kubernetes should react if the Kyverno controller is DOWN (crashed, timed out, or unreachable).

It determines whether the cluster should "Fail Closed" (Block everything) or "Fail Open" (Let everything pass) during a system outage.

Fail (Default):

  • Behavior: If Kyverno doesn't respond -> BLOCK the API request.
  • Pro: Maximum Security (nothing bypasses policy).
  • Con: If Kyverno crashes, your cluster might stop accepting changes (no new Pods).

Ignore:

  • Behavior: If Kyverno doesn't respond -> ALLOW the API request.
  • Pro: High Availability (cluster keeps working even if Kyverno dies).
  • Con: Security Risk (policies are temporarily skipped).

Example:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: critical-policy-with-fail-open
spec:
  # -------------------------------------------------------------
  # ACTION: If Kyverno is dead, just let the request go through.
  # -------------------------------------------------------------
  failurePolicy: Ignore 

  # This is the logic rule (Enforce/Audit)
  validationFailureAction: Enforce

  rules: 
    - ...

Relationship between Kyverno and Admission Controller

Think of Kyverno as a specialized, programmable Admission Controller.

  • Admission Controller: The "Police Station" built into Kubernetes.
  • Kyverno: A specific "Officer" you hire working at that station, who follows the instructions (Policies) you write in YAML.

You use Kyverno TO implement Admission Control. Instead of writing a custom Go webhook server to validate your labels, you just apply a Kyverno YAML.

Kyverno CLI

  • Checks if resources pass or fail a policy.
kyverno apply policy.yaml --resource pod.yaml
  • Runs structured tests defined in a kyverno-test.yaml file.
kyverno test ./tests
  • Tests a specific JMESPath expression against JSON input.
kyverno jp query -i object.json 'metadata.labels'
  • Checks if your policy YAML is written correctly.
kyverno validate policy.yaml
  • Shows the installed CLI version.
kyverno version

What is External Data Source?

Purpose: It allows you to load data from outside into a variable before the rule logic (validate/mutate) runs.

Document: https://main.kyverno.io/docs/policy-types/cluster-policy/external-data-sources/

In order to consume data from a ConfigMap in a rule, a context is required... The context data can then be referenced in the policy rule using JMESPath notation.

Kyverno supports 3 main data sources in context:

  • Kubernetes Resources (via API Call): Look up existing data in the cluster (e.g., ConfigMaps, Secrets, Services).
  • External APIs: Make an HTTP call to a service outside the cluster.
  • Image Registry: Fetch metadata about a container image (e.g., image size, architecture).

Scenario: You want to enforce a rule that "Pods can only use roles defined in a shared ConfigMap".

apiVersion: v1
kind: ConfigMap
metadata:
  name: allowed-roles-cm
  namespace: default
data:
  roles: '["frontend", "backend", "cache"]'
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: validate-role-from-configmap
spec:
  validationFailureAction: Enforce
  rules:
    - name: check-role
      match:
        any:
        - resources:
            kinds:
              - Pod
      # ---------------------------------------------------------
      # CONTEXT: Load data from ConfigMap into variable "allowed_list"
      # ---------------------------------------------------------
      context:
        - name: allowed_list
          apiCall:
            # We call the K8s API to fetch the ConfigMap
            urlPath: "/api/v1/namespaces/default/configmaps/allowed-roles-cm"
            jmesPath: "data.roles" # Extract just the roles list
      validate:
        message: "Invalid Role! It must be in the allowed list."
        # Use the variable {{ allowed_list }} we just loaded
        deny:
          conditions:
            all:
            - key: "{{ request.object.metadata.labels.role }}"
              operator: AnyNotIn
              value: "{{ allowed_list }}"
  • We apply manifest:
apiVersion: v1
kind: Pod
metadata:
  name: database-pod
  labels:
    # This is the label being checked by the policy
    role: database
spec:
  containers:
  - name: db-container
    image: postgres:15
    ports:
    - containerPort: 5432
  • Kyverno Logic: Checks metadata.labels.role ("database").
  • Comparison: "database" is NOT in the allowed list ["frontend", "backend", "cache"]
  • Result:
Error from server: error when creating "pod-request.yaml": admission webhook "validate.kyverno.svc-fail" denied the request:

validate-role-from-configmap:
  check-role: Role is not allowed!

Extra example for API Call in external data source

context:
  - name: podCount
    apiCall:       # <--- Generic field for ANY Kubernetes resource
      urlPath: "/api/v1/namespaces/{{request.namespace}}/pods"
      jmesPath: "items | length(@)"

Default Port in Kyverno

Here is the list of default ports used by Kyverno:

Webhook Server Port 9443 (HTTPS/TCP):

  • Purpose: This is the secure port where the Kubernetes API Server sends Admission Requests to Kyverno.
  • Usage: It handles policy validation and mutation logic.

Metrics Port 8000 (HTTP/TCP):

  • Purpose: Exposes Prometheus metrics.
  • Usage: Scraped by Prometheus at /metrics to monitor policy execution counts, latency, and rule results.

Probes / Health Check Port 8080 (HTTP/TCP):

  • Purpose: Used for Liveness and Readiness probes.
  • Usage: The Kubelet checks /health/liveness and /health/readiness on this port to ensure the Pod is healthy.

Pprof (Profiler) Port 6060 (HTTP/TCP): (Optional):

  • Purpose: Used for debugging and profiling Go performance.
  • Usage: Disabled by default. Only open if you specifically enable profiling flags for debugging.

Summary: The most critical port is 9443, as that is the main communication line between Kubernetes and Kyverno.

Kyverno Mutate - JSON Patch

patchesJson6902 is a mutation method used when you need surgical precision to modify a resource.

Unlike the standard patchStrategicMerge (which merges YAMLs together), this uses specific operations (RFC 6902 standard) to tell Kyverno exactly how to change the data.

Example:

mutate:
  patchStrategicMerge:
    spec:
      containers:
        - (name): "*"
          resources:
            limits:
              memory: "256Mi"

When to use it?

Use it when patchStrategicMerge cannot do the job, specifically for:

  • Removing a field (impossible with standard merge).
  • Adding an item to a specific position in a list (arrays).
  • Replacing a value entirely without merging.

It follows the JSON Patch format:

  • op: The action (add, remove, replace).
  • path: The location of the field (e.g., /metadata/labels/mytag).
  • value: The data to put there.

Example: "Add a sidecar container"

This adds a new container to the start of the containers list (index 0).

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-sidecar
spec:
  rules:
  - name: inject-sidecar
    match:
      any:
      - resources:
          kinds:
          - Pod
    mutate:
      # Note the |- block scalar, this expects a JSON/YAML list string
      patchesJson6902: |-
        - op: add
          path: /spec/containers/0
          value:
            name: my-sidecar
            image: alpine:latest

Generate Rules Example

generate:
  synchronize: true  # Auto-update when source changed
  kind: NetworkPolicy
  name: default-deny
  namespace: "{{request.object.metadata.name}}"
  data:
    spec:
      podSelector: {}
      policyTypes:
        - Ingress
        - Egress

Explain for flows:

  • User create Namespace "kienlt-vip-pro"
  • Kyverno trigger match and generate NetworkPolicy
  - name: default-deny
  - namespace: dev-team-a  ← get from request.object.metadata.name
  - Block all Ingress/Egress
  • Result: New namespace will have NetworkPolicy deny-all. Actual resource created
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: kienlt-vip-pro    # from request
spec:
  podSelector: {}          # all pods
  policyTypes:
    - Ingress              # block incoming
    - Egress               # block outgoing

Explain for synchronize:

  • synchronize: true: Kyverno complete managed lifecycle
  • synchronize: false: Kyverno created for the first time then user can edit it manually without getting revert back like synchronize: true

This is example for pattern zero-trust by default!

VerifyImages Example

verifyImages:
  - imageReferences:
      - "ghcr.io/myorg/*"
    attestors:
      - entries:
          - keys:
              publicKeys: |-
                -----BEGIN PUBLIC KEY-----
                ...
                -----END PUBLIC KEY-----

Foreach - Advanced Mutation

mutate:
  foreach:
    - list: "request.object.spec.containers"
      patchStrategicMerge:
        spec:
          containers:
            - name: "{{ element.name }}"
              securityContext:
                readOnlyRootFilesystem: true

More for JMESPath which will appear a lot in exam xD

Filtering (Critical)

The exam often asks: "Block the Pod if ANY container uses an image with the 'latest' tag" or "Find containers that do not have memory limits set".

You must use the [?expression] syntax.

Syntax: request.object.spec.containers[? filter_condition ]

Example: Find all containers where the image starts with "nginx":

# Returns a list of container objects that match the condition
{{ request.object.spec.containers[?starts_with(image, 'nginx')] }}

Kyverno Application (Deny if found):

validate:
  message: "Nginx images are not allowed!"
  deny:
    conditions:
      all:
      # Filter the list. Use length() to count. 
      # If count > 0, it means a violation exists -> Block.
      - key: "{{ request.object.spec.containers[?starts_with(image, 'nginx')] | length(@) }}"
        operator: GreaterThan
        value: 0

Pipe Operator | (Projection)

Used to extract a specific field from a complex object into a flat list.

Example: You don't want the whole container object; you just want a list of image names.

# Input: List of container objects
# Output: ["nginx:latest", "redis:alpine", "busybox"]
key: "{{ request.object.spec.containers[].image }}"

Exam Combo (Filter + Pipe): "Get the containerPort of the container named 'app'" request.object.spec.containers[?name == 'app'].ports[].containerPort

Null Handling (The Exam Trap)

This is where many people fail. If a field does not exist in the YAML, JMESPath returns null, causing the Policy to crash or behave unexpectedly.

Use the || (OR) operator to set a default value.

Example: Check the team label. What if the Pod has no labels at all?

  • Risk: {{ request.object.metadata.labels.team }} (If null -> Error).
  • Safe: {{ request.object.metadata.labels.team || '' }} (If null -> treat as empty string).

Kyverno Application:

preconditions:
  all:
  - key: "{{ request.object.metadata.labels.team || 'no-team' }}"
    operator: Equals
    value: "production"

The length() function

Used to limit the quantity of a resource.

Example: "A Pod must not have more than 3 containers."

validate:
  deny:
    conditions:
      all:
      - key: "{{ request.object.spec.containers | length(@) }}"
        operator: GreaterThan
        value: 3

Sample JMESPath questions

Scenario: You are writing a policy against the following Pod JSON. Analyze this JSON and answer the 3 questions below.

{
  "apiVersion": "v1",
  "kind": "Pod",
  "metadata": {
    "name": "backend-app",
    "labels": {
      "app": "backend",
      "env": "production"
    }
  },
  "spec": {
    "containers": [
      {
        "name": "main-app",
        "image": "my-org/app:v1",
        "ports": [
          { "containerPort": 8080, "protocol": "TCP" }
        ]
      },
      {
        "name": "sidecar-logger",
        "image": "fluentd:latest",
        "ports": []
      }
    ],
    "volumes": [
      { "name": "config", "configMap": { "name": "app-conf" } },
      { "name": "data", "emptyDir": {} }
    ]
  }
}

Question Basic Selection: You need to extract the name of the first container in the list

  • Answer:
spec.containers[0].name
  • Explanation: In JMESPath, you access array items by index using brackets [0].

Question Filtering & Counting: You want to check how many containers are using the latest tag. Which JMESPath expression returns the count (integer)?

  • Answer:
spec.containers[?contains(image, 'latest')] | length(@)
  • Explanation:
- spec.containers[...]: Access the list
- [?contains(image, 'latest')]: Filter the list to keep only objects where the image string contains "latest".
- | length(@): Pipe the result to the length function to count them.

Complex Projection: You want to find the names of all volumes that are of type emptyDir. Which expression returns ["data"]?

  • Answer:
spec.volumes[?emptyDir != null].name
  • Explanation:
1. We need to check if the field emptyDir exists.
2. In the first volume ("config"), emptyDir is missing (null).
3. In the second volume ("data"), emptyDir exists (it is an object {}).
4. [?emptyDir != null] filters the list to keep only volumes that have this field.
5. .name extracts the name from the remaining items.
6. Some JMESPath implementations allow [?emptyDir] as a shorthand for "not null", but != null is the strict standard used in many exams.

Kyverno JP CLI examples xD

# object.json
{
  "apiVersion": "v1",
  "kind": "Pod",
  "metadata": {
    "name": "web-app",
    "namespace": "production",
    "labels": {
      "app": "nginx",
      "env": "production",
      "team": "platform"
    },
    "annotations": {
      "contact-email": "team@example.com"
    }
  },
  "spec": {
    "containers": [
      {
        "name": "nginx",
        "image": "nginx:1.25",
        "ports": [
          { "containerPort": 80, "protocol": "TCP" },
          { "containerPort": 443, "protocol": "TCP" }
        ],
        "resources": {
          "limits": { "memory": "256Mi", "cpu": "500m" }
        }
      },
      {
        "name": "sidecar",
        "image": "fluentd:latest",
        "ports": []
      }
    ],
    "volumes": [
      { "name": "config", "configMap": { "name": "app-config" } },
      { "name": "data", "emptyDir": {} },
      { "name": "secret", "secret": { "secretName": "app-secret" } }
    ]
  }
}

Basic Selection:

# Get Pod Name
kyverno jp query -i object.json 'metadata.name'
# Output: "web-app"

# Get namespace
kyverno jp query -i object.json 'metadata.namespace'
# Output: "production"

# Get all labels
kyverno jp query -i object.json 'metadata.labels'
# Output: {"app":"nginx","env":"production","team":"platform"}

# Get specific label 
kyverno jp query -i object.json 'metadata.labels.env'
# Output: "production"

Array Access:

# Get first container
kyverno jp query -i object.json 'spec.containers[0]'
# Output: {"name":"nginx","image":"nginx:1.25",...}

# get first container name
kyverno jp query -i object.json 'spec.containers[0].name'
# Output: "nginx"

# Get all containers name
kyverno jp query -i object.json 'spec.containers[].name'
# Output: ["nginx","sidecar"]

# Get all images
kyverno jp query -i object.json 'spec.containers[].image'
# Output: ["nginx:1.25","fluentd:latest"]

Filtering with [? ]:

# Find container image contains "latest"
kyverno jp query -i object.json "spec.containers[?contains(image, 'latest')]"
# Output: [{"name":"sidecar","image":"fluentd:latest",...}]

# Find container named "nginx"
kyverno jp query -i object.json "spec.containers[?name == 'nginx']"
# Output: [{"name":"nginx","image":"nginx:1.25",...}]

# find volumes has emptyDir
kyverno jp query -i object.json "spec.volumes[?emptyDir != null]"
# Output: [{"name":"data","emptyDir":{}}]

# find volumes has configMap
kyverno jp query -i object.json "spec.volumes[?configMap != null].name"
# Output: ["config"]

Counting with length()

# count containers
kyverno jp query -i object.json 'spec.containers | length(@)'
# Output: 2

# count volumes
kyverno jp query -i object.json 'spec.volumes | length(@)'
# Output: 3

# count containers using "latest" tag
kyverno jp query -i object.json "spec.containers[?contains(image, 'latest')] | length(@)"
# Output: 1

Null Handling with ||

# Label exists
kyverno jp query -i object.json "metadata.labels.env || 'not-set'"
# Output: "production"

# Label not exists - fallback to default
kyverno jp query -i object.json "metadata.labels.tier || 'not-set'"
# Output: "not-set"

# Check annotation exists or not!
kyverno jp query -i object.json "metadata.annotations.description || 'no-description'"
# Output: "no-description"

Combined Examples (Exam-style)

# Get all containerPort of container "nginx"
kyverno jp query -i object.json "spec.containers[?name == 'nginx'].ports[].containerPort"
# Output: [80,443]

# Check if container missing resources.limits!
kyverno jp query -i object.json "spec.containers[?resources.limits == null].name"
# Output: ["sidecar"]

# Get memory limit of the first container
kyverno jp query -i object.json "spec.containers[0].resources.limits.memory || 'not-set'"
# Output: "256Mi"

# Check label "env" ccontains "prod"
kyverno jp query -i object.json "contains(metadata.labels.env || '', 'prod')"
# Output: true

My secret weapons - Mock Exam

Here is the link Udemy Practice Test:

https://www.udemy.com/course/complete-certified-kyverno-associate-kca-exam-prep/

Exam Note

  • Exam duration: 90 minutes
  • Exam questions: 60 questions
  • The exam required 75/100 to pass, and I scored 80/100!

Some extra section that appeared in exam!

  • Need to remember correct flow:
Authn/Authz --> Mutating --> Schema Validation -> Validate --> ETCD
  • Auto Gen feature, remember annotation xD:

https://release-1-8-0.kyverno.io/docs/writing-policies/autogen/

  • TTL Default of Certification in Kyverno:

https://pkg.go.dev/github.com/kyverno/kyverno/pkg/tls

const (
    CAValidityDuration = 365 * 24 * time.Hour      // 365 days
    TLSValidityDuration = 150 * 24 * time.Hour     // 150 days
    CertRenewalInterval = 12 * time.Hour           // 12 hours
)
  • Concurrent Policies Generation Number Default!
  • PolicyException CRD
  • CEL
  • CLI Commands with parameter, yes the fucking parameter you don't heard it wrong!

It is pretty hard to get perfect number even in scenario I take another test for Kyverno since some question is really advanced for me! I only scored 82/100!

Conclusion

  • Understand everything in this article
  • Take exam notes
  • Scored >80 in Mock exams. 4 Exams in totals

Good luck!!!


Published

Category

Knowledge Base

Tags

Contact