Commit Graph
3 Commits
Author SHA1 Message Date
jklein f6eeff87c2 feat(bulwark): Gateway-API as an alternative to the Ingress
Group 3 of the #114 sweep. One route, one rule, at the root - and THREE
EXPECTATIONS ABOUT THIS CHART DID NOT SURVIVE CONTACT WITH IT:

  - #114 says bulwark has TWO Ingress objects. It has ONE. keycloak has two.
  - #114's measured table lists bulwark under rewrite-target. It has no
    rewrite-target annotation and CANNOT have one: NEXT_PUBLIC_BASE_PATH is
    compiled into the bundle, so the application cannot be served under a
    sub-path, and stripping a prefix at the proxy would yield a page that
    loads and then fetches everything from the wrong place. Every occurrence
    of the string "rewrite-target" in this chart - values.yaml, the schema,
    the Ingress template - is an explanation of exactly that. A grep for the
    string finds the refutations and counts them as occurrences.
  - ci/run-httproute-match-test.sh said in its header that bulwark was the
    second rewrite-target chart and would meet the #118 prefix/redirect
    assurance when group 3 landed. Group 3 landed and it does not: with no
    URLRewrite on a prefix other than "/", the assurance's precondition is
    never met here. The header is corrected rather than left standing, and it
    now says freesailarr is the only chart the pairing applies to.

So no redirect rule is added, and none is needed.

WHAT THIS MODE DROPS, MEASURED ON THE LIVE OBJECT rather than inferred. On
2026-10-12 the live Ingress on webmail.bekemeyer-klein.de carried

    nginx.ingress.kubernetes.io/proxy-body-size: 25m

supplied by the OPERATOR through ingress.annotations - values.yaml recommends
exactly that value and explains why the chart cannot pick it, so the chart
sets nothing and a reader of this repository alone would not find it. It is
the single annotation this installation depends on, and the Gateway API has no
body-size counterpart: implementation policy, not routing. Per decisions 1 and
2 of #114 it is NAMED, not rebuilt - in values.yaml, in the schema and in the
Gateway branch of NOTES.txt, in this application's own terms: an attachment
above the Gateway's default is rejected before Bulwark sees a byte, the 413
comes from the proxy, and it arrives after the user has written the mail and
attached the file.

CORRECTED CLAIM, the same one garage and calibre-web-automated needed: the
Ingress template, the bulwark.publicUrl helper and the schema all stated that
nothing outside the Ingress template FUNCTIONALLY reads ingress.domain. The
HTTPRoute reads it, so the claim held only while both modes were off. All
three now say so, and the route carries a required() of its own.

bulwark.publicUrl follows the ACTIVE mode instead of always printing
ingress.domain. The two differ exactly when httpRoute.hostnames is set, which
is how a migration off the Ingress runs - and printing the old host there
would hand the operator an OIDC redirect URI for the hostname being migrated
away from, a failure that surfaces as a rejected login at the identity
provider and says nothing about this chart.

Minor: additive, defaults unchanged. Empty helm template diff against
origin/main with ci/bulwark/test-values.yaml.

Part of #114
2026-10-12 20:49:56 +00:00
jklein f202fdc3fc fix(freesailarr): redirect the bare /qbittorrent prefix in Gateway mode
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 / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 8s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 22s
Install-test / Install-test freesailarr (pull_request) Successful in 4m19s
Validate Helm Charts / Install-test (pull_request) Successful in 4m19s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 12s
Publish Freesailarr Helm Chart / Publish Freesailarr Helm Chart (push) Successful in 12s
SonarQube Analysis / SonarQube Analysis (push) Successful in 48s
qBittorrent has no base-path setting, so the chart serves it under a prefix
and strips that prefix before the request reaches it. For the slashed form
that is correct. For the BARE form - /qbittorrent, no trailing slash - the
request is rewritten to "/" and qBittorrent answers 200 with its login page,
while the browser's document URL keeps the prefixless form. Every relative
link in that page therefore resolves against the host root, and on the shared
host the root is seerr: the login form comes up unstyled and does nothing
while a different application answers its assets and XHRs. Nothing reports an
error at any level.

The shared-domain HTTPRoute now carries a second rule for qBittorrent: an
Exact match on qbittorrent.ingress.path with a RequestRedirect filter that
replaces the full path with the slashed form, statusCode 301. The Gateway API
ranks an Exact path match above every prefix match, so it wins over both
qBittorrent's own PathPrefix rule and seerr's "/" wherever it is emitted - the
rules' length-descending order stays a rendering convention. 301 rather than
308 because only 301 and 302 are Core in that API; an Extended value an
implementation declines makes it set Accepted=False on the WHOLE route, which
would take the four neighbouring apps' routing down with it, and that would
also contradict decision 1 of Gitea #114. statusCode is written out because it
defaults to 302. The rule is guarded on the app being qbittorrent and its path
not being "/", and it reads the path from the value, never a literal.

The INGRESS BRANCH IS UNCHANGED, deliberately. The remedy there is an
nginx configuration-snippet annotation, and the chart cannot ship it: snippets
are ignored unless the controller runs with allow-snippet-annotations=true,
off by default since ingress-nginx 1.9 and only switched on by accepting
annotation risk level "Critical". A shipped annotation would be a silent no-op
on every cluster keeping the default. The working snippet, that price, and why
a third Ingress object with pathType: Exact is not a substitute - buildLocation
in ingress-nginx controller-v1.14.5 returns a regex location in its
enforceRegex branch before reaching the PathTypeExact branch, and enforceRegex
is computed once per nginx server over every location of the hostname, one of
which carries rewrite-target - are all written out in values.yaml instead.

ci/run-httproute-match-test.sh gains a third assertion, per HTTPRoute object:
a rule stripping a prefix with ReplacePrefixMatch requires a rule in the same
route matching that path Exactly and carrying a RequestRedirect filter. This
is what stops the gap reappearing; bulwark is the second rewrite-target chart
and meets the same check when group 3 of Gitea #114 lands. 37 assertions over
15 charts, up from 36. Both fail paths were exercised: the rule deleted, and
the redirect pointed at a path other than the one stripped.

Minor and not major: with the Ingress branch untouched and httpRoute.enabled
defaulting to false, no existing release's render changes by a byte and no
value becomes required. Shown by an empty helm template diff against
origin/main in both Ingress shapes, shared-domain and per-app hosts.

Fixes #118
2026-10-12 20:06:09 +00:00
jklein 50f683776e feat(cyberchef): Gateway-API as an alternative to the Ingress (pilot)
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 18s
Validate Helm Charts / Build template-chart (pull_request) Successful in 7s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Install-test / Install-test cyberchef (pull_request) Successful in 1m0s
Validate Helm Charts / Install-test (pull_request) Successful in 1m0s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
The Gateway-API pilot of Gitea #114: one chart, the convention, the CI that
holds it. The remaining 20 charts follow in their own PRs.

The form, additive and off by default:

    httpRoute:
      enabled: false     # counterpart to ingress.enabled
      parentRefs: []     # the Gateway - a platform object
      hostnames: []      # empty = [ingress.domain]

Only HTTPRoute with parentRefs is rendered: no Gateway, no gatewayClassName,
no Certificate, and no implementation-specific policy object. That subset is
identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, which is
what lets the implementation be chosen later without touching the charts
again. Class and certificate move to the platform.

Four guards, all against silent failures:

  - mutually exclusive with ingress.enabled, with a render abort naming BOTH
    value paths. A migration runs over a second hostname, not in parallel.
  - parentRefs mandatory in Gateway mode, render abort on the empty list: a
    route without a parent is created, is schema-valid, and routes nothing.
  - hostnames empty means [ingress.domain], so the hostname has one source.
  - the mode is a VALUE, not auto-detection: .Capabilities.APIVersions is
    unreliable at template time and a missing CRD fails at install time.

ingress.clusterIssuer was already only read inside the Ingress template, so
the "conditionally required" half of #114 needed no change in this chart -
checked rather than assumed, and it has to be re-checked per chart in the
sweep.

CI, both halves, because neither covers the other:

  - the install-test installs cyberchef in GATEWAY mode against the REAL
    Gateway-API CRDs (standard channel v1.6.3, pinned and Renovate-tracked,
    applied --server-side). A schema-less stub would accept a malformed route
    and prove only that something was created. The Ingress path of this one
    chart is no longer installed; it is a built-in type that never routed in
    k3d anyway, and helm lint renders this chart's defaults - Ingress mode -
    on every PR.
  - ci/run-httproute-match-test.sh, over every chart on every PR: every
    backendRefs entry must name a Service, on a port, that the SAME render
    creates, and parentRefs must not be empty. The CRDs accept both of those
    mistakes, so the schema cannot catch them, and the only report would be a
    route status no controller writes in CI.

What stays unprovable, and is written down instead of implied: that traffic
flows. No Gateway controller runs in k3d.

Minor: additive with unchanged defaults, proven by an empty helm template
diff against origin/main for the default values.

Part of #114
2026-10-12 15:16:26 +00:00