In a Kubernetes AdmissionReview, what does the API server actually send an engine whose match includes Secrets?
answer
- push, not pull
- full body, not the fields you read
- old and new on an update
- no other object, no cluster state
- the match rules are the only control
basics
~20 sThe full object body — for a Secret, its complete data — plus the previous version on an update, the requesting user and groups, the operation and dryRun. It carries no other object and no cluster state.
solid answer
~50 sAn AdmissionReview request carries the object under review in full, `oldObject` on an update or delete, `userInfo` for the requester, the operation, the resource and namespace, and the `dryRun` flag. The body is not filtered to the fields your rule reads — the API server has no idea which those are. So if the engine's match rules include `secrets`, every Secret written in the cluster is POSTed to the engine's endpoint with its data intact, and an update sends the old and new values together. That is a cluster-wide read path nobody granted: it does not depend on the engine's own permissions at all, only on its match. Equally important is what is absent — no other object, no cluster state, no history — so a rule cannot ask whether some other object already exists or what an external list says.
code
json · 22 lines{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
"uid": "9f0b...",
"kind": { "group": "", "version": "v1", "kind": "Secret" },
"resource": { "group": "", "version": "v1", "resource": "secrets" },
"namespace": "payments",
"operation": "UPDATE",
"userInfo": {
"username": "system:serviceaccount:payments:deployer",
"groups": ["system:serviceaccounts", "system:authenticated"]
},
"object": {
"apiVersion": "v1", "kind": "Secret", "type": "Opaque",
"metadata": { "name": "db-credentials", "namespace": "payments" },
"data": { "password": "<the new value, in full>" }
},
"oldObject": { "...": "the previously stored Secret, also in full" },
"dryRun": false
}
}go deeper
Know that the engine is sent the whole object, not a summary, and that a Secret's data is base64-encoded rather than encrypted. Be able to name what else rides along: the previous object on an update and who made the request.
Explain the push model precisely — the match rules decide what arrives, independent of the engine's own permissions — and list what the request does not carry: any other object, any cluster state, any history.
Turn it into design: audit which resources your match actually selects, drop the ones no rule reasons about, and treat the engine's endpoint and pods as holding the most sensitive data in the cluster.
Be ready to argue match scope as a security boundary in a review where the ask is broader coverage, and to say what a rule that needs cluster-wide context should cost before you approve it.
## What is in the request When a request matches a registered webhook, the API server sends the engine an `AdmissionReview` whose `request` field carries: - **`object`** — the object under review, complete. - **`oldObject`** — the previously stored version, on an update or a delete (on a delete there is an old object and no new one). - **`userInfo`** — the username, UID and groups of whoever made the request. - **`operation`**, **`kind`**, **`resource`**, **`subresource`**, **`namespace`**, **`name`**, a request **`uid`**, and **`dryRun`**. Two things about this list matter far more than the rest. ## The body is complete, not a projection The API server does not know which fields your rule inspects, so it cannot send a subset. A rule that only checks `metadata.labels` is still handed the entire object. For most resources that is unremarkable. For Secrets it is the whole point: a Secret's `data` is base64-encoded, not encrypted, so a match that includes `secrets` means every credential written anywhere in the cluster crosses the network to the engine's pods, in full, and an update to rotate a credential delivers both the old and the new value in one request. Notice how that read path came about. Nobody granted the engine permission to read Secrets. Admission is a **push**: the API server sends what the match selects, regardless of what the engine's own credentials would allow it to fetch. The match rules — the resources, the namespace and object selectors — are the *only* control over that channel. That is why narrowing a match is a least-privilege argument about the engine, not a throughput optimisation. It is also why the engine's pods, the network path to them, and anything the engine writes down about a decision hold some of the most sensitive data in the cluster; what the engine then does with that body is its own design problem. ## What is absent, and what that forbids The request carries **no other object, no cluster state and no history**. This is the single fact policy authors most often get wrong. From an admission request alone a rule cannot decide: - "no two Ingresses may claim the same hostname" — it cannot see the other Ingresses; - "this namespace already has ten Deployments" — it cannot count them; - "this image is on the approved list" — unless the list travels with the rules; - "this user did the same thing an hour ago" — there is no history. Anything of that shape needs state the request does not contain, which means either shipping the data to the engine as configuration or giving the engine credentials to fetch it live — a decision with its own consequences, because it adds a second privilege channel alongside the pushed objects. ## Practical consequences - **Match only what the rules need.** If no rule reasons about Secrets, no Secret should ever be sent. The same goes for ConfigMaps, which routinely hold connection strings and tokens. - **Do not confuse the two channels.** The engine having no read permission on a resource does not stop that resource's bodies arriving at its endpoint. - **Use `oldObject` deliberately.** It is what makes update rules possible — comparing what changed rather than judging the new object alone — and it is also a doubling of exposure on every update you match. - **Design rules around what the request contains.** A rule that needs the rest of the cluster to decide is a rule that will either be weakened, be moved to a check that runs after the fact, or push you into granting the engine standing access.
- The engine's ServiceAccount has no read permission on Secrets. Does that stop it seeing them?No. Admission is a push: the API server sends the bodies of whatever the match selects, and it does not consult the engine's own permissions to do it. The engine's credentials govern what it can go and fetch; the match governs what arrives unasked. Only narrowing the match closes that channel.
- Give an example of a rule that cannot be decided from an admission request alone.Anything needing another object or history: uniqueness of an Ingress hostname across the namespace, how much quota is already consumed, whether an image appears on a list held elsewhere, or whether the same change was attempted before. The request carries only the object under review, its previous version, and who asked.
- What extra data does an update send compared with a create?`oldObject`, the previously stored version in full. It is what lets a rule reason about the change rather than the end state — for example, allowing a field to stay wrong but not to get worse — and it also means a matched Secret rotation hands the engine the old and new credential in the same request.
saying these in an interview costs you the question
- Believes the engine only receives the fields its rule reads
- Thinks a rule can look up other objects during admission
- Assumes the engine's own permissions limit which bodies reach it
- Says the API server redacts Secret data before admission
- Treats a narrow match as purely a performance tuning knob