feat: enforce non-empty nodeSelector with CEL instead of the webhook - #455
feat: enforce non-empty nodeSelector with CEL instead of the webhook#455tejassinghbhati wants to merge 1 commit into
Conversation
An empty nodeSelector matches every Node in the cluster, so a rule carrying one applies its taint fleet wide. The only thing stopping that today is validateSpec in the validating webhook, and the webhook is optional and off by default, so the guard is absent on a default install. That gap is what kubernetes-sigs#403 documented from the chart side. Move the constraint onto the CRD as CEL, where it applies on every cluster regardless of whether the webhook is deployed. This continues the migration described in kubernetes-sigs#449, and does not overlap kubernetes-sigs#451, which covers the two bootstrap-only constraints. CEL cannot express whether a selector parses, so the LabelSelectorAsSelector error check stays in the webhook. The empty check is removed rather than left alongside it: CRD validation runs before validating webhooks, so that branch is now unreachable. An absent nodeSelector is still caught by the required marker rather than by CEL, because the field is omitempty/omitzero and serialises away. Both reject the object, they just report it differently, and the tests cover each path. Signed-off-by: tejassinghbhati <tejassinghbhati077@gmail.com>
✅ Deploy Preview for node-readiness-controller canceled.
|
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: tejassinghbhati The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hi @tejassinghbhati. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Tip We noticed you've done this a few times! Consider joining the org to skip this step and gain Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
Flagging an overlap with #451 before either of these merges, so nobody lands a broken combination. Four of the five files here are also touched by #451. Two spots actually conflict rather than just sitting near each other. #451 deletes the The one that matters more: in allErrs = webhook.validateNodeReadinessRule(ctx, invalidRule, false)
Expect(allErrs).To(HaveLen(1)) // Empty nodeSelector validation
Expect(allErrs[0].Field).To(Equal("spec.nodeSelector"))That assertion only holds while the webhook still rejects an empty selector, which this PR removes. So if #451 goes in first and this is rebased naively, that spec either fails or passes for the wrong reason. This PR repoints it at an unparseable selector, which is what the webhook still owns. No preference on ordering, happy to rebase on #451 if you would rather take that one first. Just did not want it discovered at merge time. /cc @vishnukothakapu |
|
@tejassinghbhati: GitHub didn't allow me to request PR reviews from the following users: vishnukothakapu. Note that only kubernetes-sigs members and repo collaborators can review this PR, and authors cannot review their own PRs. DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
Description
Continues the webhook to CEL migration from #449. This one is outside #451's scope, which covers the two bootstrap-only constraints.
An empty
spec.nodeSelectorbecomeslabels.Everything(), so a rule carrying one taints every Node in the cluster. The only guard wasvalidateSpecin the validating webhook, which is off by default, so a default install had nothing stopping it. #403 came at the same gap from the chart side, where the chart's own error text suggestednodeSelector: {}while the webhook forbade it.The constraint now lives on the CRD:
// +kubebuilder:validation:XValidation:rule="(has(self.matchLabels) && size(self.matchLabels) > 0) || (has(self.matchExpressions) && size(self.matchExpressions) > 0)",message="nodeSelector must not be empty"and the empty check is removed from the webhook rather than left beside it, since CRD validation runs before validating webhooks and the branch is unreachable once the rule is in place.
Two things I deliberately did not change:
The
LabelSelectorAsSelectorerror check stays. CEL can tell thatmatchExpressionsis non-empty but not whether the operator is one the parser accepts, so{key: k, operator: NotARealOperator}still needs Go. The webhook specs that used an empty selector as their invalid input now use an unparseable one instead, so they exercise what the webhook still owns.An absent
nodeSelectoris caught by+required, not by this rule, because the field isomitempty,omitzeroand serialises away so CEL never evaluates. I expected my rule to catch it and it does not, which is why there is a separate spec assertingspec.nodeSelector: Required valuefor that path.After this the only thing left in the webhook is cross-object taint conflict detection, which cannot move to CEL because it has to list other rules.
Related Issue
Fixes #454
Type of Change
/kind feature
/kind api-change
Testing
New
nodeSelector CEL validationspecs drive a real API server through envtest rather than calling the validator directly, since the point is that the API server enforces it now:spec.nodeSelector: Required valuenodeSelector must not be emptymatchLabels: {}present but empty, rejectedmatchExpressions: []present but empty, rejectedmatchLabelsset, accepted and persistedmatchExpressionsonly, acceptedFull suite: 83 of 83 specs, up from 78.
internal/webhook37 specs green after the three migrated ones were reworked.internal/metricsand the reporter pass.golangci-lintv2.12.1 reports 0 issues andgofmtis clean.Checklist
make testpassesmake lintpassesDoes this PR introduce a user-facing change?