Skip to content

fix(external-secrets): move the webhook off the kubelet's port - #3360

Merged
myasnikovdaniil merged 1 commit into
mainfrom
fix/eso-webhook-port
Jul 20, 2026
Merged

myasnikovdaniil merged 1 commit into
mainfrom
fix/eso-webhook-port

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

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

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

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.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 port collision issue where the external-secrets webhook shared the kubelet's port (10250). By moving the webhook to port 10260 and updating the Service to use a named target port, the changes eliminate potential misrouting scenarios that previously resulted in confusing TLS handshake failures. These updates ensure more robust admission control and CRD conversion stability during operator deployments.

Highlights

  • Webhook Port Migration: The external-secrets webhook port has been moved from 10250 (the kubelet's port) to 10260 to prevent connection misrouting and misleading x509 certificate errors.
  • Service Target Port Update: Updated the Service to target the webhook port by name instead of by number, ensuring a seamless rollout without traffic gaps during pod replacement.
  • Regression Testing: Added comprehensive helm unit tests to verify the new port configuration, ensure the Service correctly targets the port name, and confirm admission failure policies.
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.

Ignored Files
  • Ignored by pattern: **/*.patch (1)
    • packages/system/external-secrets-operator/patches/webhook-service-named-target-port.patch
  • Ignored by pattern: **/charts/** (1)
    • packages/system/external-secrets-operator/charts/external-secrets/templates/webhook-service.yaml
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/bug Categorizes issue or PR as related to a bug labels Jul 19, 2026
@dosubot dosubot Bot added the area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) label Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The external-secrets operator chart configures its webhook on port 10260, routes Service traffic through the named webhook port, adds update-time patching, and introduces Helm regression tests for networking and admission behavior.

Changes

External Secrets webhook networking

Layer / File(s) Summary
Webhook listener configuration
packages/system/external-secrets-operator/values.yaml
Adds the webhook port override at 10260 with configuration comments.
Named webhook Service routing
packages/system/external-secrets-operator/charts/external-secrets/templates/webhook-service.yaml, packages/system/external-secrets-operator/patches/*, packages/system/external-secrets-operator/Makefile
Changes the Service target port to the named webhook port and applies that patch during chart updates.
Helm regression validation
packages/system/external-secrets-operator/tests/*, packages/system/external-secrets-operator/Makefile
Adds Helm assertions for port 10260, named Service routing, network settings, admission policies, and CRD conversion, plus a test target.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: kvaps

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: moving the external-secrets webhook off port 10250 to avoid the kubelet port conflict.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/eso-webhook-port

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request configures the external-secrets-operator package to run its webhook on port 10260 instead of the default 10250 to prevent certificate validation errors caused by traffic misrouting to the kubelet. It adds a new Helm unit test suite to verify these port configurations and admission policies, and updates the Makefile to apply a downstream patch and run these tests. The review feedback suggests adding a trailing newline to the Makefile to comply with standard POSIX formatting.

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.

# the named targetPort makes patch(1) silently reverse the hunk and exit
# 0, quietly restoring the numeric form. With it, an already-applied
# patch fails loudly instead.
patch --no-backup-if-mismatch --forward -p4 < patches/webhook-service-named-target-port.patch No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

The Makefile is missing a newline at the end of the file. It is standard practice to ensure all source files end with a newline character to adhere to POSIX standards and avoid warnings from various development tools.

	patch --no-backup-if-mismatch --forward -p4 < patches/webhook-service-named-target-port.patch

The chart defaults the validating webhook to port 10250, which the
kubelet also serves on. When a connection to the webhook Service
resolves to a node IP rather than a Pod IP it reaches the kubelet,
which completes the TLS handshake with its node serving certificate,
and the apiserver rejects the call with an x509 error naming the node
instead of the webhook Service. The certificate is valid, just the
wrong server's, so the failure reads as a PKI fault and hides the
misroute behind it. Moving the listener to a port nothing else answers
on turns the same misroute into a connection refusal, which names the
real problem. This does not prevent the misroute.

9443, the controller-runtime default this chart would otherwise take,
is disqualified: the etcd kube-rbac-proxy binds it on host-network, so
it would swap one wrong certificate for another. Nothing in the tree
binds 10260 in a node network namespace. It remains
CloudControllerManagerWebhookPort in k8s.io/cloud-provider, so a
host-network cloud-controller-manager added later has to keep clear of
it.

Point the Service at the "webhook" container-port name so the move is
safe. Upstream renders targetPort from webhook.port as a number, which
every endpoint shares regardless of what its Pod listens on, so
changing the port alone would aim the Service at a port the outgoing
Pod does not serve until the replacement passes readiness. That window
rejects admission for externalsecrets, secretstores and
clustersecretstores, and breaks CRD conversion, which routes through
the same Service. No values-only setting closes it; a named targetPort
resolves per-Pod and does.

Add unit tests pinning the listener flag, the containerPort and the
named targetPort. This chart's values.schema.json allows unknown keys,
so a renamed upstream value would be silently ignored and the port
would revert to 10250 with nothing else noticing.

Assisted-By: Claude <[email protected]>
Signed-off-by: Aleksei Sviridkin <[email protected]>

@myasnikovdaniil myasnikovdaniil left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — this was the highest-severity item in the series and it holds up under the most rigorous check available short of a live cluster. The failurePolicy: Fail gap is real and the patch genuinely closes it.

Business context: same trap as cert-manager (#3359) — the external-secrets webhook listens on 10250, the kubelet's port, so a misroute to a node IP yields a valid-but-wrong certificate instead of an honest connection refused. The difference is that here the Service targets the port by number, so a bare port change would have opened a real admission and conversion outage on rollout.

Verified end to end

The gap without the patch is real. Upstream renders targetPort: {{ .Values.webhook.port }} as an integer. With replicaCount: 1 (charts/external-secrets/values.yaml:248), changing only the port would leave the Service's single endpoint pointing at 10260 on a pod still serving 10250 — every admission call for externalsecrets / secretstores / clustersecretstores and CRD conversion failing until the replacement passes readiness. failurePolicy: Fail (values.yaml:258) makes that a hard cluster-wide outage, not a degradation. The PR is right that surge-first does not help when both pods sit behind one port number.

The patch closes it. The containerPort is already named webhook upstream (charts/external-secrets/templates/webhook-deployment.yaml:78-80), so the named targetPort resolves per-pod and the old and new pods are each reached correctly during the overlap. The metrics port in the same Service was already named, which confirms the PR's "consistent with its neighbour" framing rather than introducing a new convention.

I exercised the patch mechanics rather than reading them. Restored the pristine upstream file from origin/main and ran the exact Makefile command: patch --no-backup-if-mismatch --forward -p4 applied cleanly (exit 0) and produced exactly the committed state — targetPort: webhook at line 26, targetPort: metrics at line 32. -p4 is the correct strip depth given make's working directory is the package dir.

The --forward guard works as documented. Re-running against the already-patched tree produces "Reversed (or previously applied) patch detected! Skipping patch." and exit 1, so make update aborts loudly. That is the intended tripwire for the eventual chart bump past 0.19.1, and it is not theoretical.

The test is load-bearing here in a way it is not for #3359. external-secrets' values.schema.json contains zero additionalProperties entries, so unlike cert-manager, an upstream rename of webhook.port would be silently accepted and ignored — reverting to 10250 with no error anywhere. The test suite really is the only guard, and the PR states this correctly. helm unittest . → 6 tests, all pass.

No maintenance note needed. Helm's kind sorter applies Service before Deployment in the same release, and with the named targetPort there is no window with zero ready endpoints.

Follow-ups (non-blocking)

1. The exclusivity claim goes stale at merge. packages/system/external-secrets-operator/values.yaml:40 states "Nothing in this repo binds 10260." Once #3359 lands, cert-manager binds it (packages/system/cert-manager/values.yaml:41), so the sentence becomes literally false regardless of merge order.

Note #3359 got this right by scoping to the network namespace — "nothing answers on it in the node's network namespace" — which stays true because the external-secrets webhook is pod-netns. Suggest rescoping this sentence the same way.

2. Note the sibling under hostNetwork. After both PRs land, cert-manager's webhook and this one both target 10260. Harmless as shipped (both pod-netns), but both charts document hostNetwork as the supported remedy for managed clusters where the apiserver cannot reach pod IPs. Enabling it on both gives the second pod bind: address already in use — exactly what #3361 avoids for metrics-server. The hostNetwork caveat at values.yaml:48-50 currently only warns about the cilium network policy; one sentence naming cert-manager would close it.

Note on merge order

None required for this series — metrics-server (#3361) moves 10250 → 4443 and never transits 10260, and its hostNetwork is off by default and enabled nowhere in the tree.

@myasnikovdaniil
myasnikovdaniil merged commit 8af4c1d into main Jul 20, 2026
16 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the fix/eso-webhook-port branch July 20, 2026 07:26
myasnikovdaniil added a commit that referenced this pull request Jul 20, 2026
## 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 -->
myasnikovdaniil added a commit that referenced this pull request Jul 20, 2026
…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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants