feat(stalwart): MTA-STS routes as a maintainable list, in both routing modes #126
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
978e6fbaa8
|
refactor(stalwart): name the MTA-STS Ingress objects by index, per the corrected rule
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Resource quantities (pull_request) Successful in 16s
Validate Helm Charts / Selector match (pull_request) Successful in 15s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 16s
Validate Helm Charts / Build stalwart (pull_request) Successful in 9s
Validate Helm Charts / Trivy config stalwart (pull_request) Successful in 29s
Install-test / Install-test stalwart (pull_request) Successful in 1m42s
Validate Helm Charts / Install-test (pull_request) Successful in 1m42s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 13s
Publish Stalwart Helm Chart / Publish Stalwart Helm Chart (push) Successful in 13s
SonarQube Analysis / SonarQube Analysis (push) Successful in 50s
Maintainer's decision, and it reverses this chart's naming rather than the
style guide's rule. The objects become
<release>-stalwart-mta-sts-ingress, -1, -2, ...
in this repo's idiom ({{ if $i }}-{{ $i }}{{ end }}), the same as
homeassistant's routes and keycloak's realm Ingresses, inlined in the
template instead of behind a helper of its own.
WHY IT GOES THIS WAY. This chart named by issuer, reading the style guide's
index rule as resting on "a route holds no state and no certificate" and
observing that these objects hold one. That reading was right and the
rule's STATED REASON was wrong: the parallel #122 round named by index,
cited the same rule as binding, and entered keycloak into it - whose realm
Ingresses hold certificates too. Two charts did opposite things for one
shape, and the rule had been extended onto cases its own precondition
excluded. #125 is merged and published, so the reason is repaired and the
naming unified on index; keycloak is not touched.
What actually carries the index rule, in three steps that have different
standing and are written as such in the template header and the style guide:
cert-manager here runs WITHOUT --enable-certificate-owner-ref (MEASURED by
the maintainer - absent from the Deployment's args and from the
ControllerConfiguration its --config names, which carries only
kind: ControllerConfiguration and enableGatewayAPI: true); by that default
the Certificate is collected with its Ingress but the SECRET IS NOT
(DOCUMENTED behaviour, not measured here), so a rename recreates the
Certificate object against a surviving secret and issues nothing; and NO
RENAME WAS APPLIED against a cluster. The promise therefore hangs on a
cluster-level flag this repository does not control - with it on, a reorder
would cost a fresh ACME request per renumbered object.
UNCHANGED: the structure. One Ingress per distinct clusterIssuer, one rule
per host, tls grouped by secret, and the abort when one secret spans two
issuers - grouping is a cert-manager question and naming is a style
question, and they are independent.
TWO THINGS THE RENAME OBLIGES.
1. NOTES.txt now prints the mapping - index, object name, issuer, secrets
and hosts per object, the way keycloak's does for its realms - because
the name no longer carries the issuer and nothing else in the cluster
says which object holds which.
2. The reordering hazard is documented as SHARPER than homeassistant's:
there an index names a host, here it names an issuer GROUP derived from
the list, so moving one entry can change the grouping and renumber
objects that were not themselves moved.
AN ASYMMETRY WITH keycloak, named in the template header: there both modes
render one object per entry, so the index question arises twice. Here it
arises on the Ingress side ONLY - Gateway mode renders one HTTPRoute
carrying every hostname, because TLS is none of the route's business, so
there is no index and no name to go stale.
Version stays 1.3.0+upv0.16.25: nothing is published yet, so nothing is
being renamed in a release.
Re-measured after the rename, not assumed: three empty helm template diffs
against origin/main (defaults, CI fixture, overlay), helm lint --strict
clean three ways, every abort path still firing with the structural one
still winning over the per-entry one, the schema still rejecting
mtaSts.enabled, and the HTTPRoute render test still at 64 assertions over
22 charts - object names are not assertions.
|
||
|
|
71b757bf9f
|
docs(README): the MTA-STS delta on top of both landed rounds, measured at 64/22
Validate Helm Charts / Build stalwart (pull_request) Successful in 9s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Resource quantities (pull_request) Successful in 16s
Validate Helm Charts / Selector match (pull_request) Successful in 15s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 16s
Validate Helm Charts / Trivy config stalwart (pull_request) Successful in 24s
Install-test / Install-test stalwart (pull_request) Successful in 1m36s
Validate Helm Charts / Install-test (pull_request) Successful in 1m36s
Second rebase of this branch, and the last one the parallel rounds force. Both have now merged - rss-bridge plus its documentation corrections, and the realm hostnames of #122 - so line 184 carries the chain 56/21 -> 58/22 -> 62/22 with the delta convention #124 introduced. That chain is kept WORD FOR WORD and this round's delta is appended to it; the only change to the line is an append, verified line by line against origin/main rather than asserted. THE NUMBER IS READ, NOT COMPUTED, and that distinction is this paragraph's own rule. 62 + 2 = 64 is arithmetic over a branch that had a different substructure when its 2 were measured; the figure committed here comes from ci/run-httproute-match-test.sh on THIS branch, rebased onto the main that already holds both other rounds: 64 assertions over 22 charts, 0 failed, with stalwart contributing 4. It is therefore the first figure in that sentence that is a genuine running total rather than a delta - the first run that contains all three rounds, which is exactly where the convention says to read it. The earlier 58/21 and 60/22 in this branch's history were each correct for the substructure they were measured on and are both superseded. The commit that set 60/22 was dropped in this rebase rather than carried, since its only content was a number that no longer holds. |
||
|
|
771c26500c
|
fix(stalwart): let the mode conflict, not the MTA-STS entry, name the defect
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. |
||
|
|
b4c9823e01
|
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
|