skip to content

What do Gatekeeper's enforcementAction values deny, dryrun and warn each do to a request?

level: middleimportance: should knowfreq 61%

answer

  1. three values, one field
  2. the field sits on the instance
  3. two of them admit the request
  4. the difference is who finds out
  5. omitting it means blocking

basics

~20 s

deny rejects the request and returns the violation message. dryrun admits it and only records the violation on the Constraint's status. warn admits it too, but the API server returns a warning that the person applying sees immediately.

solid answer

~40 s

`enforcementAction` is a field on the Constraint, not on the ConstraintTemplate, so two instances of one rule can behave differently. `deny` is the default when the field is absent: the admission request is rejected and the violation message goes back to whoever ran the apply. `dryrun` admits the request; the rule still evaluates and the violation still shows up on the Constraint's `status` when Gatekeeper's audit runs, but nobody is stopped and nothing appears at request time. `warn` also admits the request, but Gatekeeper returns a warning on the admission response, so `kubectl` prints it next to the applier's `created`/`configured` line. The practical difference between `dryrun` and `warn` is who finds out and when: `dryrun` reaches whoever reads the constraint's status later, `warn` reaches the actual author at the moment of the change.

go deeper

for a junior

Learn the three values and the one-line outcome of each: deny rejects, dryrun and warn both let the object through. Remember that leaving the field out means the rule blocks.

for a middle

Explain the mechanism behind each: a rejection carrying the violation message, an audit entry on the Constraint's status, and an API-server warning relayed to the client. Say which one the applier actually sees.

for a senior

Show you pick a value for who needs to find out. Treat the violation message as the product under both warn and deny, and know that audit reports on pre-existing objects regardless of the action.

for a principal

Own what each value costs the organisation: an inventory nobody reads, a warning everyone learns to ignore, or a rejection that stops delivery. Be able to justify the mix you have chosen across a policy estate.

## The three values, and who each one reaches Every Gatekeeper Constraint carries `spec.enforcementAction`. It takes one of three values, and the distinction interviewers care about is not "strict versus lenient" — it is **who learns about the violation, and when**. ### deny (the default) Omit the field and you get `deny`. The admission request is rejected. The API server returns the rejection to the client, carrying the violation message the rule produced, so the person running `kubectl apply` — or the controller reconciling on their behalf — sees the reason inline and the object never exists. The consequence worth stating out loud: the audience for a `deny` is whoever or whatever submitted the request. If the submitter is a human at a terminal, the message is read. If it is a GitOps controller reconciling a repository, the message lands in that controller's logs or sync status and a person only sees it if they are looking there — which is why the wording of the violation message matters as much as the decision. ### dryrun The request is admitted. The rule is still evaluated, and violations are still recorded — Gatekeeper's periodic audit walks live objects and writes what it finds onto the Constraint's own `status`. But nothing is surfaced at request time at all. The applier gets an ordinary success and has no idea a rule disagreed. So `dryrun` produces a list you have to go and read. It answers the question "if I turned this on, how much would break, and where?" and it answers it after the fact, on the Constraint object, for whoever thinks to look. ### warn The request is also admitted — `warn` blocks nothing. The difference from `dryrun` is that Gatekeeper returns a warning on the admission response, and the API server relays warnings to the client, so `kubectl` prints the message right under the applier's output. The author of the change sees the exact violation text, at the moment they made the change, and is still allowed to proceed. That makes `warn` the only one of the three that reaches the person who can most cheaply fix the problem without stopping them. It is also the value most often misremembered: candidates describe it as "a soft block" or "blocks after a grace period", and it is neither — the object is admitted, unmodified, every time. ### A comparison that fits on one line each | value | request outcome | who sees it, and when | |---|---|---| | `deny` | rejected | the applier, immediately, with the message | | `dryrun` | admitted | whoever reads the Constraint's status later | | `warn` | admitted | the applier, immediately, as a warning | ### Mechanical details that come up as follow-ups **It is per Constraint.** Two Constraints generated from the same ConstraintTemplate can carry different values, because the field lives on the instance. The template has no say in it. **Audit does not care.** Gatekeeper's audit evaluates live objects against Constraints and reports on their status regardless of the enforcement action, so a `deny` Constraint still tells you about objects that were created before it existed — admission only ever sees requests, never the resources already sitting in the cluster. **A warning is not a mutation.** `warn` does not fix, default or annotate anything. The object is stored exactly as submitted. **The message is the product.** Under `warn` and `deny` alike, the only thing the applier receives is the violation text. "Denied by policy" wastes the one channel you have; naming the field, the offending value and what an acceptable value looks like is what makes the action useful. ### The common wrong answers - "`dryrun` logs a warning to the user" — no, that is `warn`; `dryrun` surfaces nothing at request time. - "`warn` blocks unless you add an annotation" — no, `warn` always admits. - "The default is `dryrun`, so it is safe to apply a Constraint and see what happens" — the default is `deny`. Applying a Constraint without setting the field turns on blocking.

  • What is the practical difference between dryrun and warn, given both admit the request?
    The audience. Under `dryrun` nothing reaches the applier at all; the violation only shows up on the Constraint's status for someone who goes and reads it. Under `warn` the API server relays a warning on the admission response, so `kubectl` prints the violation message to the person making the change while still letting it through.
  • Where is enforcementAction set, and can two rules from the same template differ?
    It is a field on the Constraint, so yes. The ConstraintTemplate holds the logic and the parameter schema and says nothing about enforcement, which means one template can back a Constraint that denies in production namespaces and another that only warns elsewhere.
  • Does a Constraint set to dryrun still produce violation records?
    Yes. Gatekeeper's audit evaluates live objects against every Constraint and writes what it finds onto that Constraint's status, independent of the enforcement action. Admission is what dryrun turns off, not evaluation, so you still get the inventory of what would have been rejected.

saying these in an interview costs you the question

  • Says dryrun shows a warning to the applier
  • Describes warn as a delayed or conditional block
  • Thinks the default action is dryrun
  • Puts enforcementAction on the ConstraintTemplate
  • Assumes dryrun stops violations being recorded

context