Validate Helm Charts / Detect changed charts (pull_request) Successful in 6s
Validate Helm Charts / Resource quantities (pull_request) Successful in 17s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 15s
Validate Helm Charts / Build stalwart (pull_request) Successful in 18s
Validate Helm Charts / Trivy config stalwart (pull_request) Successful in 23s
Install-test / Install-test stalwart (pull_request) Successful in 1m55s
Validate Helm Charts / Install-test (pull_request) Successful in 1m55s
With BOTH routing modes on and an mtaSts entry missing its tlsSecretName, the render reported mtaSts.hosts[0] (mta-sts.example.com) has no tlsSecretName, and in Ingress mode every entry needs one which is true and is the wrong diagnosis: the values file's defect is that ingress.enabled and httpRoute.enabled are both true, and "in Ingress mode" is a claim about a configuration that is not coherent at all. Whoever met it would add a tlsSecretName, re-run, and reach the real message one round later - or conclude the MTA-STS feature was the problem. Measured on this chart, not reasoned about. And it is the second occurrence of a defect the parallel keycloak round of Gitea #122 found on its own realm list, independently and in the same round, which is what makes it a rule rather than one chart's story: a specific validation must not be reachable in a state that a structural validation already rejects. Ordering is not the fix and could not be - Helm renders templates in an order a chart cannot pin, so this template may not assume the httproute template's abort runs first. Scope is: the Ingress-mode MTA-STS block is now gated on ingress.enabled AND NOT httpRoute.enabled, so in the incoherent combination it validates nothing and the only fail left is the one that names the real problem. The per-entry guard is NOT dead by this - verified reachable with ingress.enabled alone. Defaults, the unchanged CI fixture and the overlay all still render byte-identically against origin/main, and the HTTPRoute render test still holds 58 assertions over 21 charts.
170 lines
7.7 KiB
YAML
170 lines
7.7 KiB
YAML
{{- /*
|
|
The Ingress for the WebUI and JMAP only. SMTP, IMAP and ManageSieve are not
|
|
HTTP and cannot pass through it - those reach the pods through the per-port
|
|
NodePort Services.
|
|
|
|
It targets exactly ONE node group (ingress.nodeGroup) through that group's own
|
|
http Service, never a port-label selector: the management surface is not safe
|
|
to spread across a group that may not run the management tasks.
|
|
|
|
TWO TRAPS THAT BELONG TO THIS OBJECT AND ARE NOT SOLVED BY IT:
|
|
|
|
1. The auto-ban sees the INGRESS CONTROLLER's pod IP for every JMAP and WebUI
|
|
request, not the client's. Sooner or later it bans that address and the
|
|
WebUI and JMAP go dark for everyone at once. The PROXY protocol is not the
|
|
remedy for HTTP: the remedy is Stalwart's "forwarded" handling with
|
|
X-Forwarded-For evaluation, plus discarding whatever forwarding headers the
|
|
client brought along. That is a configuration step in the database, and it
|
|
is mandatory rather than optional - the failure arrives weeks later and
|
|
looks like a server problem.
|
|
|
|
2. AFTER BOOTSTRAP, PORT 8080 IS NOT THE ADMIN PORT ANY MORE. Upstream:
|
|
"After the restart, the WebUI is reached at https://<hostname>/admin on the
|
|
configured server hostname, and HTTP access on port 8080 is no longer
|
|
available for administration." So this Ingress only works once an HTTP
|
|
NetworkListener on 8080 exists in the database. Between finishing the setup
|
|
and creating that listener, the Ingress resolves to a port that answers
|
|
nothing.
|
|
*/ -}}
|
|
{{- if .Values.ingress.enabled }}
|
|
{{- $group := include "stalwart.ingressNodeGroup" . -}}
|
|
{{- $nodes := fromYaml (include "stalwart.nodes" .) -}}
|
|
{{- $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 Ingress to point at" $group $group) -}}
|
|
{{- end -}}
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: {{ include "stalwart.fullname" . }}-ingress
|
|
labels:
|
|
app: {{ include "stalwart.fullname" . }}
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided (ingress.clusterIssuer)" .Values.ingress.clusterIssuer }}
|
|
{{- with .Values.ingress.annotations }}
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
spec:
|
|
ingressClassName: {{ .Values.ingress.className }}
|
|
rules:
|
|
- host: {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }}
|
|
http:
|
|
paths:
|
|
- backend:
|
|
service:
|
|
# The http Service of the "{{ $group }}" group only.
|
|
name: {{ include "stalwart.fullname" . }}-{{ $group }}-http-service
|
|
port:
|
|
name: http
|
|
path: /
|
|
pathType: Prefix
|
|
tls:
|
|
- hosts:
|
|
- {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }}
|
|
secretName: tls-{{ include "stalwart.fullname" . }}-ingress
|
|
{{- end }}
|
|
{{- /*
|
|
MTA-STS (Gitea #123) - the policy route, in Ingress mode.
|
|
|
|
ONE OBJECT PER clusterIssuer, one rule per host, tls grouped by secret. The
|
|
whole reasoning for that shape, including the cert-manager measurement it rests
|
|
on, is in stalwart.mtaSts.ingressGroups in _helpers.tpl; the short version is
|
|
that the issuer is an object-level ANNOTATION while the secret is a per-tls-entry
|
|
field, so an object cannot both ask cert-manager for one certificate and leave
|
|
another alone.
|
|
|
|
GATED ON ingress.enabled, not on a switch of its own. mtaSts.hosts is the
|
|
switch for the feature (empty list = nothing rendered, in either mode) and
|
|
ingress.enabled is the switch for the MODE - so these objects appear exactly
|
|
when this chart is publishing over an Ingress AND the list is non-empty. The
|
|
mutual exclusion of the two modes is enforced once, in stalwart.httproute.yaml,
|
|
and it is unconditional on mtaSts: a fail anywhere aborts the whole render.
|
|
|
|
AND "not httpRoute.enabled" IS PART OF THAT GATE RATHER THAN REDUNDANT WITH
|
|
IT. Without it this block's per-entry required() can fire while BOTH modes are
|
|
on, and then the render reports "mtaSts.hosts[0] has no tlsSecretName, and in
|
|
Ingress mode every entry needs one" - true, and the wrong diagnosis: the
|
|
values file's defect is that both modes are on, and "in Ingress mode" is a
|
|
claim about a configuration that is not coherent at all. Whoever met it would
|
|
add a tlsSecretName, re-run, and reach the real message one round later, or
|
|
conclude this feature was the problem. MEASURED on this chart before the gate
|
|
was added, not reasoned about.
|
|
|
|
Ordering is not the fix and could not be: Helm renders templates in an order a
|
|
chart cannot pin, so this template may not assume the httproute template's
|
|
structural abort runs first. SCOPE is the fix - with both modes on this block
|
|
validates nothing, so the only fail left is the one that names the real
|
|
problem. The generalisation (a specific validation must not be reachable in a
|
|
state a structural validation already rejects) is the Lessons-Learned entry
|
|
that the parallel keycloak round of Gitea #122 bought on its own realm list.
|
|
|
|
WHAT THIS DELIBERATELY DOES NOT CARRY: ingress.annotations. Those belong to the
|
|
WebUI/JMAP host - on the live installation they are its CORS configuration for
|
|
the bulwark webmail - and a policy file served to sending MTAs has no use for
|
|
them. Copying them here would put a CORS allow-origin on an endpoint no browser
|
|
ever calls, and would do it silently.
|
|
|
|
AND THE LIMIT THAT NO OBJECT HERE CAN FIX: this chart registers a ROUTE, it
|
|
does not create a DOMAIN. Whether Stalwart answers for mta-sts.<domain> depends
|
|
on <domain> existing in its data store, which is not in any chart value (see
|
|
values.yaml, line 1 onwards, and the mtaSts block).
|
|
*/ -}}
|
|
{{- $httpRoute := .Values.httpRoute | default dict }}
|
|
{{- if and .Values.ingress.enabled (not $httpRoute.enabled) }}
|
|
{{- $mtaSts := fromYaml (include "stalwart.mtaSts.ingressGroups" .) }}
|
|
{{- if $mtaSts.order }}
|
|
{{- $group := include "stalwart.ingressNodeGroup" . }}
|
|
{{- $nodes := fromYaml (include "stalwart.nodes" .) }}
|
|
{{- $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 MTA-STS Ingress to point at" $group $group) }}
|
|
{{- end }}
|
|
{{- $service := printf "%s-%s-http-service" (include "stalwart.fullname" .) $group }}
|
|
{{- $path := include "stalwart.mtaSts.path" . }}
|
|
{{- range $issuer := $mtaSts.order }}
|
|
{{- $entry := index $mtaSts.groups $issuer }}
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: {{ include "stalwart.mtaSts.ingressName" (dict "root" $ "issuer" $issuer) }}
|
|
labels:
|
|
app: {{ include "stalwart.fullname" $ }}
|
|
{{- if $issuer }}
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: {{ $issuer }}
|
|
{{- end }}
|
|
spec:
|
|
ingressClassName: {{ $.Values.ingress.className }}
|
|
rules:
|
|
{{- range $host := $entry.hosts }}
|
|
- host: {{ $host }}
|
|
http:
|
|
paths:
|
|
{{- /*
|
|
Exact, not Prefix, and the same per-group http Service the WebUI
|
|
rule above names - by PORT NAME, which an Ingress backend can do
|
|
and a Gateway backendRef cannot. RFC 8461 serves exactly one file
|
|
at exactly one path; a Prefix here would hand this host's whole
|
|
URL space to the mail server's management surface.
|
|
*/}}
|
|
- backend:
|
|
service:
|
|
name: {{ $service }}
|
|
port:
|
|
name: http
|
|
path: {{ $path }}
|
|
pathType: Exact
|
|
{{- end }}
|
|
tls:
|
|
{{- range $secret := $entry.tlsOrder }}
|
|
- hosts:
|
|
{{- range $host := index $entry.tls $secret }}
|
|
- {{ $host }}
|
|
{{- end }}
|
|
secretName: {{ $secret }}
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- end }}
|