Cybersecurity Watch

Demystifying `foreach`: A Practical Guide to Iterative Policy Enforcement in Kyverno

An expert-level analysis of a common `foreach` pitfall in Kyverno, dissecting the root cause and providing a definitive, actionable solution for robust policy enforcement.

Klaudia Chen

Author

5 min read
1 views

The Silent Failure: A Common `foreach` Pitfall

In Kubernetes policy enforcement, silent failures represent a significant threat. Kyverno GitHub issue #14007 recently highlighted a critical example: a user's foreach policy with a deny condition was successfully validated and accepted by the API server, yet it failed to enforce any of its intended rules. This perplexing "silent failure" created a major, undiscovered security vulnerability. The behavior was not a simple bug but rather exposed a fundamental nuance in Kyverno's processing logic, specifically how context is handled within iterative rule execution.

This article dissects this precise issue to reveal the underlying architectural reasons for the policy's inertness. We will explore why a syntactically valid policy can fail to trigger and, most importantly, provide a clear, authoritative solution. By understanding the mechanics behind this failure, you will learn to write effective, predictable foreach policies, ensuring your intended denials are always enforced and avoiding this critical pitfall.

Why `foreach` Exists: The Developer Experience Imperative

Kyverno's foreach construct embodies the design principle of "making the right way the easy way," a philosophy championed by cloud-native thought leader Kelsey Hightower. It simplifies policy authoring by abstracting away complex array manipulations, enabling a natural "for each container, ensure X" mindset. Instead of wrestling with intricate JMESPath queries to iterate over lists like spec.containers[], foreach handles the iteration implicitly.

This approach dramatically improves policy readability and maintainability. By lowering the technical barrier, foreach makes security and configuration policies more accessible to the entire engineering team, not just a few specialists. The result is a more collaborative and reliable policy-as-code workflow, where intent is clear and enforcement is predictable.

Under the Hood: Sub-Rule Generation and Context Failure

Kyverno processes a foreach loop by dynamically generating temporary, in-memory sub-rules for each element in the target array. For a policy designed to deny privileged containers, a user wrote the following syntactically valid rule:

rules:
- name: check-privileged-containers
  match:
    resources: { kinds: [Pod] }
  foreach:
  - list: "request.object.spec.containers"
    deny:
      conditions:
        any:
        - key: "{{ element.securityContext.privileged }}"
          operator: Equals
          value: true

This policy failed silently due to a subtle breakdown in context propagation. The deny condition's evaluation logic did not correctly interpret the {{ element }} context variable supplied by the foreach iterator. Consequently, the engine could not resolve the dynamic path to each container's securityContext.

Log analysis from the Kyverno pod confirmed this failure mechanism. For every generated sub-rule—one for each container—the engine attempted to validate the exact same static JSON path: /spec/template/spec/containers/0/securityContext/. The iteration context was lost, causing the engine to default to the first element's path ([0]) for all subsequent checks. This rendered the rule inert for any container beyond the first, creating a critical security blind spot. This type of context loss is particularly severe for rule properties that depend on dynamic path resolution, as they cannot locate the correct data for evaluation.

Visualizing the Processing Failure
Visualizing the Processing Failure

The Definitive Solution: Correct Implementation with `pattern` and CEL

The definitive solution is to use a validate rule with a pattern or cel block, as both are designed to correctly operate within the foreach context.

The recommended best practice using pattern is:

rules:
- name: check-privileged-containers-pattern
  match:
    any:
    - resources:
        kinds:
        - Pod
        - Deployment
        - StatefulSet
        - DaemonSet
        - Job
  validate:
    message: "Privileged containers are not allowed."
    foreach:
    - list: "request.object.spec.template.spec.containers"
      pattern:
        securityContext:
          privileged: false

This policy works reliably for the following reasons:

  • list: "request.object.spec.template.spec.containers": This JMESPath correctly targets the container list in Pods and workload controllers (like Deployments or StatefulSets) that embed a Pod template. Using request.object ensures the path is resolved against the complete incoming resource, providing broad applicability.
  • pattern: ...: Unlike the deny condition, the pattern block is architected to work with foreach. It implicitly applies its structure against each element passed by the iterator. Kyverno understands that the pattern must match the structure of an individual container object, avoiding the context-loss failure entirely. The check privileged: false is correctly evaluated for every container in the list.

An equally valid and powerful best practice is to use a validate.cel rule. Common Expression Language (CEL) provides a concise and efficient way to express the same logic:

rules:
- name: check-privileged-containers-cel
  match:
    any:
    # ... same match block as above ...
  validate:
    message: "Privileged containers are not allowed."
    foreach:
    - list: "request.object.spec.template.spec.containers"
      cel:
        expression: "element.securityContext.privileged == false"

Here, the CEL expression directly accesses the element variable provided by the foreach loop. The expression element.securityContext.privileged == false is evaluated for each container, providing a clear, explicit, and reliable validation that is functionally identical to the pattern approach.

Practical Applications and Best Practices

The silent failure of deny within a foreach loop underscores a critical lesson: policy constructs must be chosen based on their specific design and context-handling capabilities. This principle extends to other common use cases, such as enforcing a trusted image registry. To achieve this, a single validate rule can contain two foreach blocks. The first iterates over request.object.spec.template.spec.containers and the second over request.object.spec.template.spec.initContainers. Within each, a pattern block with image: "my-registry.io/*" ensures every container, regardless of its type, uses an approved image source. This approach is robust and avoids the context-loss pitfalls seen with deny.

By internalizing the mechanics of foreach processing, you can write policies that are clear, maintainable, and—most importantly—effective. To build resilient and predictable policies, adhere to these critical best practices:

  • Always use validate with a pattern or cel block for conditional logic inside a foreach loop. These constructs are architected to correctly receive the element context from the iterator, whereas deny conditions can fail silently.
  • Use request.object in the list path (e.g., request.object.spec.template.spec.containers) to ensure your policy is compatible with both standalone resources (like Pods) and workload controllers (like Deployments) that use pod templates.
  • Test policies against multiple, distinct resource kinds (e.g., Pod, Deployment, StatefulSet) to verify that JMESPath expressions resolve correctly and the rule behaves as intended across all relevant targets.
  • When debugging, remember that Kyverno generates temporary sub-rules for each item in the list. Check Kyverno pod logs for the evaluation status of each individual sub-rule to pinpoint context-loss failures or other iteration-specific issues.
Klaudia Chen

Written by

Klaudia Chen

Recommended

Stay Connected

Showcase Your Brand

Connect with our engaged tech audience. Explore impactful advertising opportunities tailored for you.

Inquire Now