feat: Gateway-API as an alternative to the Ingress in the six special shapes (group 3) #120
No files matched your search
@@ -0,0 +1,61 @@
|
|||||||
|
# Render-only overlay for ci/run-httproute-match-test.sh, applied LAST.
|
||||||
|
#
|
||||||
|
# Its one job is to put this chart into Gateway mode, because the convention
|
||||||
|
# ships with httpRoute.enabled: false and the test would otherwise be green
|
||||||
|
# over nothing. A chart that has an overlay here and still renders no
|
||||||
|
# HTTPRoute FAILS the test - that is the whole point of the file existing.
|
||||||
|
#
|
||||||
|
# An overlay and not a change to ci/stalwart/test-values.yaml: the install-test
|
||||||
|
# runs exactly ONE configuration per chart, and this chart's fixture brings up
|
||||||
|
# the node groups against a throwaway Postgres. Trading its Ingress for a CRD
|
||||||
|
# schema check that cyberchef's fixture already performs against the real
|
||||||
|
# Gateway-API CRDs would be a bad swap. The install keeps the Ingress; the
|
||||||
|
# route is held here, at render level, which is where its silent failure modes
|
||||||
|
# live.
|
||||||
|
#
|
||||||
|
# ingress.enabled: false is not optional here - ingress.enabled is this chart's
|
||||||
|
# HTTP routing switch and defaults to TRUE, so without this line the chart
|
||||||
|
# aborts its own render with both modes on.
|
||||||
|
#
|
||||||
|
# WHAT THIS OVERLAY COVERS:
|
||||||
|
#
|
||||||
|
# - ingress.domain comes from the fixture, so the route renders with its
|
||||||
|
# hostname from that single source of truth. httpRoute.hostnames stays
|
||||||
|
# empty on purpose: the fallback is the path every installation will
|
||||||
|
# actually use.
|
||||||
|
# - ingress.nodeGroup comes from the fixture too, so the route is checked to
|
||||||
|
# point at THAT GROUP'S http Service and no other. This chart is the one in
|
||||||
|
# group 3 where the backend is group-scoped, and the ONE-route-not-one-
|
||||||
|
# per-group property is exactly what would be easy to get wrong on a copy.
|
||||||
|
# - the backendRef is checked against the per-group http Service of the same
|
||||||
|
# render, by PORT NUMBER (ports.http.containerPort) rather than by the name
|
||||||
|
# "http" the Ingress uses, because backendRefs takes a PortNumber. Note
|
||||||
|
# that this chart's NodePort Services publish a DIFFERENT number
|
||||||
|
# (ports.<name>.nodePort) than the container binds, so naming the wrong one
|
||||||
|
# here would be a plausible mistake - this overlay is what catches it.
|
||||||
|
#
|
||||||
|
# No URLRewrite filter is rendered by this chart in either mode, so the #118
|
||||||
|
# prefix/redirect assurance has no precondition to meet here.
|
||||||
|
#
|
||||||
|
# NOT covered here, and none of it can be:
|
||||||
|
#
|
||||||
|
# - that the hostname answers. No Gateway controller runs in CI and no route
|
||||||
|
# reaches "Accepted: True".
|
||||||
|
# - THE CORS CONFIGURATION, which is the thing this chart actually loses in
|
||||||
|
# this mode and the one that breaks the bulwark webmail. It lives in the
|
||||||
|
# operator's ingress.annotations, the chart has no values for it, and the
|
||||||
|
# Gateway API's own CORS filter is Extended-support with observed
|
||||||
|
# implementation variance - all of which is documented in values.yaml and
|
||||||
|
# printed by NOTES.txt. Documentation is not something a test can assert.
|
||||||
|
# - that SMTP/IMAP still work. They never went through the Ingress and do not
|
||||||
|
# go through the route; the mail Certificate that serves them keeps being
|
||||||
|
# rendered in this mode, which is this chart's distinguishing feature in
|
||||||
|
# the sweep, and no render test looks at whether it was left alone.
|
||||||
|
ingress:
|
||||||
|
enabled: false
|
||||||
|
httpRoute:
|
||||||
|
enabled: true
|
||||||
|
parentRefs:
|
||||||
|
- name: public
|
||||||
|
namespace: gateway
|
||||||
|
sectionName: https
|
||||||
+1
-1
@@ -25,7 +25,7 @@ type: application
|
|||||||
# byte-for-byte identically for the defaults AND for ci/stalwart/test-values.yaml
|
# byte-for-byte identically for the defaults AND for ci/stalwart/test-values.yaml
|
||||||
# - measured, both empty diffs. New optional values with unchanged defaults is
|
# - measured, both empty diffs. New optional values with unchanged defaults is
|
||||||
# exactly the minor case (README, "Versioning").
|
# exactly the minor case (README, "Versioning").
|
||||||
version: 1.1.1+upv0.16.25
|
version: 1.2.0+upv0.16.25
|
||||||
appVersion: "v0.16.25"
|
appVersion: "v0.16.25"
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
|
{{- /*
|
||||||
|
NOTHING IN CI READS THIS FILE. `helm template` does not emit notes and no
|
||||||
|
workflow or script under ci/ reads it, so every claim below carries
|
||||||
|
review-only assurance - it is checked by being read, not by a stage. A
|
||||||
|
rendering ERROR here does fail `helm template`, which is all the pipeline can
|
||||||
|
see of it.
|
||||||
|
*/ -}}
|
||||||
{{- $release := include "stalwart.fullname" . -}}
|
{{- $release := include "stalwart.fullname" . -}}
|
||||||
{{- $nodes := fromYaml (include "stalwart.nodes" .) -}}
|
{{- $nodes := fromYaml (include "stalwart.nodes" .) -}}
|
||||||
{{- $ports := fromYaml (include "stalwart.ports" .) -}}
|
{{- $ports := fromYaml (include "stalwart.ports" .) -}}
|
||||||
{{- $valkeyHost := include "stalwart.valkeyHost" . -}}
|
{{- $valkeyHost := include "stalwart.valkeyHost" . -}}
|
||||||
|
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
||||||
|
{{- /*
|
||||||
|
"Published" rather than "has an Ingress": the two modes are alternatives, and
|
||||||
|
the paragraphs below that asked ingress.enabled were really asking whether the
|
||||||
|
WebUI and JMAP are reachable from outside at all (Gitea #114).
|
||||||
|
*/ -}}
|
||||||
|
{{- $published := or .Values.ingress.enabled $httpRoute.enabled -}}
|
||||||
|
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
||||||
|
{{- if not $hostnames -}}
|
||||||
|
{{- $hostnames = list .Values.ingress.domain -}}
|
||||||
|
{{- end -}}
|
||||||
{{- $ingressGroup := "" -}}
|
{{- $ingressGroup := "" -}}
|
||||||
{{- if .Values.ingress.enabled }}{{- $ingressGroup = include "stalwart.ingressNodeGroup" . }}{{- end -}}
|
{{- if $published }}{{- $ingressGroup = include "stalwart.ingressNodeGroup" . }}{{- end -}}
|
||||||
Stalwart {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
Stalwart {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||||
|
|
||||||
THIS CHART CONFIGURES ALMOST NOTHING, AND THAT IS NOT A GAP
|
THIS CHART CONFIGURES ALMOST NOTHING, AND THAT IS NOT A GAP
|
||||||
@@ -140,14 +158,18 @@ FIRST INSTALL — EIGHT STEPS, AND NONE OF THEM IS OPTIONAL
|
|||||||
'Cluster role "<name>" not found in registry', and it is printed AFTER the
|
'Cluster role "<name>" not found in registry', and it is printed AFTER the
|
||||||
listeners are already bound.
|
listeners are already bound.
|
||||||
|
|
||||||
4. Configure HTTP forwarding before the auto-ban notices the Ingress.
|
4. Configure HTTP forwarding before the auto-ban notices the {{ if $httpRoute.enabled }}Gateway{{ else }}Ingress{{ end }}.
|
||||||
Stalwart sees the ingress controller's pod IP for every WebUI and JMAP
|
Stalwart sees the {{ if $httpRoute.enabled }}Gateway's{{ else }}ingress controller's{{ end }} pod IP for every WebUI and JMAP
|
||||||
request, not the client's. Sooner or later the auto-ban bans that one
|
request, not the client's. Sooner or later the auto-ban bans that one
|
||||||
address and the WebUI and JMAP go dark for everyone at once. The PROXY
|
address and the WebUI and JMAP go dark for everyone at once. The PROXY
|
||||||
protocol is not the fix for HTTP: enable "forwarded" handling with
|
protocol is not the fix for HTTP: enable "forwarded" handling with
|
||||||
X-Forwarded-For evaluation, and discard forwarding headers the client
|
X-Forwarded-For evaluation, and discard forwarding headers the client
|
||||||
brought itself. The outage arrives weeks later and looks like a server
|
brought itself. The outage arrives weeks later and looks like a server
|
||||||
fault, which is why this is a step and not a tip.
|
fault, which is why this is a step and not a tip.
|
||||||
|
{{ if $httpRoute.enabled }}Switching to the Gateway API does not avoid this and does not
|
||||||
|
reset it: it is one proxy address instead of another, and whether the
|
||||||
|
Gateway sends an X-Forwarded-For at all is that implementation's business,
|
||||||
|
not something this route can state.{{ end }}
|
||||||
|
|
||||||
5. Point the TLS object at the mounted certificate.
|
5. Point the TLS object at the mounted certificate.
|
||||||
{{- if .Values.certificate.enabled }}
|
{{- if .Values.certificate.enabled }}
|
||||||
@@ -295,7 +317,7 @@ DISTRIBUTION AND DISRUPTION BUDGET — WHAT EACH GROUP ACTUALLY GOT
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.ingress.enabled }}
|
{{- if .Values.ingress.enabled }}
|
||||||
|
|
||||||
WEB UI AND JMAP
|
WEB UI AND JMAP — INGRESS MODE (ingress.enabled: true, httpRoute.enabled: false)
|
||||||
https://{{ .Values.ingress.domain }} → node group "{{ $ingressGroup }}"
|
https://{{ .Values.ingress.domain }} → node group "{{ $ingressGroup }}"
|
||||||
Certificate requested by cert-manager through the "{{ .Values.ingress.clusterIssuer }}" cluster issuer:
|
Certificate requested by cert-manager through the "{{ .Values.ingress.clusterIssuer }}" cluster issuer:
|
||||||
kubectl -n {{ .Release.Namespace }} get certificate tls-{{ $release }}-ingress
|
kubectl -n {{ .Release.Namespace }} get certificate tls-{{ $release }}-ingress
|
||||||
@@ -305,10 +327,101 @@ WEB UI AND JMAP
|
|||||||
Ingress only works while an HTTP NetworkListener on {{ (index $ports "http").containerPort }} exists in the
|
Ingress only works while an HTTP NetworkListener on {{ (index $ports "http").containerPort }} exists in the
|
||||||
database (step 3). Between finishing setup and creating that listener the
|
database (step 3). Between finishing setup and creating that listener the
|
||||||
Ingress resolves to a port that answers nothing.
|
Ingress resolves to a port that answers nothing.
|
||||||
|
{{- else if $httpRoute.enabled }}
|
||||||
|
|
||||||
|
WEB UI AND JMAP — GATEWAY MODE (httpRoute.enabled: true, ingress.enabled: false)
|
||||||
|
{{- range $hostnames }}
|
||||||
|
https://{{ . }}
|
||||||
|
{{- end }}
|
||||||
|
→ node group "{{ $ingressGroup }}", one route, not one per group
|
||||||
|
|
||||||
|
This release renders ONE HTTPRoute ({{ $release }}-httproute) and nothing
|
||||||
|
else of the HTTP routing path — no Gateway, no class, no certificate for this
|
||||||
|
hostname, no policy object. Three things therefore belong to the cluster:
|
||||||
|
|
||||||
|
1. The Gateway-API CRDs. Without them this install would have failed, so
|
||||||
|
they are present — nothing to check.
|
||||||
|
2. The Gateway named in httpRoute.parentRefs
|
||||||
|
{{- range $httpRoute.parentRefs }}
|
||||||
|
{{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }}
|
||||||
|
{{- end }}
|
||||||
|
must exist AND its listener must allow routes from this namespace
|
||||||
|
({{ .Release.Namespace }}) in allowedRoutes. A rejected attachment shows only in
|
||||||
|
the route's status:
|
||||||
|
|
||||||
|
kubectl -n {{ .Release.Namespace }} get httproute {{ $release }}-httproute -o yaml
|
||||||
|
|
||||||
|
Read status.parents[].conditions — "Accepted: True" and
|
||||||
|
"ResolvedRefs: True" is a working attachment; an empty status means no
|
||||||
|
controller has looked at the route at all.
|
||||||
|
3. The TLS certificate for the HTTP hostname, on the Gateway's listener.
|
||||||
|
ingress.clusterIssuer is not needed FOR THAT — but read the next
|
||||||
|
paragraph before removing it.
|
||||||
|
|
||||||
|
THE MAIL CERTIFICATE STAYS, AND MUST NOT BE CLEANED UP.
|
||||||
|
{{ if .Values.certificate.enabled }}certificate.enabled is true, so this release still renders
|
||||||
|
{{ $release }}-mail-certificate.{{ else }}certificate.enabled is false, so no mail certificate is rendered here.{{ end }}
|
||||||
|
That object has nothing to do with the Ingress or the Gateway: it covers
|
||||||
|
STARTTLS on the SMTP port and implicit TLS on the submission and IMAPS
|
||||||
|
ports, and those connections pass through no HTTP proxy at all. So in this mode
|
||||||
|
ingress.domain is STILL REQUIRED (the certificate's dnsNames read it) and
|
||||||
|
ingress.clusterIssuer is still required unless certificate.clusterIssuer is
|
||||||
|
set. Deleting the Certificate as part of a migration drops SMTP and IMAP back
|
||||||
|
to Stalwart's self-signed certificate: clients see a name mismatch while
|
||||||
|
every Kubernetes object in this release stays green.
|
||||||
|
|
||||||
|
SMTP, IMAP AND MANAGESIEVE ARE NOT ROUTED HERE, exactly as they were not
|
||||||
|
routed by the Ingress. They keep arriving through the per-port NodePort
|
||||||
|
Services. A Gateway could carry them as TLSRoute/TCPRoute listeners; this
|
||||||
|
chart renders neither.
|
||||||
|
|
||||||
|
AND NOW THE ONE THAT WILL BITE: CORS, AND THE WEBMAIL.
|
||||||
|
|
||||||
|
This installation's live Ingress carries, set by the operator through
|
||||||
|
ingress.annotations and NOT by this chart:
|
||||||
|
|
||||||
|
nginx.ingress.kubernetes.io/enable-cors: true
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-origin: https://<the webmail host>
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-methods: GET, POST, OPTIONS
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-headers: Authorization, Content-Type
|
||||||
|
|
||||||
|
That is what lets the browser-side webmail (the bulwark chart) call this
|
||||||
|
server's JMAP endpoint at all. IN THIS MODE IT IS GONE, and the failure is
|
||||||
|
entirely on the client side: a console error in the browser, and nothing
|
||||||
|
whatsoever in this server's logs.
|
||||||
|
|
||||||
|
The Gateway API does have a CORS filter — HTTPRoute filters carry a `cors`
|
||||||
|
field in the CRDs this repository pins — so this is not a missing feature of
|
||||||
|
the API. THIS CHART STILL DOES NOT RENDER IT, and knowingly:
|
||||||
|
|
||||||
|
- the chart has no CORS values to render it from (the origin and the method
|
||||||
|
list are the operator's, and the chart sets no CORS annotations in
|
||||||
|
Ingress mode either),
|
||||||
|
- that filter's support level is "Extended", so an implementation may
|
||||||
|
reject the route — and rejection is whole-route, which would take the
|
||||||
|
WebUI and JMAP down together — or, worse, accept it and not honour it,
|
||||||
|
- and the part a plain header filter COULD do is the part that does not
|
||||||
|
help here: the browser's PREFLIGHT has to be answered by the gateway
|
||||||
|
rather than by Stalwart, and because the allowed headers include
|
||||||
|
Authorization, EVERY request from the webmail is preflighted.
|
||||||
|
|
||||||
|
So: configure CORS AT THE GATEWAY, with that implementation's own support,
|
||||||
|
BEFORE you move this host. Then TEST IT FROM THE WEBMAIL IN A BROWSER — curl
|
||||||
|
sends no preflight, so it will pass while the webmail fails.
|
||||||
|
|
||||||
|
ingress.annotations is inert in this mode in general: the Gateway API has no
|
||||||
|
annotation channel for controller behaviour.
|
||||||
|
|
||||||
|
AFTER BOOTSTRAP, PORT {{ (index $ports "http").containerPort }} IS NOT THE ADMIN PORT ANY MORE, and the hostname
|
||||||
|
the WebUI sends administrators to lives IN THE DATABASE, not in any value
|
||||||
|
here. Upstream moves administration to https://<the configured server
|
||||||
|
hostname>/admin once setup is complete, so this route only works while an
|
||||||
|
HTTP NetworkListener on {{ (index $ports "http").containerPort }} exists in the database (step 3).
|
||||||
{{- else }}
|
{{- else }}
|
||||||
|
|
||||||
NO INGRESS
|
NEITHER INGRESS NOR HTTPROUTE
|
||||||
ingress.enabled is false, so there is no Ingress and no WebUI certificate.
|
ingress.enabled and httpRoute.enabled are both false, so there is no Ingress,
|
||||||
|
no HTTPRoute and no WebUI certificate.
|
||||||
ingress.domain STAYS REQUIRED even so — the mail Certificate reads it for its
|
ingress.domain STAYS REQUIRED even so — the mail Certificate reads it for its
|
||||||
dnsNames, and without it the certificate would carry no name at all.
|
dnsNames, and without it the certificate would carry no name at all.
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -363,6 +476,8 @@ NETWORKPOLICY: THE HTTP PORT IS OPEN INSIDE THE CLUSTER
|
|||||||
that never becomes Ready reads as a broken application rather than as a
|
that never becomes Ready reads as a broken application rather than as a
|
||||||
policy. The port carries no nodePort, so this is open inside the cluster, not
|
policy. The port carries no nodePort, so this is open inside the cluster, not
|
||||||
from outside. Narrow it with networkPolicy.probeSources, and then add the
|
from outside. Narrow it with networkPolicy.probeSources, and then add the
|
||||||
ingress controller's namespace to networkPolicy.allowedNamespaces or the
|
{{ if $httpRoute.enabled }}GATEWAY's namespace (not an ingress controller's — in Gateway mode the
|
||||||
Ingress goes dark.
|
traffic arrives from the Gateway's data-plane pods, which usually live in a
|
||||||
|
namespace of their own){{ else }}ingress controller's namespace{{ end }} to networkPolicy.allowedNamespaces or the
|
||||||
|
{{ if $httpRoute.enabled }}route{{ else }}Ingress{{ end }} goes dark.
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
{{/*
|
||||||
|
HTTPRoute - the Gateway-API alternative to stalwart.ingress.yaml (Gitea #114).
|
||||||
|
|
||||||
|
ONE ROUTE, NOT ONE PER NODE GROUP, and that is worth saying because #114
|
||||||
|
describes this chart as rendering "per node group". Its SERVICES and
|
||||||
|
StatefulSets are per group; the routing object is not and must not be. The
|
||||||
|
Ingress targets exactly ONE group (ingress.nodeGroup) through that group's own
|
||||||
|
http Service, because the WebUI's setup and management screens are not safe to
|
||||||
|
spread over a group that may not run the management tasks. The route keeps that
|
||||||
|
property, including the render abort when the named group does not expose
|
||||||
|
"http".
|
||||||
|
|
||||||
|
WHAT STAYS IN THIS MODE, AND IT IS THE ONLY CHART IN THE SWEEP WHERE ANYTHING
|
||||||
|
DOES: the MAIL certificate. stalwart.certificate.yaml is not the Ingress
|
||||||
|
certificate - it covers STARTTLS on 25 and implicit TLS on 465/993, and those
|
||||||
|
connections never pass through an ingress controller or a Gateway. So
|
||||||
|
certificate.enabled keeps rendering a Certificate here, ingress.domain stays
|
||||||
|
required (the certificate's dnsNames read it) and ingress.clusterIssuer stays
|
||||||
|
required unless certificate.clusterIssuer is set. Do NOT "clean up" the
|
||||||
|
Certificate when switching to Gateway mode: SMTP and IMAP would fall back to
|
||||||
|
whatever self-signed certificate Stalwart generates, and clients would see a
|
||||||
|
name mismatch while every Kubernetes object stayed green.
|
||||||
|
|
||||||
|
WHAT THIS RENDERS, AND WHAT IT MUST NOT
|
||||||
|
|
||||||
|
Only an HTTPRoute with parentRefs. No Gateway, no gatewayClassName, no
|
||||||
|
Certificate FOR THE HTTP HOSTNAME, and no implementation-specific policy
|
||||||
|
object - the subset that looks identical on nginx-gateway-fabric, Cilium, Envoy
|
||||||
|
Gateway and Istio (decision 1 of #114). The HTTP hostname's certificate belongs
|
||||||
|
on the Gateway's listener; the mail certificate stays here, which is the
|
||||||
|
distinction above.
|
||||||
|
|
||||||
|
And the Gateway API routes HTTP only, so SMTP, IMAP and ManageSieve are as far
|
||||||
|
outside this object as they are outside the Ingress - they keep reaching the
|
||||||
|
pods through the per-port NodePort Services. A Gateway could in principle carry
|
||||||
|
them as TLSRoute/TCPRoute listeners, which this chart does not render and which
|
||||||
|
would be a separate decision.
|
||||||
|
|
||||||
|
WHAT THIS MODE DROPS, MEASURED ON THE LIVE OBJECT - AND THIS IS THE ONE PLACE
|
||||||
|
IN GROUP 3 WHERE THE LOSS BREAKS A SECOND CHART OF THE SAME GROUP
|
||||||
|
|
||||||
|
On 2026-10-12 the live Ingress (jk-cluster, namespace stalwart,
|
||||||
|
mail.bekemeyer-klein.de) carried, all of it OPERATOR-supplied through
|
||||||
|
ingress.annotations - this chart sets none of it:
|
||||||
|
|
||||||
|
nginx.ingress.kubernetes.io/enable-cors: true
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-origin: https://webmail.bekemeyer-klein.de
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-methods: GET, POST, OPTIONS
|
||||||
|
nginx.ingress.kubernetes.io/cors-allow-headers: Authorization, Content-Type
|
||||||
|
|
||||||
|
That origin is BULWARK, the webmail chart in this same group. The CORS
|
||||||
|
configuration is the whole reason the browser-side webmail may call this
|
||||||
|
server's JMAP endpoint at all, so it is not decoration: without it the webmail
|
||||||
|
stops working, with a console error and no server-side symptom.
|
||||||
|
|
||||||
|
WHAT THE GATEWAY API HAS FOR IT, MEASURED AGAINST THE PINNED CRDs RATHER THAN
|
||||||
|
ASSUMED. Both halves exist in the API, and neither is promisable by this chart:
|
||||||
|
|
||||||
|
- A full CORS FILTER exists. In the standard channel this repository pins
|
||||||
|
(gateway-api v1.6.3, ci/gateway-api-crds.sh), HTTPRoute's filters carry a
|
||||||
|
`cors` field with allowOrigins, allowMethods, allowHeaders,
|
||||||
|
allowCredentials and maxAge - GEP-1767. Grepped out of the downloaded CRD,
|
||||||
|
not inferred. Its documented support level, verbatim from that CRD, is
|
||||||
|
"Support: Extended".
|
||||||
|
- responseHeaderModifier is in the same CRD and is ALSO "Support: Extended",
|
||||||
|
not Core.
|
||||||
|
|
||||||
|
So "no counterpart" would be the wrong thing to write down. The accurate
|
||||||
|
statement is that there is a counterpart and this chart still does not render
|
||||||
|
it, for three reasons, in order:
|
||||||
|
|
||||||
|
1. THE CHART HAS NO CORS VALUES AT ALL. The live configuration is the
|
||||||
|
operator's: a specific origin, a specific method list. Rendering a CORS
|
||||||
|
filter means inventing a values surface in a sweep PR - and one that
|
||||||
|
would exist in Gateway mode only, because this chart sets no CORS
|
||||||
|
annotations in Ingress mode either. That asymmetry is its own defect and
|
||||||
|
its own piece of work.
|
||||||
|
2. EXTENDED SUPPORT IS NOT SUPPORT. #119 established the shape of this: an
|
||||||
|
implementation that does not support an Extended value must report it in
|
||||||
|
the route's status, and a rejected route is rejected WHOLE - here that
|
||||||
|
would take the WebUI and JMAP down together. Worse, the observed failure
|
||||||
|
is quieter than that: Cilium accepts `filters.type: CORS` and still
|
||||||
|
forwards the browser's preflight upstream
|
||||||
|
(cilium/cilium#45272), i.e. the object is Accepted and the behaviour is
|
||||||
|
absent. A chart cannot check which implementation it landed on.
|
||||||
|
3. HALF A CORS IS WORSE THAN NONE, AND THE TRANSLATABLE HALF IS THE USELESS
|
||||||
|
ONE HERE. A responseHeaderModifier can add the three
|
||||||
|
Access-Control-Allow-* headers to a response, but the PREFLIGHT is a
|
||||||
|
different thing: the browser sends OPTIONS first and the gateway - not the
|
||||||
|
backend - has to answer it. The CORS filter's own contract says so (a path
|
||||||
|
with a CORS filter MUST NOT pass the preflight upstream), which is exactly
|
||||||
|
what a bare header modifier cannot do. And this installation's header list
|
||||||
|
includes Authorization, which makes EVERY webmail request preflighted: the
|
||||||
|
half that is expressible without the CORS filter is precisely the half
|
||||||
|
that does not help. Rendering it would produce a setup that looks
|
||||||
|
configured and fails on the first real call.
|
||||||
|
|
||||||
|
So: named, not rebuilt (decision 2 of #114), in the Gateway branch of
|
||||||
|
NOTES.txt, with the instruction to configure CORS at the Gateway - via that
|
||||||
|
implementation's CORS support - BEFORE moving this host, and to test it from
|
||||||
|
the webmail rather than with curl, because curl sends no preflight.
|
||||||
|
*/}}
|
||||||
|
{{- /*
|
||||||
|
Null-safe like every other block a template here dereferences (#99): an
|
||||||
|
override written without children - "httpRoute:" with its keys deleted - is
|
||||||
|
YAML null, and .Values.httpRoute.enabled would abort with a nil pointer.
|
||||||
|
*/ -}}
|
||||||
|
{{- $httpRoute := .Values.httpRoute | default dict -}}
|
||||||
|
{{- if and $httpRoute.enabled .Values.ingress.enabled -}}
|
||||||
|
{{- fail "ingress.enabled and httpRoute.enabled are both true - set exactly one of them. The Ingress and the HTTPRoute would publish the same Service under the same hostname over two different data paths, which is already a conflict at DNS level: one name, two addresses, and which one a client reaches is luck. For Stalwart there is a second edge to it: the server's auto-ban sees the proxy's address and bans it, so two data paths mean two addresses to get banned and a WebUI that fails for half the requests. A migration runs over a SECOND hostname (set httpRoute.hostnames explicitly while the Ingress keeps ingress.domain), never over parallel operation of both - and note that the server hostname configured IN THE DATABASE decides where the WebUI tells administrators to go, which no chart value can change." -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $httpRoute.enabled -}}
|
||||||
|
{{- $group := include "stalwart.ingressNodeGroup" . -}}
|
||||||
|
{{- $nodes := fromYaml (include "stalwart.nodes" .) -}}
|
||||||
|
{{- $ports := fromYaml (include "stalwart.ports" .) -}}
|
||||||
|
{{- $spec := index $nodes $group -}}
|
||||||
|
{{- if not (has "http" $spec.exposedPorts) -}}
|
||||||
|
{{- fail (printf "ingress.nodeGroup is %q, but nodes.%s.exposedPorts does not include \"http\" - there would be no Service for the HTTPRoute to point at. The value is shared by both modes on purpose: it names the node group the management surface is published from, which is not a property of the Ingress." $group $group) -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- $parentRefs := $httpRoute.parentRefs | default list -}}
|
||||||
|
{{- if not $parentRefs -}}
|
||||||
|
{{- fail "httpRoute.enabled is true but httpRoute.parentRefs is empty - an HTTPRoute without a parent attaches to no Gateway. It is created, it is schema-valid, and it routes NOTHING, with no event and no condition that says so; the only symptom is a hostname that does not answer. Name the cluster's Gateway, e.g. httpRoute.parentRefs: [{name: public, namespace: gateway}]." -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- /*
|
||||||
|
One source of truth for the hostname: an empty httpRoute.hostnames means
|
||||||
|
[ingress.domain]. Two values that have to agree drift apart. ingress.domain is
|
||||||
|
read outside the routing templates in this chart - the mail Certificate's
|
||||||
|
dnsNames - so it already carries a required() there and is mandatory in every
|
||||||
|
mode, including with both modes off. This required() is the route's own, so the
|
||||||
|
message names the route.
|
||||||
|
*/ -}}
|
||||||
|
{{- $hostnames := $httpRoute.hostnames | default list -}}
|
||||||
|
{{- if not $hostnames -}}
|
||||||
|
{{- $hostnames = list (required "A Domain/Host must be provided: set ingress.domain (used as the HTTPRoute's single hostname when httpRoute.hostnames is empty) or set httpRoute.hostnames explicitly" .Values.ingress.domain) -}}
|
||||||
|
{{- end -}}
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: {{ include "stalwart.fullname" . }}-httproute
|
||||||
|
labels:
|
||||||
|
app: {{ include "stalwart.fullname" . }}
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
{{- toYaml $parentRefs | nindent 4 }}
|
||||||
|
hostnames:
|
||||||
|
{{- toYaml $hostnames | nindent 4 }}
|
||||||
|
rules:
|
||||||
|
{{- /*
|
||||||
|
The Ingress counterpart is `path: / , pathType: Prefix` on the http
|
||||||
|
Service of the "{{ $group }}" group ONLY - never a port-label selector,
|
||||||
|
for the reason in the header. The port is the Service's port NUMBER and
|
||||||
|
not its name "http": backendRefs takes a PortNumber, so unlike the
|
||||||
|
Ingress this reference cannot be made by name. It comes from
|
||||||
|
ports.http.containerPort, which is the same value the Service renders its
|
||||||
|
port from, so the two cannot drift;
|
||||||
|
ci/run-httproute-match-test.sh holds the pair for the case where they do.
|
||||||
|
*/}}
|
||||||
|
- matches:
|
||||||
|
- path:
|
||||||
|
type: PathPrefix
|
||||||
|
value: /
|
||||||
|
backendRefs:
|
||||||
|
- name: {{ include "stalwart.fullname" . }}-{{ $group }}-http-service
|
||||||
|
port: {{ int (index $ports "http").containerPort }}
|
||||||
|
{{- end }}
|
||||||
@@ -390,7 +390,7 @@
|
|||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"description": "Gates the Ingress template only. ingress.domain stays REQUIRED either way, because the mail Certificate's dnsNames reads it too - switching the Ingress off would otherwise render a certificate with no name in it (guideline 12.1). clusterIssuer becomes unnecessary only if certificate.clusterIssuer is set, since that falls back to it.",
|
"description": "Gates the Ingress template only. Mutually exclusive with httpRoute.enabled - the render aborts when both are true. ingress.domain stays REQUIRED either way, because the mail Certificate's dnsNames reads it too - switching the Ingress off would otherwise render a certificate with no name in it (guideline 12.1) - and the HTTPRoute reads it as its hostname as well. clusterIssuer becomes unnecessary only if certificate.clusterIssuer is set, since that falls back to it.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"clusterIssuer": {
|
"clusterIssuer": {
|
||||||
@@ -405,16 +405,34 @@
|
|||||||
"type": ["string", "null"]
|
"type": ["string", "null"]
|
||||||
},
|
},
|
||||||
"nodeGroup": {
|
"nodeGroup": {
|
||||||
"description": "Which node group the Ingress routes to, through that group's own http Service. Must be a key of nodes: and must expose http; both are checked in the template. A single group on purpose - the management surface is not safe to spread across a group that may not run the management tasks, and a port-label selector would send each request to a random group.",
|
"description": "Which node group the routing object - Ingress or HTTPRoute - points at, through that group's own http Service. Shared by both modes on purpose: it names the node group the management surface is published from, which is not a property of the Ingress. Must be a key of nodes: and must expose http; both are checked in both templates. A single group on purpose - the management surface is not safe to spread across a group that may not run the management tasks, and a port-label selector would send each request to a random group.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"minLength": 1
|
"minLength": 1
|
||||||
},
|
},
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"description": "Extra Ingress annotations, merged with the cert-manager annotation the chart always sets. Free-form keys by nature, so this object stays open.",
|
"description": "Extra Ingress annotations, merged with the cert-manager annotation the chart always sets. Free-form keys by nature, so this object stays open. INERT IN GATEWAY MODE, and for this chart that is the consequential one: this installation's live Ingress carries its CORS configuration here (enable-cors plus allow-origin/methods/headers for the bulwark webmail). See values.yaml, httpRoute block - the Gateway API does have a CORS filter, and the chart still does not render it.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"httpRoute": {
|
||||||
|
"description": "Gateway-API alternative to the Ingress (Gitea #114). Mutually exclusive with ingress.enabled - the render aborts when both are true, naming both value paths. The chart renders only an HTTPRoute with parentRefs; the HTTP hostname's certificate sits on the Gateway's listener and belongs to the platform. ONE route, not one per node group - it targets ingress.nodeGroup's http Service, with the same two guards the Ingress has. THE MAIL CERTIFICATE STAYS in this mode: it covers STARTTLS on 25 and implicit TLS on 465/993, which pass through no HTTP proxy, so ingress.domain stays required and the Certificate must not be removed as part of a migration. SMTP, IMAP and ManageSieve are not routed here any more than by the Ingress. NOT COVERED IN THIS MODE: the CORS configuration this installation relies on for the bulwark webmail. A CORS filter DOES exist in the pinned gateway-api CRDs (v1.6.3, GEP-1767) but is 'Support: Extended', implementations vary (Cilium accepts it and forwards the preflight upstream anyway), the chart has no CORS values to render it from, and a bare responseHeaderModifier cannot answer a preflight - which is every request here, since the allowed headers include Authorization. Configure CORS at the Gateway and test from a browser, not with curl. See values.yaml, httpRoute block.",
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"parentRefs": {
|
||||||
|
"description": "The Gateway(s) the route attaches to. Required while httpRoute.enabled is true, enforced by required()-equivalent in the template (an empty list aborts the render) - a JSON Schema \"required\" cannot do it, because the key ships as an empty list and is therefore present. Entries are the Gateway API's ParentReference and are passed through unchanged.",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/parentRef" }
|
||||||
|
},
|
||||||
|
"hostnames": {
|
||||||
|
"description": "Empty means [ingress.domain], so the HTTP hostname has a single source of truth. Note that the server's own hostname lives in the DATABASE, not in any chart value: after bootstrap the WebUI is reached at https://<configured server hostname>/admin, so a second hostname can be reachable without the application agreeing that it is its name.",
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"securityContext": {
|
"securityContext": {
|
||||||
"description": "May be null / empty at any level: the chart then falls back to its hardening defaults (#99). THIS CHART'S FALLBACK REPRODUCES ITS DEVIATION, not the repo standard - capabilities.add carries NET_BIND_SERVICE, because the image sets 'setcap cap_net_bind_service=+ep' on its binary and the kernel then fails the execve itself when the bounding set does not carry the capability. An erased block that fell back to the standard 'add: []' would stop the chart from starting. uid/gid 2000 is the image's own user, and there is deliberately no fsGroup because this chart mounts no PVC.",
|
"description": "May be null / empty at any level: the chart then falls back to its hardening defaults (#99). THIS CHART'S FALLBACK REPRODUCES ITS DEVIATION, not the repo standard - capabilities.add carries NET_BIND_SERVICE, because the image sets 'setcap cap_net_bind_service=+ep' on its binary and the kernel then fails the execve itself when the bounding set does not carry the capability. An erased block that fell back to the standard 'add: []' would stop the chart from starting. uid/gid 2000 is the image's own user, and there is deliberately no fsGroup because this chart mounts no PVC.",
|
||||||
"type": ["object", "null"],
|
"type": ["object", "null"],
|
||||||
@@ -455,6 +473,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"parentRef": {
|
||||||
|
"description": "A Gateway API ParentReference, passed verbatim into HTTPRoute.spec.parentRefs. Deliberately left OPEN (no additionalProperties: false), for the same reason probes and security contexts are: Kubernetes owns this schema, the Gateway API adds fields to it across releases, and a closed list here would reject a field only upstream knows about. What the chart can assert is that \"name\" is there - it is required by the API and a reference without it attaches to nothing, which is the silent failure this chart aborts the render over.",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name"]
|
||||||
|
},
|
||||||
"exposedPorts": {
|
"exposedPorts": {
|
||||||
"description": "Keys of ports: this group may be reached on. Checked against ports: in the template, which also requires 'http' - all three probes target it, so a group without it would never become Ready. Not a copy of the database's listener configuration but a statement of its own, which is why a disagreement in either direction is harmless and why it blunts the fail-open described on nodes:.",
|
"description": "Keys of ports: this group may be reached on. Checked against ports: in the template, which also requires 'http' - all three probes target it, so a group without it would never become Ready. Not a copy of the database's listener configuration but a statement of its own, which is why a disagreement in either direction is harmless and why it blunts the fail-open described on nodes:.",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
|
|||||||
@@ -625,8 +625,137 @@ ingress:
|
|||||||
nodeGroup: full
|
nodeGroup: full
|
||||||
# Extra annotations for the Ingress; merged with the cert-manager
|
# Extra annotations for the Ingress; merged with the cert-manager
|
||||||
# cluster-issuer annotation the chart always sets.
|
# cluster-issuer annotation the chart always sets.
|
||||||
|
#
|
||||||
|
# INERT IN GATEWAY MODE - the Gateway API has no annotation channel for
|
||||||
|
# controller behaviour. For THIS chart that is the most consequential line in
|
||||||
|
# the whole block: read the httpRoute section below before switching modes,
|
||||||
|
# because what lives in here on the live installation is the CORS
|
||||||
|
# configuration the webmail depends on.
|
||||||
annotations: {}
|
annotations: {}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# THE GATEWAY-API ALTERNATIVE TO THE INGRESS (Gitea #114)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Mutually exclusive with ingress.enabled - the render aborts when both are
|
||||||
|
# true, and the message names both value paths. The chart renders ONLY an
|
||||||
|
# HTTPRoute with parentRefs: no Gateway, no class, no Certificate for the HTTP
|
||||||
|
# hostname, and no implementation-specific policy object.
|
||||||
|
#
|
||||||
|
# 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 the ingress.nodeGroup group,
|
||||||
|
# through that group's own http Service, and aborts the render if that group
|
||||||
|
# does not expose "http" - the same two guards the Ingress has, for the same
|
||||||
|
# reason: the WebUI's setup and management screens are not safe to spread over
|
||||||
|
# a group that may not run the management tasks.
|
||||||
|
#
|
||||||
|
# THE MAIL CERTIFICATE STAYS, and this is the only chart in the sweep where
|
||||||
|
# anything does. certificate.enabled renders a Certificate for 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 "cleaned up" as part of the migration.
|
||||||
|
# Deleting it would drop SMTP and IMAP back to Stalwart's self-signed
|
||||||
|
# certificate: clients see a name mismatch while every Kubernetes object in the
|
||||||
|
# release stays green.
|
||||||
|
#
|
||||||
|
# SMTP, IMAP AND MANAGESIEVE ARE NOT ROUTED HERE, exactly as they are not
|
||||||
|
# routed by the Ingress. They keep reaching the pods through the per-port
|
||||||
|
# NodePort Services. A Gateway could carry them as TLSRoute/TCPRoute listeners;
|
||||||
|
# this chart renders neither, and doing so would be a separate decision.
|
||||||
|
#
|
||||||
|
# WHAT THIS MODE COSTS - 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):
|
||||||
|
#
|
||||||
|
# nginx.ingress.kubernetes.io/enable-cors: true
|
||||||
|
# nginx.ingress.kubernetes.io/cors-allow-origin: https://webmail.bekemeyer-klein.de
|
||||||
|
# nginx.ingress.kubernetes.io/cors-allow-methods: GET, POST, OPTIONS
|
||||||
|
# nginx.ingress.kubernetes.io/cors-allow-headers: Authorization, Content-Type
|
||||||
|
#
|
||||||
|
# That origin is BULWARK, the webmail chart in this same group. The CORS
|
||||||
|
# configuration is what allows the browser-side webmail to call this server's
|
||||||
|
# JMAP endpoint at all. Lose it and the webmail stops working, with a console
|
||||||
|
# error in the browser and NO server-side symptom whatsoever.
|
||||||
|
#
|
||||||
|
# AND "no counterpart" would be the wrong thing to claim here. Measured against
|
||||||
|
# the CRDs this repository pins (gateway-api v1.6.3 standard channel,
|
||||||
|
# ci/gateway-api-crds.sh), by grepping the downloaded manifest rather than by
|
||||||
|
# inference:
|
||||||
|
# - HTTPRoute's filters carry a full `cors` field (GEP-1767) with
|
||||||
|
# allowOrigins, allowMethods, allowHeaders, allowCredentials and maxAge.
|
||||||
|
# Its documented support level, verbatim from that CRD: "Support:
|
||||||
|
# Extended".
|
||||||
|
# - `responseHeaderModifier` is in the same CRD and is ALSO "Support:
|
||||||
|
# Extended", not Core.
|
||||||
|
#
|
||||||
|
# There IS a counterpart, then, and this chart still does not render it. Three
|
||||||
|
# reasons, in order:
|
||||||
|
#
|
||||||
|
# 1. THIS CHART HAS NO CORS VALUES. The live configuration is the operator's
|
||||||
|
# - a specific origin, a specific method list. Rendering a filter means
|
||||||
|
# inventing a values surface in a sweep PR, and one that would exist in
|
||||||
|
# Gateway mode only, since the chart sets no CORS annotations in Ingress
|
||||||
|
# mode either. That asymmetry is its own defect and its own piece of work.
|
||||||
|
# 2. EXTENDED SUPPORT IS NOT SUPPORT. An implementation that does not support
|
||||||
|
# an Extended value reports it in the route's status, and a rejected route
|
||||||
|
# is rejected WHOLE - which here would take the WebUI and JMAP down
|
||||||
|
# together (the reasoning #119 arrived at for statusCode). The observed
|
||||||
|
# failure is quieter still: Cilium accepts filters.type: CORS and
|
||||||
|
# forwards the browser's preflight upstream anyway (cilium/cilium#45272),
|
||||||
|
# i.e. Accepted: True with the behaviour absent. A chart cannot know which
|
||||||
|
# implementation it landed on.
|
||||||
|
# 3. HALF A CORS IS WORSE THAN NONE, AND THE TRANSLATABLE HALF IS THE USELESS
|
||||||
|
# ONE HERE. A responseHeaderModifier can add the three
|
||||||
|
# Access-Control-Allow-* headers to a response. The PREFLIGHT is a
|
||||||
|
# different thing: the browser sends OPTIONS first and the GATEWAY, not
|
||||||
|
# the backend, has to answer it - the CORS filter's own contract says a
|
||||||
|
# path carrying it MUST NOT pass the preflight upstream. A bare header
|
||||||
|
# modifier cannot do that. And this installation's header list includes
|
||||||
|
# Authorization, which makes EVERY webmail request preflighted: the half
|
||||||
|
# that is expressible without the CORS filter is precisely the half that
|
||||||
|
# does not help.
|
||||||
|
#
|
||||||
|
# So configure CORS AT THE GATEWAY, using that implementation's own CORS
|
||||||
|
# support, BEFORE moving this host - and TEST IT FROM THE WEBMAIL, not with
|
||||||
|
# curl: curl sends no preflight, so it passes while a browser fails.
|
||||||
|
#
|
||||||
|
# THE GATEWAY-API CRDs MUST BE PRESENT. The chart cannot detect them
|
||||||
|
# (.Capabilities.APIVersions is unreliable at template time) and an HTTPRoute
|
||||||
|
# without them fails at install, so this is a value and not a guess.
|
||||||
|
httpRoute:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# The Gateway(s) the route attaches to - a platform object, usually in
|
||||||
|
# another namespace, whose listener must allow this namespace in
|
||||||
|
# allowedRoutes. Entries are the Gateway API's ParentReference (name, and
|
||||||
|
# optionally namespace, sectionName, port, group, kind), passed through
|
||||||
|
# unchanged:
|
||||||
|
#
|
||||||
|
# parentRefs:
|
||||||
|
# - name: public
|
||||||
|
# namespace: gateway
|
||||||
|
# sectionName: https
|
||||||
|
#
|
||||||
|
# REQUIRED while httpRoute.enabled is true - the render aborts on an empty
|
||||||
|
# list, because a route without a parent is created, is valid and routes
|
||||||
|
# nothing, with no event to say so.
|
||||||
|
parentRefs: []
|
||||||
|
|
||||||
|
# Empty means [ingress.domain], so the HTTP hostname has one source of truth.
|
||||||
|
# Set explicitly only to publish under a different or an additional name -
|
||||||
|
# which is how a migration from the Ingress runs: the HTTPRoute takes a
|
||||||
|
# second hostname while the Ingress keeps ingress.domain.
|
||||||
|
#
|
||||||
|
# NOTE THAT THE SERVER'S OWN HOSTNAME LIVES IN THE DATABASE, not here. After
|
||||||
|
# bootstrap the WebUI is reached at https://<the configured server
|
||||||
|
# hostname>/admin, and no value of this chart changes that - so a second
|
||||||
|
# hostname is reachable without the application agreeing that it is its name.
|
||||||
|
hostnames: []
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Hardening — chart-wide, applied to every node group
|
# Hardening — chart-wide, applied to every node group
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in new issue
Block a user