skip to content

Your Gatekeeper Constraint is blocking system-namespace workloads — how do you scope or exempt it?

level: seniorimportance: should knowfreq 54%

answer

  1. empty match is not empty scope
  2. allowlist and denylist both exist
  3. two selectors, two different objects
  4. one rule versus the whole engine
  5. system namespaces are a recoverability question

basics

~20 s

Narrow the Constraint's match block: list kinds, restrict namespaces or add excludedNamespaces, or select by label. For system namespaces that must never be evaluated at all, exempt them engine-wide in Gatekeeper's Config rather than per rule.

solid answer

~50 s

First check the match block, because an empty `spec.match` matches everything the webhook is sent. Scope it: `kinds` narrows the API groups and kinds; `namespaces` turns it into an allowlist; `excludedNamespaces` carves out specific ones; `labelSelector` selects on the matched object's labels while `namespaceSelector` selects on the *namespace's* labels — a distinction people get backwards. For one rule misfiring, a per-Constraint exclusion is right and it is visible to anyone reading that object. For system namespaces such as `kube-system` and Gatekeeper's own namespace, the exclusion belongs in Gatekeeper's `Config` resource, whose match entries list `excludedNamespaces` against a set of processes (audit, webhook, sync, mutation), because that exempts them from the engine as a whole rather than one rule at a time. Prefer a namespace label plus `namespaceSelector` over a growing hand-maintained list of namespace names.

go deeper

for a junior

Know that a Constraint has a match block and that leaving it empty is not the same as leaving it unscoped — it matches everything. Be able to point at kinds, namespaces and excludedNamespaces.

for a middle

Explain each match field and, in particular, that labelSelector reads the admitted object's labels while namespaceSelector reads the namespace's. Note that the same match block also governs what the audit reports.

for a senior

Diagnose from the rejection message to the Constraint's match block, and choose consciously between a per-rule exclusion and an engine-wide one. Be able to say why system namespaces belong at the engine level.

for a principal

Own the exemption policy: how many carve-outs the estate tolerates, who approves one, and whether narrowing scope is fixing a rule or quietly retiring a control.

## Scoping is where Gatekeeper rules actually go wrong A Constraint that blocks the wrong thing is usually not a bad rule — it is a rule with no boundary. The `spec.match` block is the boundary, and its default is the dangerous one: **an empty match matches every object the webhook is sent**. ### The fields of a Constraint's match block - **`kinds`** — a list of `{apiGroups, kinds}` pairs. The core group is the empty string `""`. Without this, a rule written for Pods is evaluated against everything. - **`namespaces`** — an allowlist. If present, only these namespaces are in scope. - **`excludedNamespaces`** — a denylist applied on top. - **`labelSelector`** — a selector over the labels of the **object being admitted**. - **`namespaceSelector`** — a selector over the labels of the **namespace the object is in**. Mixing these two up is the classic scoping bug: you label namespaces `policy-tier=strict` and then write `labelSelector`, which looks at pod labels and matches nothing. - **`scope`** and **`name`** — narrow to cluster-scoped or namespaced resources, or to a named object. The same match block governs the periodic audit, not just admission, so narrowing it also narrows what shows up on the Constraint's status. ### Two different exemption mechanisms **Per-Constraint exclusion.** Add the namespace to that Constraint's `excludedNamespaces`, or scope by label. The request still reaches Gatekeeper and the engine still decides; it simply finds no match. This is the right tool when one rule is wrong for one place, and its great virtue is visibility: the exception is written on the object that carries the rule, so anyone reading the Constraint sees it, and it is a reviewable diff in the repository that owns the rule. **Engine-wide exemption.** Gatekeeper's `Config` resource carries a match list whose entries pair `excludedNamespaces` with a list of `processes` — audit, webhook, sync, mutation, or all of them. A namespace excluded from the webhook process is not evaluated by any Constraint at all. This is the right tool for namespaces that must keep working while the policy engine is broken or misconfigured: `kube-system`, the CNI and CSI namespaces, and Gatekeeper's own namespace. The cost is that this exemption is invisible from the rule. Someone reading a Constraint that says "all Pods" has no way to know a set of namespaces was carved out somewhere else entirely, so an engine-wide exclusion list has to be short, deliberate, owned and reviewed. ### Why system namespaces are a special case, not just another exclusion A Constraint that rejects, say, Pods without resource limits will happily reject a system component's Pod. If that component is on the path to recovering the cluster — or is Gatekeeper itself — you have built a rule that stops you from fixing the rule. Exempting the system namespaces from the engine is what keeps that failure recoverable, which is why it belongs at the engine level rather than being re-derived in every Constraint by someone who might forget. ### Diagnosing the live incident 1. Read the rejection message: it names the Constraint that fired, which tells you which object to look at. 2. Read that Constraint's `match`. Nine times out of ten it is empty, or it lists `kinds` but no namespace scoping. 3. Decide the blast radius: is this one namespace that should never have been in scope, or is the rule itself wrong? Excluding a namespace to make an alert stop, when the rule is genuinely correct there, converts a security control into a formality. 4. Apply the narrower match, or the engine-level exemption for a system namespace, and write down which one you chose and why. ### Scaling past a list of names Hand-maintained `namespaces` and `excludedNamespaces` lists rot: a new namespace is created, nobody adds it, and either it escapes the rule or it is blocked by surprise. The durable pattern is a namespace **label** that expresses intent — a tier, an environment, an opt-in — combined with `namespaceSelector` on the Constraint. New namespaces get the label from whatever creates them, and the rule's scope follows automatically instead of being edited after every incident. ### Common wrong answers - "Set it to dryrun until they fix it" — that turns off enforcement everywhere the rule applies, not just where it misfires. - "Delete the ConstraintTemplate" — that garbage-collects every Constraint built on it, cluster-wide. - "`labelSelector` matches namespace labels" — it matches the admitted object's labels; `namespaceSelector` is the one that reads namespace labels.

  • What is the difference between labelSelector and namespaceSelector in a Constraint's match block?
    `labelSelector` is evaluated against the labels of the object being admitted — the Pod, the Deployment. `namespaceSelector` is evaluated against the labels on the namespace that object lives in. If you have labelled namespaces to express an opt-in tier, `namespaceSelector` is the one that reads it; using `labelSelector` there silently matches nothing.
  • When would you exempt a namespace in Gatekeeper's Config rather than on the Constraint?
    When the namespace must be outside the engine entirely rather than outside one rule — typically `kube-system`, cluster infrastructure namespaces and Gatekeeper's own. Config's match entries pair excludedNamespaces with the processes they apply to, so those namespaces keep working even when a rule or the engine is misconfigured. The trade is visibility: it is invisible to anyone reading the Constraint.
  • A new namespace keeps escaping a rule that lists namespaces explicitly. What do you change?
    Stop enumerating names. Put a label on namespaces that expresses the intent — an environment or a policy tier — have whatever creates namespaces set it, and match with `namespaceSelector`. Scope then follows namespace creation automatically instead of depending on someone editing a list after each miss.

saying these in an interview costs you the question

  • Assumes an empty match block matches nothing
  • Says labelSelector reads namespace labels
  • Switches the rule to dryrun to unblock one namespace
  • Deletes the ConstraintTemplate to stop one Constraint
  • Never exempts system namespaces from the engine
  • Maintains long hand-edited namespace lists

context