skip to content

How does a Gatekeeper rule read a sibling object from data.inventory, and what happens if that kind was never synced?

level: middleimportance: should knowfreq 48%

answer

  1. index by scope, groupVersion, kind, name
  2. the groupVersion string carries the group
  3. a missing path is not a false value
  4. no result rather than a denial
  5. which side of the not() decides

basics

~10 s

A rule indexes data.inventory by scope, groupVersion, kind and name. An unsynced kind makes that lookup undefined, not false — so the surrounding logic decides whether the rule blocks everything or waves everything through.

solid answer

~50 s

You address the object explicitly: `data.inventory.namespace[ns]["networking.k8s.io/v1"]["NetworkPolicy"][_]` for a namespaced kind, or the `cluster` branch for a cluster-scoped one, with the groupVersion string exactly as the API server spells it. The trap is what happens when nothing is there. In Rego an unresolvable path is **undefined**, not `false`, and an undefined rule body produces no result at all. If your rule denies when a helper *fails* to find a default-deny NetworkPolicy, an unsynced kind makes that helper undefined, `not helper` succeeds, and every workload is rejected. Write it the other way round — deny only when you positively see something bad — and the same missing sync silently allows everything. Neither direction is safe by accident, so treat the sync entry as part of the policy and verify it, and remember that a typo in the groupVersion produces exactly the same undefined path as no sync at all.

code

rego · 13 lines
rego
package k8srequiredefaultdeny

violation[{"msg": msg}] {
  ns := input.review.object.metadata.namespace
  not default_deny_exists(ns)
  msg := sprintf("namespace %v has no default-deny NetworkPolicy", [ns])
}

default_deny_exists(ns) {
  np := data.inventory.namespace[ns]["networking.k8s.io/v1"]["NetworkPolicy"][_]
  np.spec.podSelector == {}
  np.spec.policyTypes[_] == "Ingress"
}

go deeper

for a junior

Know that a replicated object is indexed by scope, then groupVersion, then kind, then name, and that a path which does not resolve gives you nothing rather than a false value.

for a middle

Be ready to write the lookup, name the groupVersion string precisely, and explain that undefined is not false — so a missing sync either denies everything or allows everything depending on the negation.

for a senior

Demonstrate that you design for the failure: a distinct message when the kind is not visible at all, a non-blocking rollout, and RBAC checked before the rule is trusted in production.

for a principal

Own the standard that a rule and the data it depends on ship and are reviewed together, so no constraint can reach a cluster whose sync configuration silently makes it meaningless.

## Addressing the object The replicated cache is an ordinary Rego document, so you reach into it with ordinary indexing. Two branches exist, one per scope: ``` data.inventory.cluster[<groupVersion>][<kind>][<name>] data.inventory.namespace[<namespace>][<groupVersion>][<kind>][<name>] ``` Every segment is a string key. `<groupVersion>` is `"v1"` for core resources and `"<group>/<version>"` otherwise — `"networking.k8s.io/v1"`, `"apps/v1"`. `<kind>` is the kind as spelled in the manifest (`"NetworkPolicy"`, capital N and P). Use `[_]` in the name position when you want to iterate every object of that kind, which is what an existence check does. A rule for *"a namespace may not hold workloads unless it holds a default-deny NetworkPolicy"* looks like the code example: pull the namespace off the object under admission, and check the inventory for a NetworkPolicy in that namespace whose `spec.podSelector` is the empty object (it selects every pod) and whose `policyTypes` includes `Ingress`. ## Undefined is not false This is the fact that decides how the rule behaves when the plumbing is wrong. In Rego, a reference that cannot be resolved — a key that is not present, a document that was never populated — is **undefined**. Undefined is not `false`, and it is not `null`. A rule body containing an undefined expression produces **no result**: the rule does not contribute a value, and a partial rule like `violation` simply adds nothing to its set. The consequence flips depending on where the negation sits. - **Deny on absence.** `not default_deny_exists(ns)` is the natural way to write the requirement. If `NetworkPolicy` was never added to the sync configuration, `data.inventory.namespace[ns]["networking.k8s.io/v1"]` is undefined, so `default_deny_exists(ns)` is undefined, so `not default_deny_exists(ns)` **succeeds** — and every matching workload in every namespace is rejected, including the ones that do have the policy. The rule fails closed and looks, from the outside, like a broken cluster. - **Deny on presence.** If instead you deny only when you can positively see a bad object, the same missing sync makes the lookup undefined, no violation is produced, and everything is admitted. The rule fails open, quietly, and the constraint's status looks perfectly healthy because it never finds anything to complain about. Neither is a safe default. The sync entry is not infrastructure trivia sitting beside the policy; it is *part of* the policy, and a rule that reads the inventory is only correct in a cluster where its kinds are replicated. ## The other way to get undefined A wrong key produces exactly the same undefined path as a missing sync, and it is easier to write than you would like: - `"v1"` instead of `"networking.k8s.io/v1"` — right version, missing group. - `"networkpolicies"` (the plural resource name) instead of `"NetworkPolicy"` (the kind). - Reading the `cluster` branch for a namespaced kind, or vice versa. None of these is a syntax error. Rego will happily evaluate the rule; it just never resolves the path. ## Making the failure visible Because both failure directions are silent, build the check in deliberately rather than trusting the rule to shout: 1. **Separate "no policy exists" from "I cannot see any policies at all."** A helper that asserts the inventory branch for the kind is present at least somewhere in the cluster lets you emit a distinct message — *"NetworkPolicy is not replicated into the inventory"* — instead of blaming the namespace under review. That one message turns a confusing outage into a five-second fix. 2. **Test with a fixture that has the object and one that does not**, offline, before the rule reaches a cluster. Note the limit: a hand-built fixture proves the *rule logic*, not that the cluster's sync configuration matches. Those are two separate correctness conditions. 3. **Roll the constraint out non-blocking first.** If a missing sync is going to reject every workload in the estate, you want to discover that from reported violations rather than from a pager. 4. **Check RBAC.** Gatekeeper only replicates kinds its ServiceAccount may list and watch. A correct `syncOnly` entry plus a narrowed ClusterRole gives you an empty branch and, again, an undefined lookup. The short version to say out loud: *undefined is not false, so a rule that reads the inventory has two failure modes, and which one you get depends purely on which side of a negation your lookup ended up on.*

  • How would you make the rule distinguish 'this namespace has no policy' from 'the kind is not synced'?
    Add a helper that asserts the kind is visible anywhere in the inventory, and emit a different message when it is not. The rule then reports *"NetworkPolicy is not replicated"* rather than accusing every namespace of being misconfigured. It costs three lines and turns a cluster-wide denial into an obvious configuration fix.
  • Your rule works against a hand-built inventory fixture but rejects everything in the cluster. Where do you look first?
    At the sync configuration and Gatekeeper's RBAC, not at the Rego. A fixture proves the logic given data; it says nothing about whether the cluster is actually replicating that kind. Confirm the group, version and kind entry exists in a SyncSet or the Config, that the ServiceAccount may list and watch it, and that the groupVersion string in the rule matches exactly.

saying these in an interview costs you the question

  • Says an unresolvable Rego path evaluates to false
  • Assumes a rule that compiles will behave correctly without the sync
  • Uses the plural resource name where the kind is expected
  • Omits the API group from the groupVersion key
  • Treats an offline fixture as proof the cluster is configured

context