Skip to content

server.route / adminConsole.route cannot express path matches or filters, and silently render a catch-all route for sub-path deployments #12

Description

@tada5hi

Problem

server.route / adminConsole.route render an HTTPRoute whose only rule is a bare backendRefs. There is no way to express a matches[].path, a filters[] entry, or a raw rules override.

templates/_ingress.tpl:112-116:

 rules:
- backendRefs:
- name: {{ .serviceName }}
port: {{ .servicePort }}

values.yaml:744-755 exposes only enabled / hostnames / parentRefs / annotations (identically for adminConsole.route, values.yaml:1045-1056), and values.schema.json sets additionalProperties: false on both route objects, so nothing extra can be passed through either.

The result is that route only works for a dedicated hostname serving authup at /. That is the one topology it supports, and it is not the topology the rest of the chart is built for.

It is not merely missing — it silently emits a wrong manifest

_ingress.tpl:99-104 derives the route hostname from the component's public URL via authup.urlOrigin, and _urls.tpl:58-65 returns scheme://host only — the path is discarded.

So an operator who follows the chart's own documented sub-path setup:

server:
publicUrl: "https://hub.example.com/auth"route:
enabled: trueparentRefs:
- name: my-gateway

gets:

spec:
parentRefs:
- name: my-gatewayhostnames:
- hub.example.comrules:
- backendRefs:
- name: release-authup-serverport: 3000

Per the Gateway API spec, an HTTPRouteRule with unspecified matchesdefaults to a PathPrefix / match, i.e. it matches every request. The chart consumed a /auth public URL, threw the path away, and attached authup as the catch-all backend for a hostname it is meant to share. On a shared-hostname gateway the more specific sibling routes still win their own prefixes, but every unmatched request on that host now lands on the IdP. There is no value that can correct this.

route.annotations cannot substitute

For Ingress, the chart documents the proxy-buffer workaround authup's large token responses need (values.yaml:729-730, README.md:367: "Token responses are large; with ingress-nginx consider proxy-buffer-size 16k+"). For Gateway API there is no annotation equivalent — NGINX Gateway Fabric requires an ExtensionRef filter to a gateway.nginx.org/v1alpha1 SnippetsFilter, which is a first-class spec field, not an annotation. So the route's single knob cannot address the one operational hazard the chart itself flags.

Parity gap against the chart's own design

The Ingress renderer ships four escape hatches — extraPaths, extraHosts, extraTls, extraRules (_ingress.tpl:42-60, values.yaml:736-743) — plus first-class path / pathType (values.yaml:721-723). The HTTPRoute renderer ships none of them, while DESIGN.md:304-307 states that "Sub-path single-host deployments are supported (authup rebases assets off publicUrl's pathname)". That topology is expressible through server.ingress and inexpressible through server.route.

Concrete use case: PrivateAIM/flame-hub

The FLAME Hub umbrella chart serves authup alongside client-ui, server-core, storage and telemetry on one shared hostname, with authup at PathPrefix /auth. It renders this itself (charts/flame-hub/templates/authup/httproute.yaml):

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ .Release.Name }}-authup
spec:
parentRefs:
{{- include "flame-hub.gateway.parentRefs" . | nindent 8 }}
hostnames:
- {{ .Values.authup.gatewayApi.hostname | default .Values.global.flameHub.gatewayApi.hostname | quote }}
rules:
- matches:
- path:
type: PathPrefix
value: {{ .Values.authup.gatewayApi.path }} # "/auth"
{{- $rewrite := ne .Values.authup.gatewayApi.path "/" }}
{{- $ngfSnip := .Values.global.flameHub.gatewayApi.nginxGatewayFabric.snippets }}
{{- if or $rewrite $ngfSnip }}
filters:
{{- if $rewrite }}
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
{{- end }}
{{- if $ngfSnip }}
- type: ExtensionRef
extensionRef:
group: gateway.nginx.org
kind: SnippetsFilter
name: {{ .Release.Name }}-authup-ngf-snippets
{{- end }}
{{- end }}
backendRefs:
- name: {{ .Release.Name }}-authup-server-core
port: 3000

paired with (charts/flame-hub/templates/authup/snippetsfilter-timeouts.yaml):

apiVersion: gateway.nginx.org/v1alpha1kind: SnippetsFiltermetadata:
name: {{ .Release.Name }}-authup-ngf-snippetsspec:
snippets:
- context: http.server.locationvalue: | proxy_buffer_size 16k; proxy_buffers 4 16k; proxy_busy_buffers_size 16k;

Three things are required and none is expressible: (a)matches[].path PathPrefix /auth, (b) a URLRewrite filter with ReplacePrefixMatch: /, (c) an ExtensionRef filter carrying the proxy buffers.

To be explicit about scope: this does not block flame-hub's migration to the upstream chart. flame-hub keeps its own HTTPRoute and leaves server.route.enabled=false. But it does mean an umbrella consumer of this chart can never use the chart's own routing feature — and that is the deployment shape the chart otherwise targets end to end.

Proposed minimal values API

Raw Gateway API passthrough, in exactly the style route.parentRefs already uses — no new abstraction, no re-modelling of the Gateway API:

server:
route:
enabled: falsehostnames: []parentRefs: []annotations: {}# -- HTTPRoute rule matches (tpl-rendered; [] = the Gateway API default, PathPrefix "/")matches: []# -- HTTPRoute rule filters (tpl-rendered; URLRewrite, RequestHeaderModifier, ExtensionRef, ...)filters: []# -- Full custom rules (tpl-rendered; replaces the generated rule entirely)extraRules: []

Template change in templates/_ingress.tpl, replacing lines 112-116:

 rules:
{{- if $route.extraRules }}
{{- include "authup.tplvalues.render" (dict "value" $route.extraRules "context" $ctx) | nindent 4 }}
{{- else }}
- {{- if $route.matches }}
matches: {{- include "authup.tplvalues.render" (dict "value" $route.matches "context" $ctx) | nindent 8 }}
{{- end }}
{{- if $route.filters }}
filters: {{- include "authup.tplvalues.render" (dict "value" $route.filters "context" $ctx) | nindent 8 }}
{{- end }}
backendRefs:
- name: {{ .serviceName }}
port: {{ .servicePort }}
{{- end }}

plus the matching values.schema.json entries (the objects are additionalProperties: false today, so the schema must be updated or the new keys are rejected). Applies identically to adminConsole.route.

Optionally, and separately: when matches is empty but the derived/derivable public URL carries a pathname, either default a PathPrefix match from it or fail — rather than silently widening it to /. At minimum the hostnames value comment ("[] = derived from server.publicUrl / ingress hostname", values.yaml:747) should say that the derivation drops the path.

Why the workaround is not acceptable as the answer

The workaround is server.route.enabled=false plus a hand-written HTTPRoute in root extraDeploy (values.yaml:32, rendered through tpl by templates/extra-list.yaml:1-4). It does work — helpers like {{ include "authup.server.fullname" . }} and {{ .Values.server.service.ports.http }} are in scope, so names and ports do not drift. But:

  1. It makes route dead weight for its most likely consumer. Anyone deploying this chart as a subchart behind a shared gateway must not use server.route, which is exactly who a Gateway API feature is for. As shipped, the value is nearly ornamental.
  2. The failure is silent, not loud. A user who tries the documented sub-path setup first does not get a render error or a no-op; they get a live catch-all route on a shared hostname. extraDeploy is only a workaround for the user who already knows the feature is broken.
  3. It forces duplication of everything the chart already renders — labels (authup.labels), commonAnnotations, namespaceOverride handling, hostname derivation, service name and port — into user-maintained YAML that silently rots across chart upgrades. That is precisely the drift this chart's authup.tplvalues.render design exists to eliminate.
  4. The chart already rejected this answer for Ingress.ingress.extraRules exists so Ingress users do not have to escape to extraDeploy for a custom rule (values.yaml:742-743). The same rationale applies verbatim to HTTPRoute; the route side just never got it.
  5. The fix is ~10 template lines and 3 values keys, all raw passthrough matching an existing convention in the same file. The cost of closing this is smaller than the cost of documenting the workaround properly.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions