Kyverno Generate Policies: Automate Namespace Resources the Right Way
— kubernetes, kyverno, policy, security — 7 min read
Kyverno is a policy engine built for Kubernetes. It runs as an admission controller that validates and mutates incoming resources, and it can also generate new resources in response to events like a namespace being created. Generate rules are how you turn "every new namespace must have a default NetworkPolicy, a ResourceQuota, and an image-pull Secret" into something the cluster enforces for you. This post explains how generate rules work, how to keep generated resources in sync, and the RBAC that trips most people up.
The examples target the current Kyverno v1 policy API (kyverno.io/v1). If you are following an older tutorial, note that the match syntax and the RBAC model changed; this post uses the current form.
TL;DR
- A generate rule creates a resource when a trigger event matches, most commonly a new namespace.
- The source can be inline (
data) or a clone of an existing resource (clone). They are mutually exclusive. synchronize: truemakes the background controller keep the generated resource reconciled: edits are reverted, and deleting it recreates it.- Generation is done by the
kyverno-background-controllerServiceAccount. To generate resource types it does not manage by default, you grant it permissions with an aggregation label. - To generate RBAC (RoleBindings), the controller must already hold the role you are granting, or Kubernetes blocks it as privilege escalation.
The problem generate rules solve
New namespaces usually need a standard set of supporting resources: a NetworkPolicy so pods are not open by default, a ResourceQuota, an image-pull Secret, maybe a RoleBinding. Doing this by hand is error-prone, and doing it with a bash script that loops over namespaces drifts the moment someone creates a namespace outside the script.
Generate rules move that logic into the cluster. You declare the desired resource once in a policy, and Kyverno creates it whenever a matching trigger appears, and optionally keeps it in sync forever.
How a generate rule works
A generate rule has three parts: a match block that defines the trigger, a generate block that defines what to create and where, and a source that is either inline data or a clone of an existing resource.
new Namespace created │ matches policy `match.any` ▼ Kyverno admission controller creates an UpdateRequest │ ▼ kyverno-background-controller reconciles it │ ▼ downstream resource created in the new namespace │ synchronize: true ▼ controller keeps it reconciled (reverts edits, recreates on delete)Caption: a namespace trigger flows through an UpdateRequest to the background controller, which creates and optionally syncs the resource.
The intermediate object is a UpdateRequest. You can inspect generation status with kubectl get updaterequests -A, which is the first place to look when a resource does not appear.
Walkthrough: generate from inline data
The inline data source defines the resource directly in the policy. This example creates a ConfigMap in every new namespace, excluding the system ones. Note the current match.any syntax and that the generate block includes apiVersion and kind.
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: default-configmapspec: rules: - name: gen-configmap match: any: - resources: kinds: - Namespace exclude: any: - resources: namespaces: [kube-system, kube-public, kube-node-lease, kyverno] generate: apiVersion: v1 kind: ConfigMap name: default-config namespace: "{{request.object.metadata.name}}" synchronize: true data: data: APP_ENV: productionNotice namespace: "{{request.object.metadata.name}}" targets the namespace that triggered the rule, and synchronize: true means later edits to the ConfigMap are reverted by Kyverno. The exclude block keeps the policy off system namespaces, which you almost always want.
Walkthrough: clone an existing resource
When the source already exists in the cluster, use clone instead of data. A common case is copying an image-pull Secret into every namespace so pods can pull private images. With synchronize: true, changes to the source Secret propagate to every copy.
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: sync-image-pull-secretspec: rules: - name: sync-regcred match: any: - resources: kinds: - Namespace generate: apiVersion: v1 kind: Secret name: regcred namespace: "{{request.object.metadata.name}}" synchronize: true clone: namespace: default name: regcredNotice clone names the source namespace and resource. Kyverno copies it as-is and cannot template its contents, which is the trade-off versus data. To copy several resources at once, use cloneList with a label selector.
What synchronize actually does
This is the part the old documentation got wrong, and it matters. With synchronize: true, the background controller reconciles the generated resource. It does not block edits with a webhook; it reverts them. The behaviour differs slightly between a data source and a clone source.
| Action | With synchronize | Without synchronize |
|---|---|---|
| Edit the generated resource | Reverted by the controller | Kept |
| Delete the generated resource | Recreated | Stays deleted |
| Delete the trigger (namespace) | Generated resource deleted | No effect |
Modify the source (clone) or data | Propagated to copies | Not propagated |
Delete the policy (data source) | Downstream deleted unless orphanDownstreamOnPolicyDelete: true | Retained |
| Delete the clone source | Downstream deleted | Retained |
Source: Kyverno generate rules. The practical takeaway: synchronize: true gives you desired-state enforcement, not a one-time copy. If you want a copy people can then edit freely, leave it false.
The RBAC that trips people up
Generation is performed by the kyverno-background-controller ServiceAccount. Its default permissions cover common, non-critical resources. Two situations need extra grants.
Generating a resource type the controller does not manage
To generate, say, a Deployment or a custom resource, add a ClusterRole with the background controller's aggregation label. Kyverno's ClusterRoles use aggregation, so a new role with the right label is merged into the controller's permissions automatically (customizing permissions).
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: kyverno:generate-networkpolicies labels: rbac.kyverno.io/aggregate-to-background-controller: "true"rules: - apiGroups: ["networking.k8s.io"] resources: ["networkpolicies"] verbs: ["create", "update", "delete", "get", "list", "watch"]Notice the label rbac.kyverno.io/aggregate-to-background-controller: "true" is what wires this into the controller. Do not edit Kyverno's built-in roles; add a new one so upgrades do not overwrite your change. After applying, confirm aggregation with kubectl get clusterrole kyverno:background-controller -o yaml.
Generating RBAC resources
Generating a RoleBinding is a special case. Kubernetes prevents privilege escalation: to create a binding that grants a role, the creator must already hold that role. So the background controller's ServiceAccount must be bound to the same role you want to grant (generating bindings).
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: kyverno:generate-adminroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: adminsubjects: - kind: ServiceAccount name: kyverno-background-controller namespace: kyvernoNotice this binds the background controller to admin so a policy can then generate RoleBindings that grant admin. Without this, Kubernetes rejects the generated binding, and it looks like a Kyverno bug when it is really RBAC working as designed.
Applying to existing namespaces
By default a generate rule only fires for new triggers. To apply it retroactively to namespaces that already exist, set generateExisting: true. It runs once when the policy is installed, then behaves like a normal generate rule.
generate: generateExisting: true apiVersion: networking.k8s.io/v1 kind: NetworkPolicy name: default-deny namespace: "{{request.object.metadata.name}}" synchronize: true data: spec: podSelector: {} policyTypes: [Ingress, Egress]Notice generateExisting: true is what closes the gap when you add Kyverno to a cluster that already has namespaces.
Failure modes and gotchas
- Nothing generated. Check
kubectl get updaterequests -A. AFailedstatus usually means missing RBAC on the background controller. Kyverno also validates permissions when you install the policy and will warn you. - "Privilege escalation" errors on RBAC generation. You did not bind the background controller to the role you are trying to grant. See the RBAC section.
- Old
matchsyntax. Barematch.resources.kindsis the pre-1.6 form. Usematch.any(ormatch.all) withresourcesunderneath, as shown here. - Forgetting
apiVersioningenerate. The current schema expectsapiVersionandkindin the generate block. - Expecting a webhook to block edits. Synchronization reverts changes through the controller; it is eventually consistent, not an instant admission denial. Give it a moment.
datavsclonedeletion semantics differ. Deleting adata-source policy deletes downstreams by default; deleting a clone source deletes downstreams too, but deleting a clone-source policy does not. Check the table before relying on cleanup behaviour.
Summary and next steps
Generate rules let Kyverno provision and maintain the resources every namespace needs: NetworkPolicies, quotas, secrets, and bindings. Use data for templated resources, clone for copies of an existing source, and synchronize: true when you want the cluster to keep enforcing the desired state. The most common failure is RBAC: grant the background controller permission with an aggregation label, and bind it to any role you want to generate bindings for.
Next, see part two of this series for more generate patterns, and the Kyverno policy library for ready-made examples.