Skip to content

gateway: security-model claims are duplicated across the tree and have drifted apart #3792

Open

Description

Several places describe how the Gateway security model behaves for a tenant whose apex is not derived from its parent, and for what the port-80 listener filter protects. The descriptions do not agree with the code, and they do not agree with each other. This is not a documentation nit: the descriptions corroborate one another, so a reader who checks two of them gets what looks like confirmation, and the tree answers consistently and wrongly.

I hit this while working on an unrelated branch and reproduced the wrong model three times in text I was writing, each time after checking the tree. A code reviewer then reached the same wrong conclusion from the same line, independently. That is the reason to fix it rather than leave it: it is currently generating fresh errors, not just recording an old one.

The deciding sites. tenant.gatewayEffective in packages/apps/tenant/templates/_helpers.tpl returns false whenever .Values.gateway is unset, via kindIs "invalid", with no reference to the apex at all. cozystack-gateway-hostname-policy matches on object.metadata.namespace.startsWith("tenant-") and compares each listener hostname against the host label of the namespace holding the Gateway, which is the parent's when a child inherits. Those two decide the behaviour; everything below only describes it.

What actually happens for a child on an independent apex. The child namespace is stamped with the parent's gateway, collectInheritingChildApexes applies no apex-containment filter, and the parent's Gateway gains a listener for the child apex. The Gateway policy then compares that listener against the parent's own host label and denies the write: *.customer1.io neither equals example.org nor ends with .example.org. reconcileGateway runs before the redirect and the status step, so the parent TenantGateway stops converging. The blast radius is the parent, and nothing is served for the child on any port.

Site 1, packages/extra/gateway/README.md:96. "A child tenant with an independent apex (customer1.io instead of a subdomain) is handled correctly because the VAP reads the per-namespace label rather than assuming a subdomain hierarchy." True of the route policy, which reads the route's own namespace. The sentence says "the VAP" in the singular, and the Gateway policy reads a different namespace and reaches the opposite conclusion. This is the line that misled the reviewer.

Site 2, packages/apps/tenant/values.yaml:17, mirrored into values.schema.json:26. The gateway parameter is documented as auto-enabling the Gateway for tenants whose apex is derived from the parent. The helper does nothing of the kind: unset means false regardless of the apex.

Site 3, packages/apps/tenant/templates/namespace.yaml:46-49. States that gatewayEffective returns "true" for tenants with a custom non-derived host. It returns "false". This one matters most in practice, because it reads as though the wedge above cannot occur, when the helper is exactly what makes it reachable.

Site 4, packages/apps/tenant/tests/gateway_default_test.yaml. The custom-apex case carries the comment "The HTTPRoutes for this tenant's apps will attach to the parent Gateway but fail TLS". They never attach; the Gateway write is denied at admission first. The test itself is sound and asserts only that no own HelmRelease is rendered, but it is green, and a green test lends its comment an authority the assertion does not support.

Site 5, internal/controller/tenantgateway/reconciler_test.go:209-216. The doc comment says app HTTPRoutes attaching by hostname "cannot bind to port 80 and silently serve plaintext". Scoped to the cozy-* namespaces named beside it this holds, so it is imprecise rather than false; stated generally it is not, because a tenant that owns its Gateway is its own gateway namespace and its app routes are admitted by that filter. The test body asserts only the cozy-* exclusion and is correct.

Site 6, the redirect route has no direct e2e coverage. The Gateway chainsaw suite never names it. Coverage is indirect, through a Ready=True assertion that would fail if the route were rejected, which is real but does not pin what the route contains.

Suggested shape of a fix. Pick one canonical description, the chart README being the natural home, state the behaviour there once, and reduce the others to a pointer. The specific corrections needed are that an unset gateway never auto-enables anything, that an independent-apex child under an inherited Gateway wedges the parent rather than degrading gracefully, and that the port-80 filter excludes cozy-* and inheriting children but not the publishing tenant's own apps.

Activity

  1. added
    kind/documentationCategorizes issue or PR as related to documentation
    area/networkingIssues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn)
    on Aug 12, 2026
  2. lexfrei commented on Aug 12, 2026

    @lexfrei
    ContributorAuthor

    One note on priority for site 2. That description is not confined to the file: values.schema.json is generated from the ## @param block in values.yaml, hack/update-crd.sh carries it into the ResourceDefinition schema, and hack/check-rd-presets.sh guards the two against drift. So the wrong sentence travels into the API surface, and an operator deciding whether to set gateway reads it there rather than in the chart.

  3. lexfrei commented on Aug 12, 2026

    @lexfrei
    ContributorAuthor

    Update on how often site 1 is reached in practice: three independent reviews of the same branch have now cited packages/extra/gateway/README.md:96 as evidence that a child tenant on an independent apex is a supported topology, and each concluded that the branch only costs such a child its port-80 redirect. The measured behaviour is that cozystack-gateway-hostname-policy denies the parent Gateway write outright, so the parent stops converging and nothing is served for that child on any port.

    I am not claiming the line causes the wrong conclusion. All three reviews stop their chain at the same earlier point, before the Gateway policy, and the line then corroborates what they already concluded. That is enough to make it worth fixing ahead of the other sites: whatever leads a reader there, the sentence confirms it.

  4. lexfrei commented on Aug 17, 2026

    @lexfrei
    ContributorAuthor

    Site 2 names packages/apps/tenant/values.yaml and values.schema.json. The same claim — that leaving gateway unset auto-enables the Gateway for a tenant whose apex is derived from the parent — has three more copies, and one of them ships into every cluster.

    The description is generated from the @param comment in values.yaml, so every copy carries it verbatim and one grep finds the whole set:

    grep -rn "auto-enables the Gateway" --include='*.yaml' --include='*.json' --include='*.md' --include='*.go' .

    On main that returns five hits, and nothing else in the tree matches:

    • packages/apps/tenant/values.yaml — the source, on the ## @param {bool} [gateway] line
    • packages/apps/tenant/values.schema.json — as the property's "description"
    • packages/apps/tenant/README.md — the parameter table row
    • packages/system/tenant-rd/cozyrds/tenant.yaml — inside the single-line openAPISchema blob, under "gateway"
    • api/apps/v1alpha1/tenant/types.go — the godoc on the Gateway bool field

    The last two are why this is worth adding rather than leaving implied. The tenant-rd copy is served through the aggregated API, so kubectl explain tenant.spec.gateway reads the wrong rule back to the user on a live cluster. The Go godoc is what regenerates it, and it is hand-written: a fix that corrects values.yaml and reruns the generators reaches the schema, the README and the RD blob, but leaves types.go alone, and the next regeneration puts the wrong text back. Both have to move in the same change.

    The grep above is the check for the fix, too — after it lands, that phrase should return nothing.

    Confirming the code half against the same tree: tenant.gatewayEffective at packages/apps/tenant/templates/_helpers.tpl:88-94 reads nothing but .Values.gateway:

    {{- define "tenant.gatewayEffective" -}}
    {{- if kindIs "invalid" .Values.gateway -}}
    false
    {{- else -}}
    {{- if .Values.gateway -}}true{{- else -}}false{{- end -}}
    {{- end -}}
    {{- end -}}
    

    No reference to .Values.host, to _cluster, or to the release namespace — there is no apex inspection in it at all, in either direction. The helper's own doc block at _helpers.tpl:17-55 already states the correct rule (:34: "Otherwise → false. The tenant inherits the parent's Gateway", and :42-48 that custom-apex tenants must opt in explicitly), so the canonical description the fix needs already exists a few lines above the code.

    That makes the drift symmetrical, which is worth recording alongside Site 3: the five generated copies claim auto-on for a derived apex, templates/namespace.yaml:46-49 claims auto-on for a custom non-derived host, and the helper does neither.

  5. added
    triage/acceptedIndicates an issue is ready to be actively worked on
    priority/important-longtermImportant over the long term, but may not be staffed and/or may need multiple releases to complete
    and removed
    triage/needs-triageIndicates an issue needs triage by a maintainer
    on Sep 23, 2026
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

    area/networkingIssues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn)kind/documentationCategorizes issue or PR as related to documentationpriority/important-longtermImportant over the long term, but may not be staffed and/or may need multiple releases to completetriage/acceptedIndicates an issue is ready to be actively worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions