skip to content

In a Gatekeeper ConstraintTemplate, what must the Rego produce to deny a request?

level: juniorimportance: must knowfreq 76%

answer

  1. the rule has one fixed name
  2. a set, not a boolean
  3. one element per offender
  4. empty set means admitted
  5. msg required, details optional

basics

~20 s

Gatekeeper denies a request when the template's violation rule produces a non-empty set of objects, each carrying a msg string. An empty set means the constraint is satisfied. Each object may also carry an optional details object.

solid answer

~40 s

Gatekeeper does not look for a boolean. It evaluates one rule by name, `violation`, and treats it as a partial set whose elements are objects. Each element is one violation and must carry a `msg` string; it may also carry a `details` object of arbitrary structure. If the rule body holds several times — once per offending container or volume — you get several elements and several messages. If it never holds, the set is empty and the constraint raises nothing, which is why a template with a typo in a field path admits everything quietly. So a hostPath rule iterates the Pod's volumes, keeps the ones with a `hostPath` field, and builds a message per offender with `sprintf`. The package name is yours to choose; the rule name is not.

code

rego · 7 lines
rego
package k8shostpath

violation[{"msg": msg, "details": {"volume": vol.name}}] {
    vol := input.review.object.spec.volumes[_]
    vol.hostPath
    msg := sprintf("volume %v mounts a hostPath, which is not allowed", [vol.name])
}

go deeper

for a junior

Recall the shape: a rule named violation, producing a set of objects, each with a msg string. Empty means nothing to report; non-empty means the constraint has complaints.

for a middle

Be ready to explain why it is a set: the body can hold many times, once per offending container or volume, and each hold adds its own message and details.

for a senior

Show that you know the contract is silent when the rule is wrong. Demonstrate testing a new template against a manifest that must fail, and writing messages that name the offender.

for a principal

Own the convention across a template library: what every msg must contain, what shape details takes so one dashboard can read all of them, and who reviews a template before it is applied.

## What the contract actually is A ConstraintTemplate is a Kubernetes custom resource. Inside it, `spec.targets` holds one entry for the target `admission.k8s.gatekeeper.sh`, and that entry carries a `rego` block of policy source. When a Constraint built from that template matches an incoming request, Gatekeeper loads the Rego into its embedded OPA and evaluates **one rule by name**: `violation`. That rule is written as a *partial set* rule whose elements are objects: ``` violation[{"msg": msg}] { # body } ``` Every time the body holds, one object is added to the set. The decision is then trivially mechanical: | result | meaning | | --- | --- | | empty set | this constraint found nothing to complain about | | non-empty set | one violation per element, each with its own message | What happens *because* of a non-empty set — a hard denial, a warning, or a recorded-but-allowed result — is the Constraint's `enforcementAction`, not the rule's business. The rule's only job is to describe what is wrong. ## The two keys in each element `msg` is **required** and must be a string. It is the human sentence that comes back to whoever ran the write, and it is stored alongside the audit results. Build it with `sprintf` so it names the specific offender rather than restating the rule. `details` is **optional** and may be any object. It is the machine-readable half: the volume name, the container, the value that failed, the threshold it failed against. Dashboards and reporting jobs read `details` precisely because they should not be parsing English out of `msg`. ## Why it is a set and not a boolean A boolean can only say "this Pod is bad". A set says "these three volumes are bad, and here is which". The idiom is to iterate and let the body hold once per offender: ``` violation[{"msg": msg, "details": {"volume": vol.name}}] { vol := input.review.object.spec.volumes[_] vol.hostPath msg := sprintf("volume %v mounts a hostPath", [vol.name]) } ``` Here `vol.hostPath` is doing double duty: it is an existence check. If a volume has no `hostPath` field, that expression contributes nothing, the body does not hold for that iteration, and no element is added. Only the offending volumes produce messages. ## The failure mode you must internalise The contract is **silent on both ends**. A rule that never adds an element and a rule that is wired to the wrong field path look identical from outside: no violations, request admitted. Nothing in Gatekeeper tells you the rule you shipped is inert. Two habits follow from that, and interviewers listen for them: 1. Always exercise a new template against a manifest you *know* should fail, not only against a compliant one. A template that passes clean input proves nothing. 2. Prefer explicit iteration with `[_]` over clever one-shot expressions, because an iteration that yields nothing is easier to spot in a failing test than a nested field access that silently goes nowhere. ## Common shapes people get wrong - **Returning a boolean or a string.** `violation = true` is a complete rule, not a set of objects, and Gatekeeper gets nothing it can turn into a denial message. The rule looks written and behaves as if absent. - **Putting the message under the wrong key.** Only `msg` is the message. A `reason`, `message` or `error` key is just more of `details` from Gatekeeper's point of view, and the denial comes back without a useful sentence. - **One violation for the whole Pod.** Technically fine, operationally poor: the person who is blocked has to work out which of eight containers offended. - **Assuming the package must be called `violation`.** The package name is conventionally the template's kind lowercased, and it is arbitrary; the *rule* name is the fixed part of the contract. ## What the rule is allowed to look at The body may only read what Gatekeeper hands it — the object under decision and the matched Constraint's parameters. There is no reaching out mid-rule to fetch something else, so the whole rule reduces to: pattern-match the object, and for every place it violates the intent, emit a message that names the offender.

  • Why iterate the volumes instead of writing one violation for the whole Pod?
    Because each iteration that holds adds its own element, so the person who is blocked gets a message naming the exact volume rather than a verdict on the Pod. It also lets `details` carry that volume's name as structured data, which is what reporting consumes. One message per offender costs nothing in the rule and saves the debugging round trip.
  • A colleague's new template never blocks anything. What is the first thing you check?
    Whether the body ever holds at all. Feed it a manifest that must fail and confirm you get a message. An empty violation set and a rule pointed at a field path that does not exist are indistinguishable from outside — both admit everything — so a known-bad fixture is the only cheap way to tell them apart.
  • What is `details` for if `msg` already explains the problem?
    `msg` is prose for a human reading a rejected write. `details` is a structured object — offending value, field, threshold — that recording and dashboard consumers can read without parsing English. Both travel with the same violation, so putting the machine-readable facts in `details` keeps `msg` short and keeps consumers off string matching.

Think of it as filing defect tickets rather than stamping PASS or FAIL: file none and the change ships, file three and three specific complaints come back, each naming its own part.

saying these in an interview costs you the question

  • Says the rule returns true or false to deny
  • Thinks an empty violation set blocks the request
  • Puts the human message under a key other than msg
  • Writes one violation for the Pod, hiding which container failed
  • Assumes a template that never fires must be correct

context