feat(stalwart): MTA-STS routes as a maintainable list, in both routing modes
The policy route for mta-sts.<domain> belonged to another repository
(jk-cluster-config) while pointing at this chart's own Service. It moves
here as mtaSts.hosts, a list, so several domains - subdomains included -
are maintainable in one place and in whichever routing mode is active.
THE LIST IS THE SWITCH. No mtaSts.enabled: "enabled with an empty list"
would be a state with no meaning that the chart would then have to catch.
The schema rejects the key outright, so the rule is enforced rather than
documented. Default [] renders nothing in either mode - the defaults and
the unchanged CI fixture both produce a byte-identical helm template
against origin/main.
Entry form {host, tlsSecretName, clusterIssuer?}. The two TLS fields are
read ONLY in Ingress mode; in Gateway mode TLS sits on the listener, so
requiring them there would demand a value that does nothing.
THE PATH IS HARDWIRED. RFC 8461 fixes /.well-known/mta-sts.txt, rendered
with pathType Exact (type: Exact in the route). A values key for a
protocol-mandated path can only ever be set wrong, and wrong is invisible
from this side. Exact also matters in itself: a prefix on these hostnames
would publish the mail server's whole management surface under them.
ONE INGRESS PER CLUSTER ISSUER, NOT ONE OBJECT - and the plan's own three
requirements forced that. cert-manager reads the two fields at different
scopes: cluster-issuer is an ANNOTATION (object-scoped, and absent means
nothing is issued for that object at all), while secretName is
tls-entry-scoped and yields one Certificate per entry. So an object cannot
both request a certificate and leave another host's secret alone, which is
what an empty per-entry issuer has to mean. Measured in jk-cluster:
ingress-shim names the Certificate AFTER THE SECRET and owns it from the
Ingress, so a shared secret must sit in exactly one object (hence grouping
the tls list by secret, reusing rss-bridge.tlsGroups' shape) and a secret
may not span two issuer groups (hence the abort). Objects are named by
issuer, not by index: an index is only free where the object holds no
certificate, which is the precondition homeassistant's route naming states
for itself.
Gateway mode renders ONE HTTPRoute with every hostname - nothing differs
per host there - attached to the same Gateway(s) as the WebUI route.
WHAT THE CHART CANNOT PROMISE, AND THEREFORE NAMES: it registers a route,
it does not create a domain. The domains live in Stalwart's data store, so
an entry whose domain is missing there answers 404 at a correctly routed
endpoint with every object green - and MTA-STS failures are silent on the
SENDER's side, which delivers in the clear and logs nothing here. Same for
the _mta-sts.<domain> TXT record and the policy's content. The subdomain
trap is documented too: mta-sts.sub.<domain> is one label below
*.<domain>, so a parent wildcard certificate does not cover it.
Not folded into certificate.extraDnsNames deliberately - that would hang
the MAIL certificate on this list, letting a bad policy entry take STARTTLS
and IMAPS with it.
Minor, additive, empty default: 1.2.0+upv0.16.25 -> 1.3.0+upv0.16.25.
CI: ci/httproute/stalwart.yaml enters all three shapes of the entry form
(with issuer, without, and a subdomain), taking the HTTPRoute render test
from 56 assertions over 21 charts to 58 over 21 - stalwart from 2 to 4.
The Ingress half cannot be covered there, since an overlay's whole job is
to switch the mode off, so ci/stalwart/test-values.yaml carries two
entries and the install-test has a real API server accept them.
Fixes #123
This commit is contained in:
10 files changed
+677
-9
No files matched your search
@@ -51,6 +51,26 @@
|
||||
# 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.
|
||||
# - THE MTA-STS ROUTE (Gitea #123), which is the second HTTPRoute this chart
|
||||
# renders. Its backendRef is held against the same per-group http Service
|
||||
# by the same port number, so the one place information travels from the
|
||||
# Service object into a route template is checked twice rather than once.
|
||||
# What the list below is shaped for is the three cases the entry form has:
|
||||
# an entry WITH a clusterIssuer, an entry WITHOUT one, and a host on a
|
||||
# SUBDOMAIN - the last because mta-sts.sub.<domain> is one label deeper
|
||||
# than *.<domain> and a parent wildcard does not cover it.
|
||||
#
|
||||
# Both TLS fields are INERT IN THIS MODE: TLS sits on the Gateway's
|
||||
# listener and this chart renders no certificate for an HTTP hostname
|
||||
# here. They are set anyway, and that is the assertion - the render has to
|
||||
# ACCEPT them in Gateway mode without requiring them, because the mirror
|
||||
# image (tlsSecretName required in Ingress mode) is what makes them
|
||||
# mode-specific rather than optional-everywhere.
|
||||
#
|
||||
# The Ingress half of this feature is NOT exercised here - this overlay's
|
||||
# whole job is to switch the mode off. It is exercised by
|
||||
# ci/stalwart/test-values.yaml instead, which installs in Ingress mode and
|
||||
# therefore has the MTA-STS Ingress objects accepted by a real API server.
|
||||
ingress:
|
||||
enabled: false
|
||||
httpRoute:
|
||||
@@ -59,3 +79,19 @@ httpRoute:
|
||||
- name: public
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
mtaSts:
|
||||
hosts:
|
||||
- host: mta-sts.ci.example.com
|
||||
tlsSecretName: tls-mta-sts-ci-example-com-ingress
|
||||
clusterIssuer: ci-selfsigned
|
||||
# One label deeper: its own secret, because the parent's wildcard would not
|
||||
# reach it. In this mode that is the Gateway listener's problem, which is
|
||||
# exactly why the entry is here and why the values are inert.
|
||||
- host: mta-sts.sub.ci.example.com
|
||||
tlsSecretName: tls-mta-sts-sub-ci-example-com-ingress
|
||||
clusterIssuer: ci-selfsigned
|
||||
# No clusterIssuer: "the certificate comes from somewhere else". Nothing in
|
||||
# this mode reads it either way; the point is that the render accepts the
|
||||
# entry without it.
|
||||
- host: mta-sts.other.ci.example
|
||||
tlsSecretName: tls-some-existing-wildcard-ingress
|
||||
@@ -83,6 +83,37 @@ ingress:
|
||||
clusterIssuer: ci-selfsigned
|
||||
nodeGroup: full
|
||||
|
||||
# MTA-STS policy routes (Gitea #123). Set HERE and not only in the render
|
||||
# overlay, because this is the ONLY stage that puts the INGRESS half of the
|
||||
# feature in front of a real API server: ci/httproute/stalwart.yaml switches
|
||||
# the chart into Gateway mode, so it never renders these objects at all.
|
||||
#
|
||||
# WHAT THIS ADDS OVER THE RENDER TEST: that two Ingress objects whose names
|
||||
# differ only by the issuer suffix are both accepted, that a `pathType: Exact`
|
||||
# on `/.well-known/mta-sts.txt` is valid, and that a tls: list grouping several
|
||||
# hosts under one secret is accepted as written. None of that is a template
|
||||
# question any more once the API server has taken it.
|
||||
#
|
||||
# THE TWO ENTRIES ARE TWO OBJECTS, and that is the point of the pair: the first
|
||||
# carries a clusterIssuer and gets the cert-manager annotation, the second has
|
||||
# none and gets no annotation - which cannot be one object, because the
|
||||
# annotation is object-scoped. The second is also a SUBDOMAIN, one label deeper
|
||||
# than the first, carrying the case a parent wildcard certificate would not
|
||||
# cover.
|
||||
#
|
||||
# NOTHING ISSUES EITHER CERTIFICATE here: cert-manager is not installed in the
|
||||
# k3d cluster, so the annotation is inert and the named Secrets never exist.
|
||||
# That is the same deliberate trade as certificate: below - the objects are
|
||||
# asserted, the issuance is not, and no Secret is needed for the install to
|
||||
# reach Ready because nothing mounts these.
|
||||
mtaSts:
|
||||
hosts:
|
||||
- host: mta-sts.ci.example.com
|
||||
tlsSecretName: tls-mta-sts-ci-example-com-ingress
|
||||
clusterIssuer: ci-selfsigned
|
||||
- host: mta-sts.sub.ci.example.com
|
||||
tlsSecretName: tls-mta-sts-sub-ci-example-com-ingress
|
||||
|
||||
certificate:
|
||||
# On, against a STUB CRD (fixtures.yaml). Nothing issues the Secret it names,
|
||||
# which is the interesting case: the volume is mounted optional, so the pods
|
||||
|
||||
Reference in new issue
Block a user