GeneratingPolicy

Dynamically Create and Synchronize Resources Across Namespaces.

The GeneratingPolicy enables the creation of Kubernetes resources using Common Expression Language (CEL) expressions. It provides the same core functionality as Kyverno’s generate rules, but is designed with a CEL-first approach for improved flexibility, expressiveness, and alignment with Kubernetes’ evolving policy standards.

By leveraging CEL, GeneratingPolicy aligns with native Kubernetes constructs like ValidatingAdmissionPolicy and MutatingAdmissionPolicy, providing a consistent and powerful experience.

Key Benefits:

  • Declarative Generation: Define and manage resources using clear, concise CEL expressions directly within your policies.

  • Powerful Cloning: Easily clone existing resources like Secrets and ConfigMaps across namespaces using CEL-based accessors.

  • Robust Synchronization: Keep generated resources in sync with changes to the policy, the trigger, or the source resource.

  • Looping: Replicate the functionality of forEach loops to generate resources based on lists.

  • Kubernetes-Native Alignment: Uses a familiar, CEL-based approach that integrates smoothly with the broader Kubernetes ecosystem.

Core Concepts

Understanding these terms is key to working with GeneratingPolicy.

TermDescription
TriggerThe resource that initiates the generation process. It is defined by the matchConstraints in the policy.
DownstreamThe resource(s) generated by the policy as a result of a trigger event.
SourceAn existing resource that is used as a template for cloning. It is fetched using resource.Get or resource.List.

A GeneratingPolicy is composed of several key fields within its spec.

FieldDescription
evaluationConfigures the policy’s runtime behavior, including synchronize, generateExisting, and orphanDownstreamOnPolicyDelete.
matchConstraintsDefines the Trigger resource. The policy activates when a resource matching these rules is created or updated.
matchConditionsOptional CEL expressions that further filter the trigger resources. The policy only runs if these conditions are met.
variablesA list of named CEL expressions that can be reused throughout the policy, simplifying complex logic.
generateA list of CEL expressions that execute the generation logic, typically using the generator.Apply() function.

Data Source

The Data Source mode allows users to define the downstream resources directly within the policy using CEL expressions.

When to Use: Ideal for generating fixed or templated resources, such as default NetworkPolicies, RoleBindings, or configuration ConfigMaps, in response to a trigger (e.g., a new Namespace).

The below policy demonstrates how to generate a ConfigMap in response to a Namespace creation:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: zk-kafka-address
 5spec:
 6  evaluation:
 7    synchronize:
 8      enabled: true
 9  matchConstraints:
10    resourceRules:
11    - apiGroups:   [""]
12      apiVersions: ["v1"]
13      operations:  ["CREATE"]
14      resources:   ["namespaces"]
15  variables:
16    - name: nsName
17      expression: "object.metadata.name"
18    - name: downstream
19      expression: >-
20        [
21          {
22            "kind": dyn("ConfigMap"),
23            "apiVersion": dyn("v1"),
24            "metadata": dyn({
25              "name": "zk-kafka-address",
26              "namespace": string(variables.nsName),
27            }),
28            "data": dyn({
29              "KAFKA_ADDRESS": "192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092",
30              "ZK_ADDRESS": "192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181"
31            })
32          }
33        ]
34  generate:
35    - expression: generator.Apply(variables.nsName, variables.downstream)

This policy is triggered whenever a Namespace is created. It captures the Namespace’s name in nsName and defines the full ConfigMap object in downstream. Then, the generator.Apply() function creates the ConfigMap defined in downstream inside the target namespace.

Clone Source

This mode copies an existing Kubernetes resource (the source) to create a new resource (the downstream). It makes use of resource.Get() or resource.List() to fetch source resources and clone them into the target namespace using generator.Apply().

When to Use: Perfect for distributing common resources like image pull secrets (regcred), TLS certificates, or standardized NetworkPolicies from a central namespace to other namespaces.

Clone Single Resource

To clone a single resource, you can use resource.Get() to fetch the source resource and then apply it in the target namespace.

The following example demonstrates how to clone a Secret from the default namespace to the target namespace:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: generate-secret
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8    - apiGroups:   [""]
 9      apiVersions: ["v1"]
10      operations:  ["CREATE", "UPDATE"]
11      resources:   ["namespaces"]
12  variables:
13    - name: nsName
14      expression: "object.metadata.name"
15    - name: source
16      expression: resource.Get("v1", "secrets", "default", "regcred")
17  generate:
18    - expression: generator.Apply(variables.nsName, [variables.source])

This policy is triggered whenever a Namespace is created. It captures the trigger’s namespace name in nsName and uses resource.Get() to fetch the regcred Secret from the default namespace, storing it in source. Then, the generator.Apply() takes the fetched secret and creates a copy of it in the new namespace.

Clone Multiple Resources

To clone multiple resources, you can use resource.List() to fetch a list of resources and then apply them in the target namespace. The following example demonstrates how to clone all Secrets from the default namespace:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: generate-secrets
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8    - apiGroups:   [""]
 9      apiVersions: ["v1"]
10      operations:  ["CREATE", "UPDATE"]
11      resources:   ["namespaces"]
12  variables:
13    - name: nsName
14      expression: "object.metadata.name"
15    - name: sources
16      expression: resource.List("v1", "secrets", "default")
17  generate:
18    - expression: generator.Apply(variables.nsName, [variables.sources])

This policy is triggered whenever a Namespace is created. It captures the trigger’s namespace name in nsName and uses resource.List() to fetch all Secrets from the default namespace, storing them in sources. The generator.Apply() then creates copies of all fetched Secrets in the new namespace.

Looping

GeneratingPolicy supports forEach-style logic using CEL’s list processing capabilities. This is a powerful technique for generating multiple resources based on a dynamic list.

When to Use: When you need to generate resources across a list of namespaces defined in a central configuration, or create multiple, varied resources based on items in a list.

Looping with a Data Source

The below policy creates a NetworkPolicy into a list of existing namespaces which is stored as a comma-separated string in a ConfigMap.

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: generate-network-policy
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8    - apiGroups:   [""]
 9      apiVersions: ["v1"]
10      operations:  ["CREATE"]
11      resources:   ["configmaps"]
12  variables:
13    - name: nsList
14      expression: "object.data.namespaces.split(',')"
15    - name: downstream
16      expression: >-
17        [
18          {
19            "kind": dyn("NetworkPolicy"),
20            "apiVersion": dyn("networking.k8s.io/v1"),
21            "metadata": dyn({
22              "name": "test-network-policy",
23            }),
24            "spec": dyn({
25              "podSelector": dyn({}),
26              "policyTypes": dyn(["Ingress", "Egress"])
27            })
28          }
29        ]
30  generate:
31    - expression: >
32        variables.nsList.all(ns, generator.Apply(ns, variables.downstream))

This policy is triggered whenever a ConfigMap is created. It captures the list of namespaces from the ConfigMap’s data field, splits it into a list, and stores it in nsList and defines the full NetworkPolicy object in downstream. It then uses variables.nsList.all(ns, ...) to iterate over each namespace in nsList, generating the NetworkPolicy defined in downstream to each namespace.

Sometimes, you need to create uniquely named resources during a loop. By creating a list of indexes, you can use the index to construct dynamic names and reference items from the original list.

The policy below creates a uniquely named NetworkPolicy (e.g., np-0, np-1) in each namespace defined in the ConfigMap.

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: generate-network-policy
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8    - apiGroups:   [""]
 9      apiVersions: ["v1"]
10      operations:  ["CREATE"]
11      resources:   ["configmaps"]
12  variables:
13    # nsList becomes ["ns1", "ns2", "ns3"]
14    - name: nsList
15      expression: "object.data.namespaces.split(',')"
16    # indexed becomes [0, 1, 2]
17    - name: indexed
18      expression: >
19        variables.nsList.map(ns, variables.nsList.indexOf(ns))
20  generate:
21    - expression: >
22        variables.indexed.all(i, generator.Apply(variables.nsList[i], [
23          {
24            "kind": dyn("NetworkPolicy"),
25            "apiVersion": dyn("networking.k8s.io/v1"),
26            "metadata": dyn({
27              "name": "np-" + string(i),
28            }),
29            "spec": dyn({
30              "podSelector": dyn({}),
31              "policyTypes": dyn(["Ingress", "Egress"])
32            })
33          }]
34        ))

This policy defines two variables:

  1. nsList: Reads the namespaces key from the trigger ConfigMap and splits the comma-separated string into a list of names (e.g., [’ns1’, ’ns2’, ’ns3’]).

  2. indexed: Creates a new list containing the numerical index of each item from nsList. For an nsList of [’ns1’, ’ns2’, ’ns3’], the indexed list becomes [0, 1, 2]. This provides a unique, stable number for each namespace.

Then, the generate block does the following:

  1. variables.indexed.all(i, ...): This CEL expression iterates through each number i in the indexed list. The loop variable i holds the current index (0, then 1, then 2).

  2. generator.Apply(variables.nsList[i], ...): Inside the loop, this function generates the resource.

    • It uses variables.nsList[i] to get the target namespace (e.g., when i is 1, it gets ns2).
    • It dynamically creates the NetworkPolicy name by appending the index i (e.g., "np-" + string(1) results in the name np-1).

This approach allows you to generate uniquely named resources in each namespace defined in the ConfigMap, ensuring that each NetworkPolicy is distinct and correctly associated with its target namespace.

There might be cases where you want to apply a filter to the list before generating resources.

The following policy combines filtering and indexing to generate resources for specific, pre-approved namespaces while maintaining their original index for naming consistency.

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: generate-network-policy
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8    - apiGroups:   [""]
 9      apiVersions: ["v1"]
10      operations:  ["CREATE", "UPDATE"]
11      resources:   ["configmaps"]
12  variables:
13    # nsList is ["filtered-ns-1", "filtered-ns-2", "filtered-ns-3"]
14    - name: nsList
15      expression: "object.data.namespaces.split(',')"
16    # filteredList is ["filtered-ns-1", "filtered-ns-3"]
17    - name: filteredList
18      expression: "variables.nsList.filter(ns, ['filtered-ns-1', 'filtered-ns-3'].exists(v, v == ns))"
19    # indexed is [0, 2]
20    - name: indexed
21      expression: >
22        variables.filteredList.map(ns, variables.nsList.indexOf(ns))
23  generate:
24    - expression: >
25        variables.indexed.all(i, generator.Apply(variables.nsList[i], [
26          {
27            "kind": dyn("NetworkPolicy"),
28            "apiVersion": dyn("networking.k8s.io/v1"),
29            "metadata": dyn({
30              "name": "filtered-np-" + string(i),
31            }),
32            "spec": dyn({
33              "podSelector": dyn({}),
34              "policyTypes": dyn(["Ingress", "Egress"])
35            })
36          }]
37        ))

This policy defines three variables:

  1. nsList: Reads the namespaces key from the trigger ConfigMap and splits it into the full source list. Example: [‘filtered-ns-1’, ‘filtered-ns-2’, ‘filtered-ns-3’].

  2. filteredList: This is the key step. It uses filter() to create a new list containing only the namespaces from nsList that also exist in a hardcoded “allow list” (['filtered-ns-1', 'filtered-ns-3']). The result is ['filtered-ns-1', 'filtered-ns-3'].

  3. indexed: This variable iterates over the filteredList but finds the original index of each item from the unfiltered nsList. The result is a list of the original indexes of the allowed items: [0, 2].

The generate block then uses these variables to create NetworkPolicies in the filtered namespaces:

  1. variables.indexed.all(i, ...): The loop iterates over the indexed list, so i will be 0 and then 2.

  2. generator.Apply(variables.nsList[i], ...): Inside the loop, it uses the index i to target the correct namespace from the original list and to create a uniquely named resource.

    • When i is 0, it creates filtered-np-0 in namespace filtered-ns-1.
    • When i is 2, it creates filtered-np-2 in namespace filtered-ns-3.
    • Namespace filtered-ns-2 is skipped entirely because it was not in the filteredList.

This approach allows you to generate resources only in specific namespaces while maintaining a consistent naming scheme based on their original index in the unfiltered list.

Looping with a Clone Source

When cloning resources, you can also use looping to generate multiple resources based on a list of items. This is particularly useful when you want to clone a resource for each item in a list.

The following policy demonstrates how to clone a Secret for each namespace defined in a ConfigMap:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: generate-secrets
 5spec:
 6  matchConstraints:
 7    resourceRules:
 8    - apiGroups:   [""]
 9      apiVersions: ["v1"]
10      operations:  ["CREATE", "UPDATE"]
11      resources:   ["configmaps"]
12  variables:
13    - name: nsList
14      expression: "object.data.namespaces.split(',')"
15    - name: source
16      expression: resource.Get("v1", "secrets", "default", "regcred")
17  generate:
18    - expression: >
19        variables.nsList.all(ns, generator.Apply(ns, [variables.source]))

This policy is triggered whenever a ConfigMap is created. It captures the list of namespaces from the ConfigMap’s data field, splits it into a list, and stores it in nsList. It also fetches the regcred Secret from the default namespace and stores it in source. The generate block then uses variables.nsList.all(ns, ...) to iterate over each namespace in nsList, generating a copy of the regcred Secret in each namespace.

You can also clone multiple resources for each item in the list. The following example demonstrates how to clone all Secrets from the default namespace into each namespace defined in the ConfigMap:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: foreach-clone-list-sync-create
 5spec:
 6  evaluation:
 7    synchronize:
 8      enabled: true
 9  matchConstraints:
10    resourceRules:
11    - apiGroups:   [""]
12      apiVersions: ["v1"]
13      operations:  ["CREATE", "UPDATE"]
14      resources:   ["configmaps"]
15  variables:
16    - name: nsList
17      expression: "object.data.namespaces.split(',')"
18    - name: sources
19      expression: resource.List("v1", "secrets", "default")
20  generate:
21    - expression: >
22        variables.nsList.all(ns, generator.Apply(ns, [variables.sources]))

This policy is triggered whenever a ConfigMap is created. It captures the list of namespaces from the ConfigMap’s data field, splits it into a list, and stores it in nsList. It also fetches all Secrets from the default namespace and stores them in sources. The generate block then uses variables.nsList.all(ns, ...) to iterate over each namespace in nsList, generating copies of all fetched Secrets in each namespace.

Synchronization

Synchronization ensures that downstream resources remain consistent with their source. The source can be the policy itself (for data source) or a cloned resource (for clone source). When synchronization is enabled, Kyverno monitors for changes and automatically updates or deletes downstream resources as needed.

This is configured using the evaluation.synchronize.enabled: true field.

Synchronization with a Data Source

When synchronize is enabled for a data source, the lifecycle of the downstream resource is tied to the policy and the trigger. For example, if you modify the policy, the generated resource is updated. If you delete the trigger, the generated resource is deleted.

The following table shows the behavior of deletion and modification events on the components of a GeneratingPolicy with a data source declaration.

ActionSync EffectNoSync Effect
Delete DownstreamDownstream recreatedDownstream deleted
Delete Rule/PolicyDownstream retained
orphanDownstreamOnPolicyDelete.enabled: true
Downstream retained
Delete Rule/PolicyDownstream deleted
orphanDownstreamOnPolicyDelete.enabled: false
Downstream retained
Delete TriggerDownstream deletedNone
Modify DownstreamDownstream revertedDownstream modified
Modify Rule/PolicyDownstream syncedDownstream unmodified
Modify TriggerDownstream deletedNone

The evaluation.orphanDownstreamOnPolicyDelete property can be used to preserve generated resources on policy deletion when synchronization is enabled. The default is false. When set to true, the generated resources will be retained (orphaned) in the cluster after the policy that created them is deleted.

Synchronization with a Clone Source

For clone source mode, synchronization keeps the downstream copies in sync with the original source resource. If the source resource is updated or deleted, all downstream clones are updated or deleted accordingly.

The following table shows the behavior of events on components of a GeneratingPolicy with a clone source. Note that deleting the policy does not delete the downstream resources, as their lifecycle is primarily tied to the source.

ActionSync EffectNoSync Effect
Delete DownstreamDownstream recreatedDownstream deleted
Delete Rule/PolicyDownstream retainedDownstream retained
Delete SourceDownstream deletedDownstream retained
Delete TriggerDownstream deletedNone
Modify DownstreamDownstream revertedDownstream modified
Modify Rule/PolicyDownstream unmodifiedDownstream unmodified
Modify SourceDownstream syncedDownstream unmodified
Modify TriggerDownstream deletedNone

Generating for Existing Resources

By default, a GeneratingPolicy triggers only for CREATE, UPDATE and DELETE events on resources that match its rules. This means if you apply a policy to a cluster that already contains matching resources (e.g., existing namespaces), the policy will not run for them.

To address this, you can set evaluation.generateExisting.enabled to true. This setting instructs Kyverno to perform a one-time scan for all existing resources that match the trigger rules and apply the generation logic to them when the policy is created.

When to Use: Use this when you need to retroactively apply a generatingpolicy to existing resources in your cluster, such as distributing a standard Secret or NetworkPolicy to all existing Namespaces.

The following policy clones a secret to all new and existing namespaces:

 1apiVersion: policies.kyverno.io/v1alpha1
 2kind: GeneratingPolicy
 3metadata:
 4  name: generate-secrets
 5spec:
 6  evaluation:
 7    generateExisting:
 8      enabled: true
 9  matchConstraints:
10    resourceRules:
11    - apiGroups:   [""]
12      apiVersions: ["v1"]
13      operations:  ["CREATE", "UPDATE"]
14      resources:   ["namespaces"]
15  variables:
16    - name: nsName
17      expression: "object.metadata.name"
18    - name: source
19      expression: resource.Get("v1", "secrets", "default", "regcred")
20  generate:
21    - expression: generator.Apply(variables.nsName, [variables.source])

When you apply this policy to a cluster with, for example, 10 existing namespaces, generateExisting.enabled: true ensures that Kyverno will immediately generate a copy of the regcred secret in each of those 10 namespaces. Without this field, the secret would only be created in namespaces created after the policy was applied.


Last modified July 22, 2025 at 11:10 PM PST: add docs for GeneratingPolicies (#1599) (4d0108d)