In Kyverno, a validate rule matches only Pods - why does it still refuse a Deployment?
answer
- one rule, every workload kind
- the applied spec is not what enforces
- the policy reads back with extra rules
- generated names carry an autogen- prefix
- paths rewritten into the Pod template
basics
~20 sKyverno's Pod controller autogen copies a Pod rule into equivalent rules for Deployment, StatefulSet, DaemonSet, Job and CronJob, rewriting field paths into each one's Pod template. The Deployment is refused by a generated rule, not the one you wrote.
solid answer
~40 sKyverno has a feature called Pod controller auto-generation. When a rule matches kind `Pod`, Kyverno derives extra copies of that rule for the workload kinds that embed a Pod template - Deployment, StatefulSet, DaemonSet, Job and CronJob - and rewrites the field paths so the copy reaches into `spec.template.spec` (and the CronJob's deeper `spec.jobTemplate.spec.template.spec`). So a single rule saying `no Secret may be injected as an environment variable` covers a bare Pod and every controller that would produce one, without you maintaining five near-copies with different paths. The generated rules show up when you read the policy back from the cluster, named `autogen-<rule>` and `autogen-cronjob-<rule>`. The spec you applied is untouched. You can narrow or switch the behaviour off with the `pod-policies.kyverno.io/autogen-controllers` annotation on the policy.
go deeper
Recall that a Kyverno rule matching Pods is automatically extended to the workload kinds that carry a Pod template, and that names beginning with autogen- are the derived copies.
Be ready to explain the rewrite itself: which kinds get a copy, why CronJob gets its own variant, and where you read the generated rules back from the cluster.
Show that you treat the effective rule set, not the applied YAML, as the thing being enforced - and that you check the controller annotation before trusting coverage.
Own the convention: rules authored at the Pod level, kind-agnostic message text, and a review rule that any narrowing of autogen is argued for explicitly rather than inherited.
## What is happening Kyverno rules are written against a resource *shape*. A validate rule that says `no container may take a Secret through env.valueFrom.secretKeyRef or envFrom.secretRef - mount it as a projected volume instead` naturally reads `spec.containers[*]`, because that is where containers live **in a Pod**. But almost nobody applies bare Pods. They apply a Deployment, a StatefulSet, a Job, a CronJob. In those objects the same container list is buried one or two levels deeper, under a Pod *template*. A rule written literally against `spec.containers` would simply not match those objects. Kyverno solves that with **Pod controller auto-generation** ("autogen"). When a rule matches kind `Pod`, Kyverno derives additional rules for the workload kinds that carry a Pod template, and rewrites the paths inside them so the same logic lands on the template. ## The generated rules For a rule you named `no-secret-env`, Kyverno derives: - `autogen-no-secret-env` - matching the controller kinds that nest the template once, with paths rewritten to `spec.template.spec.…` - `autogen-cronjob-no-secret-env` - matching CronJob, whose template is nested twice, with paths rewritten to `spec.jobTemplate.spec.template.spec.…` The CronJob variant is separate precisely because its nesting depth is different; one rewrite could not serve both. These generated rules are not written back into the spec you applied. They appear when you read the policy back from the cluster, recorded on the policy alongside the rule you authored. That read-back is the single most useful debugging habit on this feature: **what the cluster enforces is the generated set, not the YAML in your repository**. ## Why it matters for the person who was blocked A developer submits a Deployment at 5pm and the API server refuses it, quoting a rule name they have never seen - something prefixed `autogen-`. They search the policy repository for that name and find nothing, because the name exists only in the derived copy. Knowing that the prefix means "derived from a Pod rule, look for the un-prefixed name" turns a confusing rejection into a two-minute fix. The message they read is the message the policy author wrote once, against the Pod shape. If that text names a Pod field path, it will look wrong to someone holding a Deployment. Kind-agnostic wording - "mount the Secret as a volume; do not inject it into a container's environment" - survives the copy; "fix spec.containers[0].env" does not. ## What autogen does not do - It does not stop the original rule from applying to bare Pods. The rule you wrote still matches `Pod` directly; the generated rules only add the controller kinds. - It does not merge your rule with anything. If you *also* keep a hand-written rule that matches Deployment for the same control, both will match a Deployment and both will report - that is where duplicate denial messages come from. - It is not unconditional. The annotation `pod-policies.kyverno.io/autogen-controllers` on the policy narrows the set of controller kinds, and the value `none` switches generation off entirely, leaving only bare Pods covered. A narrowed value is a silent coverage gap: nothing fails, a whole workload kind is simply outside the rule. ## Writing with autogen in mind The practical consequences for an author are small but real: 1. **Write the rule once, against Pod.** Do not pre-empt autogen by adding controller kinds to the match block and hand-writing template paths; you end up maintaining the copies autogen would have derived. 2. **Write messages that make sense for any workload kind**, because one message string is what every refused author reads. 3. **Read the policy back after applying it**, and confirm the generated rules you expected exist - especially the CronJob variant, which is the one most often missing. 4. **Treat the annotation as part of the policy's meaning.** Someone narrowing it later removes coverage without changing a single line of rule logic. ## The short version One rule, written at the Pod level, becomes several rules at enforcement time so it can follow the Pod template into every object that carries one. The refusal your Deployment hit came from one of those derived copies.
- Where do you look to see the rules Kyverno actually generated from your Pod rule?Read the policy back from the cluster rather than from your repository. Kyverno records the derived rules on the policy alongside the one you authored, named `autogen-<rule>` and `autogen-cronjob-<rule>`. The spec you applied is unchanged, so a repo-only view never shows them.
- Does autogen change what happens when someone applies a bare Pod?No. The rule you wrote still matches `Pod` directly and decides that request. Autogen only adds coverage for the workload kinds that embed a Pod template; it neither replaces nor rewrites the original rule.
- Can you switch autogen off, and when would you want to?Yes - set `pod-policies.kyverno.io/autogen-controllers: none` on the policy. It is occasionally right when the rule is genuinely only meaningful for a directly submitted Pod, but it leaves every controller kind outside the rule, so it should be a deliberate, documented decision rather than a default.
You describe the passenger once, and the system reprints that description at every depth a passenger can be sitting - one seat down in a Deployment, two down in a CronJob.
saying these in an interview costs you the question
- Says the Pod rule matches a Deployment because a Deployment is a Pod
- Thinks you must hand-write one rule per workload kind
- Believes autogen rewrites the policy spec you applied
- Claims autogen covers only Deployments
- Assumes generated coverage is unconditional and cannot be narrowed