Repository navigation
fix(cert-manager): move the webhook off the kubelet's port - #3359
Conversation
The vendored chart defaults webhook.securePort to 10250, which is the port the kubelet serves on. Upstream picks that value deliberately, for GKE private clusters where apiservers may reach nodes only on 443 and 10250; it is free of consequence there and unsafe here. When a connection to the webhook Service resolves to a node IP instead of a Pod IP, it reaches the kubelet, which completes the TLS handshake with its node serving certificate. The apiserver then rejects every cert-manager admission call in the cluster with x509: certificate is valid for <node>, not cert-manager-webhook.cozy-cert-manager.svc and, because the webhook is failurePolicy: Fail, that stalls platform bootstrap. The certificate is valid; it is simply the wrong server's. The error names cert-manager and points away from the routing layer that actually failed, which makes a rare misroute expensive to diagnose. Serve on 10260 instead, the port upstream's troubleshooting guide prescribes for this exact error. It documents the failure on EKS Fargate, where a microVM forwards every port to the pod except the one its own kubelet already holds; the cause there is understood and ours is not, but the shape is the same, down to the certificate that comes back naming the node. What the fix needs of the port is that nothing answers on it in a node's network namespace, and nothing in the platform does. The same misroute is then refused at connect time, which is an honest and immediately diagnosable error. This does not prevent the misroute, whose cause is unestablished; it removes the collision that turns one into a cluster-wide failure attributed to the wrong component. The Service targets the container port by name, so it follows the change without an edit, and a rolling update resolves the old and new ports per Pod rather than dropping the endpoint. Tests pin the listener flag together with the advertised containerPort so the two cannot drift apart. Assisted-By: Claude <[email protected]> Signed-off-by: Aleksei Sviridkin <[email protected]>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical issue where the cert-manager webhook, by defaulting to port 10250, conflicts with the kubelet. This collision causes TLS handshake failures when traffic is misrouted to a node IP, leading to cluster-wide admission errors. By moving the webhook to port 10260, the change ensures that such misroutes result in clear connection refusals rather than misleading certificate errors, while also adding automated tests to prevent future regressions. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughCert-manager’s webhook secure port is set to 10260. A new test suite verifies the deployment port configuration, disabled host networking, and Service routing through the ChangesCert-manager webhook port
Estimated code review effort: 1 (Trivial) | ~5 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request configures the cert-manager webhook to serve on port 10260 instead of the default 10250 to prevent port collision with the kubelet and avoid misrouted TLS handshake errors. It also introduces a Helm unit test suite to verify this configuration. Feedback on the test suite points out that the notExists assertion is not supported by helm-unittest and should be replaced with isNull.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| - notExists: | ||
| path: spec.template.spec.hostNetwork |
myasnikovdaniil
left a comment
There was a problem hiding this comment.
LGTM — correct diagnosis, minimal surface, upgrade-safe by construction, and the reasoning is preserved where the next maintainer will actually read it.
Business context: the cert-manager webhook listens on 10250, the kubelet's port, so a connection that lands on a node IP instead of the pod IP reaches the kubelet and gets its valid-but-wrong certificate — admission calls fail with an x509 error that points at cert-manager rather than at the routing, and bootstrap stalls entirely.
Reviewed together with #3360, #3361 and #3362 as a series, since all four contend for ports in the node network namespace.
Verified
- The upgrade-safety claim holds.
charts/cert-manager/values.yaml:756(replicaCount: 1) and:811(strategy: {}) mean Kubernetes defaults tomaxUnavailable: 25% → 0/maxSurge: 25% → 1, i.e. surge-first: the replacement must pass readiness before the old pod is removed. The Service targetstargetPort: httpsby name, so the EndpointSlice controller resolves per-pod during the overlap — old pod on 10250, new pod on 10260, each reached correctly. There is no window with zero ready endpoints, so no maintenance note is needed. The PR's own statement that a numerictargetPort"would have broken cluster-wide admission" is accurate — that is exactly the hazard #3360 had to patch around. - The schema reasoning is sound.
charts/cert-manager/values.schema.jsoncarries 28"additionalProperties": falseentries andsecurePortis a defined key (:1893,:2248), so an upstream rename would fail helm loudly rather than silently ignore the override. That justifies the test comment's decision not to cover that case. (Worth contrasting with #3360, where the opposite is true and the test really is the only guard.) - The premise is corroborated upstream in cert-manager's own words —
values.schema.json:2017: "Since the default port for the webhook conflicts with kubelet on the host network,webhook.securePortshould be changed to an available port if running in hostNetwork mode." - 10260 is genuinely free. The literal appears nowhere else in the tree as a port. The aggregate host-netns TCP set across every default-enabled workload is {2379, 2380, 6641-6643, 7946, 8080, 8081, 9100, 9120, 9140, 9141, 9234, 9443, 9690-9693, 9879, 9963, 9964, 10250, 10257, 10259, 10660, 10661, 10665, 19443, 3366, 3367, 7572} — no collision. No Talos service conflicts either: the repo ships no Talos machine-config at all (
packages/core/talos/is imager profiles only). - Tests run in CI.
packages/system/cert-manager/Makefile:6-7already had atest:target on main andhack/helm-unit-tests.sh:22gates discovery on exactly that, so the new suite is picked up.helm unittest .→ 2 suites, 4 tests, all pass. hostNetwork: falsedefault confirmed atcharts/cert-manager/values.yaml:1085.
Follow-up (non-blocking)
Worth one sentence about the sibling. After this and #3360 both land, cert-manager's webhook and the external-secrets webhook both target 10260. That is harmless as shipped — both are pod-netns, so each binds 10260 in its own namespace. The trap is that both charts document hostNetwork as the supported remedy for managed clusters where the apiserver cannot reach pod IPs (cert-manager's schema says so verbatim, quoted above). An operator who enables hostNetwork on both webhooks gets the second pod CrashLooping with bind: address already in use — the exact failure mode #3361 exists to avoid for metrics-server.
The hostNetwork caveat at values.yaml:37-40 currently only warns about the cilium network policy. Suggest adding: "external-secrets' webhook also serves 10260; enabling hostNetwork on both requires giving one of them a different port."
Note on merge order
None required for this series. The concern that #3361 might transiently squat 10260 does not apply — metrics-server moves 10250 → 4443 and never transits 10260, and its hostNetwork is off by default (metrics-server/charts/metrics-server/values.yaml:70-76) and enabled nowhere in the tree. Merge in any order.
## What this PR does The external-secrets webhook listens on port 10250 — the kubelet's port — with `failurePolicy: Fail`. This is the same trap being removed from cert-manager in #3359: a connection to the webhook Service that resolves to a node IP instead of the pod IP reaches the kubelet, which completes the handshake with its own node serving certificate, and the API server rejects the call with an x509 error that blames the webhook's PKI rather than the routing layer. The port collision does not cause the misroute; it turns a rare transient one into a confidently misattributed failure. The blast radius here is wider than admission alone: `crds.conversion.enabled` defaults to true and points every ExternalSecret CRD's conversion webhook at the same Service, so a misrouted connection also breaks CRD conversion — reads at non-storage versions, not just writes. The webhook moves to 10260, matching #3359. The controller-runtime default 9443 was considered and rejected: the etcd kube-rbac-proxy binds it on host network, which would reproduce exactly the same wrong-certificate failure with a different certificate. Nothing in the tree binds 10260. Unlike cert-manager, the upstream chart's Service targets the port numerically — `targetPort: {{ .Values.webhook.port }}` — and a numeric targetPort is copied into every endpoint verbatim rather than resolved per pod. With one replica, a port change would direct traffic at a port the still-running old pod does not serve while the replacement is not yet Ready, failing every admission and conversion call in the gap; surge-first rollout does not help because both pods sit behind the same number. A one-line vendored-chart patch points the Service at the `webhook` container-port name upstream already declares (the metrics port in the same Service is already named), which closes the gap: during the overlap each pod is routed on the port it actually serves. The patch is applied by `make update` with `--forward`, so an already-applied hunk fails loudly instead of being silently reversed. Upstream has already made the same change — merged as external-secrets/external-secrets#5108 and shipped since chart 0.19.1 — but the vendored chart here is 0.10.4, nine minor releases earlier, so the patch carries the fix until the chart is bumped past that point. The `--forward` guard is exactly what will flag that moment: on a bumped chart the patch fails loudly, signalling it should be dropped. external-secrets-operator ships disabled by default and is enabled per-cluster via `bundles.enabledPackages`, so this is defensive hardening for opt-in installs rather than a response to an observed incident. `metrics-server` is a third instance of the same port collision, left out here: it serves an APIService rather than an admission webhook, a lesser failure class deserving its own change. ### Release note ```release-note fix(external-secrets): move the webhook off port 10250, which the kubelet also serves, and make its Service target the port by name so the move rolls out without an admission gap ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved webhook Service routing by switching to the named `targetPort` (`webhook`) for pod traffic. * Configured the webhook listener to run on port **10260** to avoid prior connection/TLS certificate misrouting issues. * Preserved admission validation behavior and CRD conversion routing. * **Tests** * Added Helm chart regression tests covering webhook port wiring, networking settings, failure policy behavior, and CRD conversion. * **Chores** * Added Helm chart unit testing to the development workflow and extended the chart update process to apply the webhook routing adjustment. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What this PR does metrics-server is the third component serving on port 10250 — the kubelet's port — after the webhooks addressed in #3359 and #3360. It ships default-enabled in the platform bundle and is also installed into every tenant Kubernetes cluster with no value overrides, so a package-root override propagates to both. The hard defect here is different from the webhook cases. The chart has first-class `hostNetwork.enabled` support, documented upstream for clusters where the API server cannot reach pod IPs — and on the default port that mode cannot start at all: the kubelet already holds 10250, so the process dies with `bind: address already in use`. Upstream is aware (kubernetes-sigs/metrics-server#1026 carries the exact panic) and declined to change the chart default, advising hostNetwork users to set their own port. This override is that port, set ahead of need. The misleading-certificate angle of the sibling fixes mostly does not apply: the chart defaults `apiService.insecureSkipTLSVerify` to true, so a connection misrouted to a node would fail silently rather than with a wrong-certificate error — and with endpoints resolving to pod IPs such a misroute is not established to occur here at all. The comment states that as a hypothesis, not a fact. What is factual is the consumer set: `kubectl top`, tenant HPAs, and the console's cluster-usage page all read `metrics.k8s.io`; the shipped VPA does not — it is configured with Prometheus storage. The port is 4443 rather than the siblings' 10260, for a reason worth keeping: metrics-server is the one component of the three with supported hostNetwork, and parking it on 10260 would put a live host-network listener on exactly the port the webhook fixes rely on being silent — turning their honest connection refusals back into answered handshakes. 4443 is the value metrics-server's own manifests shipped through v0.6.4, before v0.7.0 moved them to 10250; it sits outside the 10248-10259 node-component block, and nothing in the tree binds it. No chart patch is needed: the Service, both probes and the ServiceMonitor already reference the port by name, so the rollout-gap that required a vendored patch in #3360 does not exist here. The package also gains the standard `test:` target it was missing. Not verified without a cluster: that the aggregated APIService stays Available through an upgrade, and that no supported substrate filters 4443 inbound on nodes — the tree configures no host firewall, but Talos and managed substrates are outside what a render can prove. There is no e2e coverage of `metrics.k8s.io` at all; a wrong guess here would surface as HPAs silently not scaling. ### Release note ```release-note fix(metrics-server): move the listener off port 10250, which the kubelet also holds. The default made hostNetwork mode unable to start at all, and the new port keeps node-port collisions out of the metrics path. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Configured the metrics server to listen on secure port 4443. * Updated service routing and health checks to use the named HTTPS port, improving reliability when port settings change. * Ensured pod networking does not rely on host networking. * **Tests** * Added automated chart tests covering container ports, service routing, and liveness/readiness probes. * Added a test command for running the chart test suite. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
Created backport PR for
Please cherry-pick the changes locally and resolve any conflicts. git fetch origin backport-3359-to-release-1.5
git worktree add --checkout .worktree/backport-3359-to-release-1.5 backport-3359-to-release-1.5
cd .worktree/backport-3359-to-release-1.5
git reset --hard HEAD^
git cherry-pick -x df85632c8909e775580a7c8732dffae8b8e473ca
git push --force-with-lease |
…opes (#3362) ## What this PR does PRs titled with the `cert-manager`, `external-secrets` or `metrics-server` scopes landed on `area/uncategorized` because the labeler's scope map had no entry for them — all three surfaced at once in the current port-collision series (#3359, #3360, #3361). metrics-server feeds the monitoring pipeline, so it maps to `area/monitoring`; cert-manager and external-secrets are platform services, so they map to `area/platform`. ### Release note ```release-note NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Chores** - Improved pull request labeling automation. - Pull requests involving metrics, certificate management, and external secrets are now categorized more accurately with the appropriate area labels. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## What this PR does The cert-manager webhook listens on port 10250 — the kubelet's port. When a connection to the webhook Service resolves to a node IP rather than the pod IP, it reaches the kubelet, which answers the TLS handshake with its own node serving certificate. The API server then rejects every cert-manager admission call in the cluster: ```text failed calling webhook "webhook.cert-manager.io": ... x509: certificate is valid for srv3, not cert-manager-webhook.cozy-cert-manager.svc ``` The certificate is valid — it is simply the kubelet's, issued for the node name. That signature is what separates this from the cainjector failure mode, which reports `certificate signed by unknown authority`; that possibility was checked and ruled out on the run where this was observed. Here it blocked the whole platform bootstrap and pointed the operator at cert-manager rather than at the routing layer. The value comes straight from the vendored chart, where upstream chose it deliberately for a different constraint: on GKE private clusters the API server may only reach nodes on 443 and 10250. cozystack did not override it. Moving the webhook to 10260 is upstream's own remedy for this exact error. The cert-manager troubleshooting guide documents the identical signature — under a different cause, EKS Fargate, where each microVM runs a kubelet that already holds 10250 — and prescribes `--set webhook.securePort=10260`. This does not fix the misroute, whose root cause is still unestablished; the run carries no dataplane state, so candidates such as a stale service map during bootstrap remain unproven. What it removes is the trap that turns a rare, transient misroute into a cluster-wide failure attributed to the wrong component. With the collision gone, the same misroute surfaces as a connection refusal. The override lives in the package-root values file, which `make update` leaves alone, so it survives re-vendoring. ### Upgrade safety The rendered webhook Deployment has one replica and no explicit strategy, so the defaults apply: `maxUnavailable` 0 and `maxSurge` 1. The replacement Pod must become Ready before the old one is removed. The Service targets a named port rather than a number, so during the overlap the EndpointSlice controller routes to each Pod on the port it actually serves. There is no window with zero ready endpoints, and no maintenance step is required. Had the Service pinned the port numerically, this upgrade itself would have broken cluster-wide admission. ### Not addressed here `external-secrets-operator` runs its webhook on 10250 with the same `failurePolicy: Fail`, so it carries the identical exposure and deserves the same treatment; `metrics-server` also uses 10250 but is an APIService rather than an admission webhook. Both are left out to keep this change to one component. Closes #3355 ### Release note ```release-note fix(cert-manager): move the admission webhook off port 10250, which the kubelet also serves. A connection misrouted to a node answered with the kubelet's certificate, failing every cert-manager admission call cluster-wide with a misleading x509 error. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Configured the cert-manager webhook to use secure port 10260. * Added coverage to verify secure port configuration, HTTPS service routing, and disabled host networking. * **Documentation** * Added configuration comments explaining the secure port selection and its role in avoiding service routing conflicts. <!-- end of auto-generated comment: release notes by coderabbit.ai --> (cherry picked from commit 3affec9) (cherry picked from commit ee8344d7bf782a608574ff3a064cb5c766ac49ff)
Backport of #3359 onto release-1.5. The cert-manager webhook listened on 10250, the port the kubelet also serves. When a connection to the webhook Service resolved to a node IP rather than a Pod IP it reached the kubelet, which completed the TLS handshake with its own node serving certificate, and the API server then rejected every cert-manager admission call cluster-wide: failed calling webhook "webhook.cert-manager.io": ... x509: certificate is valid for srv3, not cert-manager-webhook.cozy-cert-manager.svc The certificate is valid — it is simply the wrong server's — so the error reads as a cert-manager fault and hides the misroute that caused it. Moving the webhook to 10260 is upstream's own remedy for this signature. It does not fix the misroute; it removes the collision that turns a rare transient one into a cluster-wide outage attributed to the wrong component. The override lives in the package-root values file, which `make update` leaves alone, so it survives re-vendoring. Stacked on the #3199 backport (#3202): release-1.5 ships an EMPTY packages/system/cert-manager/values.yaml, so every resource override in that file arrived after v1.5.2. The bot's cherry-pick of this fix conflicted for that reason alone — nothing to merge into — and applies cleanly once the cainjector backport is in place. Upgrade safety is unchanged from the original: one replica, no explicit strategy, so maxUnavailable 0 / maxSurge 1 apply and the replacement Pod must be Ready before the old one goes. The Service targets a NAMED port, so during the overlap each Pod is reached on the port it actually serves and there is no window with zero ready endpoints. Closes #3355 (cherry picked from commit 3affec9) Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
What this PR does
The cert-manager webhook listens on port 10250 — the kubelet's port. When a connection to the webhook Service resolves to a node IP rather than the pod IP, it reaches the kubelet, which answers the TLS handshake with its own node serving certificate. The API server then rejects every cert-manager admission call in the cluster:
The certificate is valid — it is simply the kubelet's, issued for the node name. That signature is what separates this from the cainjector failure mode, which reports
certificate signed by unknown authority; that possibility was checked and ruled out on the run where this was observed. Here it blocked the whole platform bootstrap and pointed the operator at cert-manager rather than at the routing layer.The value comes straight from the vendored chart, where upstream chose it deliberately for a different constraint: on GKE private clusters the API server may only reach nodes on 443 and 10250. cozystack did not override it.
Moving the webhook to 10260 is upstream's own remedy for this exact error. The cert-manager troubleshooting guide documents the identical signature — under a different cause, EKS Fargate, where each microVM runs a kubelet that already holds 10250 — and prescribes
--set webhook.securePort=10260.This does not fix the misroute, whose root cause is still unestablished; the run carries no dataplane state, so candidates such as a stale service map during bootstrap remain unproven. What it removes is the trap that turns a rare, transient misroute into a cluster-wide failure attributed to the wrong component. With the collision gone, the same misroute surfaces as a connection refusal.
The override lives in the package-root values file, which
make updateleaves alone, so it survives re-vendoring.Upgrade safety
The rendered webhook Deployment has one replica and no explicit strategy, so the defaults apply:
maxUnavailable0 andmaxSurge1. The replacement Pod must become Ready before the old one is removed. The Service targets a named port rather than a number, so during the overlap the EndpointSlice controller routes to each Pod on the port it actually serves. There is no window with zero ready endpoints, and no maintenance step is required. Had the Service pinned the port numerically, this upgrade itself would have broken cluster-wide admission.Not addressed here
external-secrets-operatorruns its webhook on 10250 with the samefailurePolicy: Fail, so it carries the identical exposure and deserves the same treatment;metrics-serveralso uses 10250 but is an APIService rather than an admission webhook. Both are left out to keep this change to one component.Closes #3355
Release note
Summary by CodeRabbit
New Features
Documentation