Group 3 of the #114 sweep. ONE route - not one per node group. #114 describes
this chart as rendering "per node group"; its SERVICES and StatefulSets are,
the routing object is not and must not be. The route targets exactly
ingress.nodeGroup's own http Service and keeps both of the Ingress's guards,
because the WebUI's setup and management screens are not safe to spread over a
group that may not run the management tasks. ingress.nodeGroup is therefore
shared by both modes: it names where the management surface is published from,
which is not a property of the Ingress.
THE MAIL CERTIFICATE STAYS, and this is the only chart in the sweep where
anything of the TLS path does. stalwart.certificate.yaml covers STARTTLS on 25
and implicit TLS on 465/993; those connections pass through neither an ingress
controller nor a Gateway. So in Gateway mode ingress.domain is still required
(the certificate's dnsNames read it), ingress.clusterIssuer is still required
unless certificate.clusterIssuer is set, and the Certificate must NOT be
removed as part of a migration - deleting it drops SMTP and IMAP back to
Stalwart's self-signed certificate, with clients seeing a name mismatch while
every Kubernetes object stays green. Verified in the render: Gateway mode emits
Certificate + HTTPRoute and no Ingress.
WHAT THIS MODE DROPS - AND HERE IT BREAKS A SECOND CHART OF THE SAME GROUP.
Measured on 2026-10-12, the live Ingress on mail.bekemeyer-klein.de carried,
all of it operator-supplied through ingress.annotations (this chart sets none
of it):
enable-cors: true
cors-allow-origin: https://webmail.bekemeyer-klein.de
cors-allow-methods: GET, POST, OPTIONS
cors-allow-headers: Authorization, Content-Type
That origin is BULWARK, the webmail chart in this same group. Without the CORS
configuration the browser-side webmail cannot call this server's JMAP endpoint
at all, and the failure is entirely client-side: a console error, nothing in
the server's logs.
AND "no counterpart" WOULD HAVE BEEN WRONG. Measured by grepping the CRDs this
repo pins (gateway-api v1.6.3 standard channel, ci/gateway-api-crds.sh) rather
than inferred: HTTPRoute's filters carry a full 'cors' field (GEP-1767) with
allowOrigins/allowMethods/allowHeaders/allowCredentials/maxAge, and its
documented level - verbatim from that CRD - is "Support: Extended".
responseHeaderModifier is in the same CRD and is also Extended, not Core.
The chart still does not render it, for three reasons in order:
1. The chart has NO CORS values. The live configuration is the operator's,
and the chart sets no CORS annotations in Ingress mode either - so
rendering a filter means inventing a values surface that would exist in
Gateway mode only. That asymmetry is its own defect and its own work.
2. Extended support is not support. A rejected route is rejected WHOLE,
which would take the WebUI and JMAP down together (the reasoning #119
reached for statusCode); and the observed failure is quieter - Cilium
accepts filters.type: CORS and forwards the preflight upstream anyway
(cilium/cilium#45272), i.e. Accepted: True with the behaviour absent.
3. Half a CORS is worse than none, and the translatable half is the useless
one HERE. A responseHeaderModifier can add the Access-Control-Allow-*
headers; the PREFLIGHT has to be answered BY THE GATEWAY, which the CORS
filter's own contract states and a bare header filter cannot do. Because
the allowed headers include Authorization, EVERY webmail request is
preflighted - so the expressible half is exactly the half that does not
help.
Named, not rebuilt (decision 2 of #114), in values.yaml, in the schema and in
the Gateway branch of NOTES.txt, with the operational instruction: configure
CORS at the Gateway with that implementation's own support BEFORE moving this
host, and test it from the webmail in a browser - curl sends no preflight, so
it passes while the webmail fails.
NOTES.txt now asks $published (ingress.enabled OR httpRoute.enabled) where it
asked ingress.enabled, and two paragraphs that named the ingress controller
specifically now name the Gateway in that mode: the auto-ban step (switching
API does not avoid or reset it - one proxy address instead of another) and the
NetworkPolicy paragraph, where the namespace to allow is the Gateway's data
plane rather than an ingress controller's.
Minor: additive, defaults unchanged. Empty helm template diff against
origin/main with ci/stalwart/test-values.yaml.
Part of #114