Skip to content

fix: echo request TypeMeta in admission response to comply with Kubernetes webhook spec#304

Open
dom-colangelo wants to merge 1 commit into
aws:masterfrom
dom-colangelo:fix/admission-review-typemeta-echo
Open

fix: echo request TypeMeta in admission response to comply with Kubernetes webhook spec#304
dom-colangelo wants to merge 1 commit into
aws:masterfrom
dom-colangelo:fix/admission-review-typemeta-echo

Conversation

@dom-colangelo
Copy link
Copy Markdown

Problem

The Kubernetes webhook specification requires that a webhook response include the same apiVersion and kind as the request. The handler was building the response AdmissionReview struct without copying TypeMeta from the request, so every response serialized as:

{"response": {"uid": "...", "allowed": true, "patch": "..."}}

API servers enforce this strictly:

received invalid webhook response: expected webhook response of admission.k8s.io/v1,
Kind=AdmissionReview, got /, Kind=

This is a known issue — see #225, where an EKS cluster upgraded to Kubernetes 1.27 with admissionReviewVersions: v1 began rejecting webhook responses and injection stopped working. The only workaround was to downgrade to v1beta1. The root cause is the same: when the API server negotiates v1, the response comes back with an empty apiVersion/kind and is rejected.

A related issue: deserializer.Decode used a codec scheme that registered only corev1 and admissionregistrationv1beta1 — not the admission API types. Any cluster sending admission.k8s.io/v1 requests would fail to decode before mutation ran.

Fix

pkg/handler/handler.go

  1. Copy ar.TypeMeta into the response struct before marshalling — the response echoes back whatever apiVersion/kind the request carried, as the spec requires.
  2. Replace deserializer.Decode with json.Unmarshal. json.Unmarshal decodes both v1 and v1beta1 payloads correctly without a registered scheme. The old codec scheme did not include the admission API types, so it was incapable of decoding v1 requests.
  3. Remove the now-unused runtimeScheme, codecs, deserializer package vars and init().

pkg/handler/handler_test.go

  1. Update two test cases for the new json.Unmarshal error strings.
  2. Add regression test ValidRequestV1TypeMetaEchoed: sends an AdmissionReview with apiVersion: admission.k8s.io/v1 and asserts the response echoes it back.

Testing

go test ./pkg/handler/...

All tests pass.

Related

…h json.Unmarshal

The webhook was returning AdmissionReview responses with an empty
apiVersion and kind, causing API servers (including K3s and vanilla
upstream Kubernetes) to reject the webhook response with:
  "received invalid webhook response: got /, Kind="

Root cause: the response AdmissionReview struct was never given a
TypeMeta, so it serialized as {"response": ...} with no apiVersion or
kind fields. EKS patches its API server to tolerate missing TypeMeta,
but upstream Kubernetes strictly requires it.

Fix: copy ar.TypeMeta into the response before marshalling, so the
response echoes back whatever apiVersion/kind the request carried.

The deserializer.Decode call is also replaced with json.Unmarshal.
The old codec scheme only registered corev1 and
admissionregistrationv1beta1, not admission/v1 or admission/v1beta1,
so decode would fail on v1 AdmissionReview requests from Kubernetes
>= 1.16. json.Unmarshal decodes both v1beta1 and v1 payloads without
a registered scheme.

The runtimeScheme/codecs/deserializer package-level vars and the init()
that populated the scheme are removed as they are no longer used.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant