skip to content

In a Kubernetes ValidatingAdmissionPolicy, what data can a CEL validation expression see?

level: juniorimportance: must knowfreq 58%

answer

  1. the request, and almost nothing around it
  2. no network, no API reads, no history
  3. object, oldObject, request, params, variables
  4. the namespace object is handed over, not fetched
  5. authorizer answers 'may they?', not 'what is?'

basics

~10 s

Only what the API server hands the expression: the incoming object, the previous object on an update, the request attributes, the request's namespace object, and a bound parameter resource. It cannot fetch anything else.

solid answer

~50 s

A CEL validation is evaluated inside the API server's request path, so it is given a fixed set of variables and nothing more: `object` (the incoming resource, null on DELETE), `oldObject` (the previous state, null on CREATE), `request` (the admission attributes such as the operation, the requesting user and their groups, and whether this is a dry run), `namespaceObject` (the Namespace object of the request's namespace, pre-populated for you), `params` (the parameter resource bound by the binding, if the policy declares a `paramKind`), and any named `variables` the policy defines. There is also an `authorizer`, which lets the expression ask whether some subject is authorized for a verb on a resource. What is absent is everything else: no network calls, no reading other Pods or Deployments, no cluster history. A rule that needs a fact from outside this input cannot be written here.

go deeper

for a junior

Be ready to name the variables an expression gets — object, oldObject, request, namespaceObject, params — and to say plainly that CEL here cannot make network calls or read other resources.

for a middle

An interviewer expects you to explain why the constraint exists: the expression runs in the API server's write path, so it must be deterministic, side-effect-free and bounded. Know that object is null on DELETE and oldObject is null on CREATE.

for a senior

Demonstrate that you use the variable list as a feasibility test before writing anything: if the fact the rule needs is not in the request, the namespace, or the bound parameters, the rule does not belong here and you say so early.

for a principal

Own the position that the input surface, not the language, is what decides which requirements the in-tree venue can carry — and that a team pushing to smuggle external facts into admission is really asking for a dependency in front of every cluster write.

## What a ValidatingAdmissionPolicy is, in one paragraph A `ValidatingAdmissionPolicy` is a Kubernetes API object that carries admission rules written as CEL (Common Expression Language) expressions. The API server evaluates them itself, in-process, on the write path, without calling out to anything. A companion `ValidatingAdmissionPolicyBinding` says which requests the policy applies to and supplies its parameters. Because the evaluation happens inline in every matching create, update or delete, the language is deliberately constrained: CEL expressions are total, side-effect-free, and bounded in cost. Understanding *what the expression is allowed to look at* is the first thing to learn, because it is also the thing that decides whether your requirement can live here at all. ## The variables you get - **`object`** — the resource as submitted. On a DELETE there is no incoming object, so `object` is null and the check has to read `oldObject` instead. - **`oldObject`** — the resource as it existed before this request. Null on CREATE. This is what makes "you may not change field X once set" expressible: compare `object` and `oldObject`. - **`request`** — the admission request attributes: the operation, the resource and subresource, the name and namespace, the requesting user's username and groups, and the `dryRun` flag. This is how a rule exempts a specific controller's service account, or declines to act on a dry run. - **`namespaceObject`** — the full Namespace object for the namespace the request targets. This is the single piece of surrounding cluster state you are handed, and it is handed to you: the API server has it already, so exposing it costs nothing. It is not a general lookup — you get *that* namespace, not any other object. - **`params`** — the parameter resource selected by the binding, present only if the policy declares a `paramKind`. This is the supported way to get data that is not in the request into the decision. - **`variables`** — named sub-expressions the policy defines, referenced as `variables.someName`, evaluated lazily and reused across validations. - **`authorizer`** — lets an expression ask the API server's authorizer a yes/no question, for example whether the requesting user could perform some verb on some resource. Note the shape of that: it is an *authorization decision*, not a data read. You can ask "may they?"; you cannot ask "what is?". On top of these, CEL supplies its standard macros — `has()` for field presence, `all()`, `exists()`, `map()`, `filter()` over lists — and Kubernetes adds a handful of extension libraries for things like URL parsing, regular-expression matching and resource-quantity comparison. ## What is deliberately absent There is no HTTP client. There is no API client. There is no way to list the other Pods in the namespace, look up the ConfigMap the workload references, read an image's metadata from a registry, consult a service catalogue, or remember what the same expression decided an hour ago. The expression is a pure function of the input it is given. This is not an oversight. The code runs on the API server's own write path for every matching request; a network call there would put an outside dependency in front of every write, make evaluation non-deterministic, and make request latency somebody else's problem to control. ## What this means in practice Take a concrete requirement: workloads may only be scheduled onto an approved set of node classes, expressed as a `nodeSelector` key and a set of matching tolerations. Every fact that rule needs is in the submitted object — the selector map and the toleration list are right there in the Pod template. The only thing that is not in the request is the *allowed set*, and that is exactly what `params` exists for. So the rule fits. Now change one word: workloads may only use node classes that the platform inventory currently marks as available. That fact lives in a service, and no variable in the list above will ever return it. The rule does not fit — not because CEL is a weak language, but because the venue gives it no way to reach the fact. The correct move at that point is to change where the check runs, or to change the shape of the requirement so the fact is delivered into `params` by something else. ## The common mistakes Candidates routinely assume admission can "see the cluster". It cannot. They assume `namespaceObject` implies a general read capability. It does not. They assume `object` is always populated. On DELETE it is null. And they assume `authorizer` is an escape hatch for fetching data. It answers permission questions only.

  • Why is `namespaceObject` not a violation of the no-I/O property?
    Because the expression does not perform a lookup. The API server already has the request's Namespace object resolved as part of handling the request, so it pre-populates the variable. You get that one namespace and nothing else — you cannot pivot from it to list other objects, and you cannot reach a different namespace.
  • What does `object` contain on a DELETE request, and how do you write a rule for deletes?
    It is null, because there is no incoming resource. The resource being removed is in `oldObject`, so a delete rule reads `oldObject` — for example, refusing deletion of a resource that carries a protection label. A rule that dereferences `object` unguarded will error on deletes.
  • Can a CEL validation take the requesting user into account?
    Yes. `request.userInfo` carries the username and groups, so a rule can exempt a specific controller's service account or apply only to human users. The `authorizer` variable goes further and asks whether some subject is permitted a verb on a resource, which is useful for "only someone who could already do X may do Y" rules.

It is a pure function with a fixed argument list. Anything not passed in as an argument simply does not exist as far as the expression is concerned.

saying these in an interview costs you the question

  • Claims the expression can query the API server for other objects
  • Thinks the rule can compare the Pod against other Pods in the namespace
  • Assumes object is populated on a DELETE request
  • Treats namespaceObject as a general cluster-lookup capability
  • Believes CEL can call an HTTP endpoint to fetch an allowlist

context