feat: Gateway-API as an alternative to the Ingress in eight charts
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 9s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 17s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 19s
Validate Helm Charts / Build garage (pull_request) Successful in 8s
Validate Helm Charts / Build patchmon (pull_request) Successful in 7s
Validate Helm Charts / Build rallly (pull_request) Successful in 7s
Validate Helm Charts / Build searxng (pull_request) Successful in 6s
Validate Helm Charts / Build sure (pull_request) Successful in 7s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 6s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 18s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 7s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 16s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 18s
Install-test / Install-test bentopdf (pull_request) Successful in 1m10s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 22s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 18s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 17s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 19s
Install-test / Install-test garage (pull_request) Successful in 1m17s
Install-test / Install-test patchmon (pull_request) Successful in 1m24s
Install-test / Install-test rallly (pull_request) Successful in 1m39s
Install-test / Install-test searxng (pull_request) Successful in 1m7s
Install-test / Install-test sure (pull_request) Successful in 2m39s
Install-test / Install-test voucher-vault (pull_request) Successful in 2m9s
Validate Helm Charts / Install-test (pull_request) Successful in 13m15s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m50s
Publish BentoPDF Helm Chart / Publish BentoPDF Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Garage Helm Chart / Publish Garage Helm Chart (push) Successful in 9s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Rallly Helm Chart / Publish Rallly Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish SearXNG Helm Chart / Publish SearXNG Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Sure Helm Chart / Publish Sure Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Urlaubsverwaltung Helm Chart / Publish Urlaubsverwaltung Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Patchmon Helm Chart / Publish Patchmon Helm Chart (push) Successful in 9s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish VoucherVault Helm Chart / Publish VoucherVault Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
SonarQube Analysis / SonarQube Analysis (push) Successful in 43s

Group 1 of the #114 sweep: the charts that render exactly one Ingress object
and set no behaviour-bearing nginx annotation - bentopdf, garage, patchmon,
rallly, searxng, sure, urlaubsverwaltung, voucher-vault.

Each gets the form the pilot (cyberchef, PR #115) fixed and template-chart
carries: an httpRoute block (enabled/parentRefs/hostnames, off by default), a
template rendering ONLY an HTTPRoute with parentRefs - no Gateway, no
gatewayClassName, no Certificate, no implementation-specific policy object -
render aborts on both modes at once (naming both value paths), on an empty
parentRefs and on a missing hostname, empty hostnames meaning [ingress.domain],
a schema entry closed against unknown keys with an open parentRef definition,
and a NOTES.txt that names the active mode and what the mode costs.

Minor, additive, defaults unchanged: `helm template` with each chart's CI
values is byte-identical to origin/main for seven of the eight, and for sure
identical in every rendered value (the diff is two YAML comment lines).

Three findings that were not in the plan, each argued in the file it changes:

  sure derived RAILS_ASSUME_SSL/RAILS_FORCE_SSL from ingress.enabled alone.
  Gateway mode sets that false by construction while TLS is still terminated at
  the Gateway, so both flags would have gone false: insecure cookies, no HSTS,
  http:// links, with a Ready pod and a working UI as the only symptom. They now
  read sure.publishedOverTls (ingress.enabled OR httpRoute.enabled), which is
  ingress.enabled while httpRoute.enabled is false - hence still Minor.

  garage claimed, in its Ingress template and its schema, that nothing outside
  that template reads ingress.domain. The HTTPRoute reads it, so the claim now
  holds only while both modes are off. Corrected rather than left standing.

  garage also depends on an upload limit it does not set: its annotations
  comment tells the operator to set proxy-body-size: "0" because ingress-nginx
  truncates S3 uploads at 1 MB. proxy-body-size has no Gateway-API counterpart,
  so its Gateway-mode NOTES prints that the limit is the Gateway's. README's
  list of affected charts named five and now names garage as a sixth.

clusterIssuer needed no change in any of the eight: its only required() sits
inside the Ingress template behind ingress.enabled, the same no-op the pilot
found. Annotation guidance that stops applying in Gateway mode moved into the
Gateway branch of NOTES.txt for bentopdf (X-Robots-Tag) and searxng
(forward-auth).

CI: one render overlay per chart under ci/httproute/, so the assertion is not
vacuum-green - ci/run-httproute-match-test.sh goes from 4 held assertions over
2 charts to 20 over 10. The install-test fixtures stay in Ingress mode: the
install runs one configuration per chart, cyberchef already pays that price
against the real Gateway-API CRDs, and for garage the Ingress is the only place
its Service port is referenced by NAME.

Not proven, by construction: that traffic flows. No Gateway controller runs in
CI. Not run locally either: k3d, Docker, Trivy.

Part of #114
This commit was merged in pull request #116.
This commit is contained in:
jklein committed 2026-10-12 16:06:18 +00:00
1 parent de92e59bb0
commit 33d7cedbab
51 files changed
+2303 -107

No files matched your search

+3 -2
View File
@@ -383,10 +383,11 @@ These guidelines are **binding for every current and future chart** in this repo
- **`parentRefs` is mandatory in Gateway mode**, enforced by a `fail` on the empty list. A route without a parent attaches to nothing and is simply inert — created, schema-valid, rejected by nobody, reported by nothing.
- **One source of truth for the hostname.** Empty `hostnames` means `[ingress.domain]`; two values that have to agree drift apart. `ingress.domain` is therefore read in Gateway mode too — it names the release's public hostname and is not an Ingress switch.
- **`NOTES.txt` says which mode is active**, as `keycloak` already does for its cases, and names what the mode costs.
- **What has no counterpart, and must be said rather than rebuilt.** The Gateway API has no annotation channel for controller behaviour. `rewrite-target` has a real equivalent (the `URLRewrite` filter in a rule); **`proxy-body-size` has none** — an upload limit is implementation-specific policy, so a chart that depends on one (`bulwark`, `calibre-web-automated`, `outline`, `paperless-ngx`, `zipline`) must print in its Gateway-mode `NOTES.txt` that the limit is **not enforced** and has to be set at the Gateway. The same applies to auth annotations in front of an app with no login of its own: in Gateway mode that escape hatch does not exist in the chart at all.
- **What has no counterpart, and must be said rather than rebuilt.** The Gateway API has no annotation channel for controller behaviour. `rewrite-target` has a real equivalent (the `URLRewrite` filter in a rule); **`proxy-body-size` has none** — an upload limit is implementation-specific policy, so a chart that depends on one must print in its Gateway-mode `NOTES.txt` that the limit is **not enforced** and has to be set at the Gateway. Five charts *set* one by default (`bulwark`, `calibre-web-automated`, `outline`, `paperless-ngx`, `zipline`), and `garage` is a sixth that *depends* on one without setting it: its `ingress.annotations` comment tells the operator to set `proxy-body-size: "0"` because ingress-nginx's 1 MB default truncates S3 uploads. Measured while sweeping group 1 — "sets no behaviour-bearing annotation" is not the same question as "needs none", and the sweep screens on the second one. The same applies to auth annotations in front of an app with no login of its own: in Gateway mode that escape hatch does not exist in the chart at all (`cyberchef`, `searxng`), and so does any annotation a chart documents as its only lever for a response header (`bentopdf`: `X-Robots-Tag`).
- **A value derived from `ingress.enabled` is a second thing the sweep has to look for, and it is not visible in the Ingress template.** Gateway mode sets `ingress.enabled: false` *by construction*, so any value computed from it flips — and where it stood for "is there a TLS-terminating proxy in front", the flipped value is wrong while nothing fails. Found in `sure`, whose `RAILS_ASSUME_SSL`/`RAILS_FORCE_SSL` would have gone `false` with TLS terminated at the Gateway: insecure cookies, no HSTS, `http://` links, green pod. The fix is one helper reading `ingress.enabled OR httpRoute.enabled`, not a new value. Still open on this count: `freesailarr` and `ytdlp-web-player` both read `.Values.ingress.enabled` outside their Ingress templates.
- **CI**: either the chart's install-test fixture installs in Gateway mode, or it gets a render-only overlay at `ci/httproute/<chart>.yaml` — otherwise `ci/run-httproute-match-test.sh` is green over nothing for it, which that test fails on.
Reference implementation: `template-chart/templates/xxx.httproute.yaml`. Pilot: `cyberchef`. The remaining 20 charts follow in their own PRs, with the five special cases (`matrix-synapse` renders in a loop, `keycloak` and `bulwark` have two Ingresses each, `homeassistant` is the documented ingress exception, `garage` ships `ingress.enabled: false`, `stalwart` renders per node group) argued one at a time.
Reference implementation: `template-chart/templates/xxx.httproute.yaml`. Pilot: `cyberchef`. Group 1 — the charts rendering one Ingress with no behaviour-bearing annotation — followed: `bentopdf`, `garage`, `patchmon`, `rallly`, `searxng`, `sure`, `urlaubsverwaltung`, `voucher-vault`. That leaves **twelve** charts plus `rss-bridge`, which is frozen for changes (#51) and is why #114 cannot be closed while #51 is open: group 2 is the annotation cases (`calibre-web-automated`, `outline`, `paperless-ngx`, `zipline`, `freesailarr`) and group 3 the special shapes (`homeassistant` is the documented ingress exception, `keycloak` and `bulwark` have two Ingresses each, `matrix-synapse` renders in a loop, `stalwart` renders per node group, `ytdlp-web-player` hangs off the oauth2-proxy subchart). `garage` was one of the named special cases and is done: it ships `ingress.enabled: false`, so both of its modes are off by default and its route is exercised by a render overlay rather than by the install-test, which keeps its Ingress.
- **Documented exception — freesailarr**: the chart bundles several independently exposed apps in one pod, so it has **no** `ingress.domain`. It offers both shapes. By default each app carries its own `<app>.ingress.enabled` plus a required `<app>.ingress.host` (required only while that app's ingress is enabled and it is not on the shared domain) and renders its own Ingress object `<release>-<chart>-<app>-ingress` with its own TLS secret `tls-<release>-<chart>-<app>-ingress`. Setting the global `ingress.host` switches every app without a host of its own to the shared-domain shape under `<app>.ingress.path`; qBittorrent (no base path, needs a prefix-stripping rewrite) gets the extra object described above, and seerr as well as mediathekarr are pinned to `/` — mediathekarr because its UI links assets with root-absolute paths, which a rewrite cannot fix; with both of them on one shared host the duplicate-path guard fires and one of them needs a host of its own (its ingress is therefore off by default). `clusterIssuer`, `className` and `annotations` stay global defaults under `ingress.` and are overridable per app.
### 5. Security hardening