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
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
This commit was merged in pull request #119.
This commit is contained in:
6 files changed
+345
-23
No files matched your search
+81
-1
@@ -230,7 +230,12 @@ media:
|
||||
# prefix. (In GATEWAY MODE that second object is gone: the Gateway API's
|
||||
# URLRewrite filter is per RULE, not per object, so qBittorrent is one rule
|
||||
# in the shared route while its neighbours stay unrewritten. The traps
|
||||
# below are the application's and apply in both modes.) Two traps from the
|
||||
# below are the application's and apply in both modes.) CALL ITS PATH WITH
|
||||
# A TRAILING SLASH: the bare /qbittorrent is stripped to "/", answered 200
|
||||
# by qBittorrent, and its relative links then resolve to the root of the
|
||||
# shared host - which is seerr. Gateway mode renders a 301 for this itself;
|
||||
# Ingress mode cannot, and the nginx remedy plus its price is written out
|
||||
# at qbittorrent.ingress below (Gitea #118). Two further traps from the
|
||||
# upstream recipe, both of which look like a login failure and therefore
|
||||
# point in the wrong direction:
|
||||
# * qBittorrent marks its session cookie "Secure". Reaching the UI over
|
||||
@@ -277,6 +282,14 @@ ingress:
|
||||
# an annotation YOU added here is lost without a word; a body-size cap for
|
||||
# qBittorrent's torrent-file uploads is the likely candidate, and it then has
|
||||
# to be set at the Gateway or through its implementation's own policy object.
|
||||
#
|
||||
# ONE ANNOTATION IS WORTH ADDING HERE IN INGRESS MODE and the chart cannot
|
||||
# ship it: the configuration-snippet that redirects the bare /qbittorrent to
|
||||
# /qbittorrent/. It is written out, with its allow-snippet-annotations price,
|
||||
# at qbittorrent.ingress below (Gitea #118). In Gateway mode it is neither
|
||||
# needed nor possible - the chart renders that redirect as a rule of the
|
||||
# route - so an operator who set it here does not lose a behaviour on
|
||||
# migration, unlike the body-size case above.
|
||||
annotations: {}
|
||||
|
||||
# Gateway-API alternative to the Ingress above (Gitea #114). Off by default, so
|
||||
@@ -421,6 +434,73 @@ qbittorrent:
|
||||
# path is only read in the shared-domain mode (see the ingress block above);
|
||||
# it defaults to /<app> and is ignored while this app has a host of its own.
|
||||
# (Same block for prowlarr, radarr, sonarr, seerr and bazarr below.)
|
||||
#
|
||||
# CALL THIS PATH WITH A TRAILING SLASH IN INGRESS MODE (Gitea #118)
|
||||
#
|
||||
# qBittorrent has no base-path setting, so the prefix is stripped before the
|
||||
# request reaches it. A request for the bare path - https://host/qbittorrent,
|
||||
# no trailing slash - is therefore stripped to "/", which qBittorrent answers
|
||||
# 200 with its login page. Nothing reports an error anywhere. But the
|
||||
# browser's document URL stays prefixless, so every RELATIVE link in that
|
||||
# page resolves against the host root - and "/" on the shared host is SEERR.
|
||||
# The result is an unstyled login form whose assets, XHRs and form posts are
|
||||
# all answered by a different application, which is a symptom that points
|
||||
# nowhere useful. https://host/qbittorrent/ works correctly. The same applies
|
||||
# to any custom path set here; it is a property of prefix stripping, not of
|
||||
# the default value.
|
||||
#
|
||||
# IN GATEWAY MODE THE CHART RENDERS THE REDIRECT ITSELF. qBittorrent's rule
|
||||
# in the shared HTTPRoute is joined by a second rule matching the bare path
|
||||
# EXACTLY, whose RequestRedirect filter answers 301 to the slashed form. The
|
||||
# Gateway API ranks an Exact match above every prefix match, so it wins over
|
||||
# both qBittorrent's own PathPrefix rule and seerr's "/" regardless of
|
||||
# ordering. 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, taking the neighbouring apps' routing
|
||||
# with it. One asymmetry to the nginx remedy below: the Gateway API's
|
||||
# redirect filter has no field for the query string (it replaces the full
|
||||
# PATH), while the nginx snippet carries it over explicitly with
|
||||
# $is_args$args. It costs nothing on the UI entry path, which takes no query
|
||||
# parameters, but it is a real difference.
|
||||
#
|
||||
# IN INGRESS MODE THE CHART RENDERS NO REDIRECT, and that is a limitation,
|
||||
# not an oversight. The remedy exists but cannot be shipped: put an nginx
|
||||
# snippet into the global ingress.annotations (it is merged into both of this
|
||||
# chart's Ingress objects on the shared host, which is what makes it work
|
||||
# from there):
|
||||
#
|
||||
# ingress:
|
||||
# annotations:
|
||||
# nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
# if ($uri = "/qbittorrent") { return 301 $scheme://$host/qbittorrent/$is_args$args; }
|
||||
#
|
||||
# ITS PRICE, which is why it is documented rather than added: snippet
|
||||
# annotations are only honoured on a controller started with
|
||||
# allow-snippet-annotations=true, and since ingress-nginx 1.9 that flag is
|
||||
# OFF by default. Switching it on also means accepting
|
||||
# annotations-risk-level: Critical, because a configuration snippet is
|
||||
# arbitrary nginx configuration injected from a namespaced object. A chart
|
||||
# that shipped this annotation would silently render a no-op on every cluster
|
||||
# that keeps the default, which is worse than naming the gap. The decision
|
||||
# belongs to whoever runs the ingress controller.
|
||||
#
|
||||
# AND A THIRD INGRESS OBJECT WITH pathType: Exact IS NOT A SUBSTITUTE here,
|
||||
# which is the obvious-looking alternative and does not work. In
|
||||
# ingress-nginx controller-v1.14.5, internal/ingress/controller/template/
|
||||
# template.go, buildLocation() returns a plain regex location
|
||||
# (~* "^<path>") in its enforceRegex branch and RETURNS THERE - before it
|
||||
# ever reaches the PathTypeExact branch that would emit the = "<path>"
|
||||
# location nginx gives precedence to. And enforceRegex is computed once per
|
||||
# nginx SERVER, not per location: rootfs/etc/nginx/template/nginx.tmpl
|
||||
# evaluates enforceRegexModifier over every location of a hostname, and it
|
||||
# returns true as soon as one of them needs a rewrite. qBittorrent's own
|
||||
# object carries rewrite-target on this very hostname, so enforceRegex is
|
||||
# true for the whole server and an Exact path would render as an ordinary
|
||||
# regex location. The exact-match precedence the idea depends on never comes
|
||||
# into being. The unsuitability follows from that short-circuit alone; what
|
||||
# an unanchored regex location would then do to ORDERING - shadow the app, or
|
||||
# loop the redirect, depending on emission order - is inferred from the same
|
||||
# sources and has NOT been observed on a running nginx.
|
||||
ingress:
|
||||
enabled: true
|
||||
host: null
|
||||
|
||||
Reference in new issue
Block a user