Repository navigation
gateway: two tenants with the Gateway integration overwrite each other's namespace attach label #4208
Description
Describe the bug
Two tenants that both enable the Gateway integration fight over the namespace.cozystack.io/gateway label on the shared namespaces, and the platform routes follow whichever wrote last.
ensureNamespaceLabels (internal/controller/tenantgateway/reconciler.go) builds its expected set from the TenantGateway's own namespace plus every entry of spec.attachedNamespaces, and calls patchNamespaceGatewayLabel for each. That function reads the namespace, sets labels["namespace.cozystack.io/gateway"] = ownerNamespace and annotations[...managed-by] = ownerNamespace, and patches. There is no check for an existing value, so it takes ownership of a namespace another TenantGateway already labelled.
The garbage-collection pass in the same function looks like the guard against exactly this and is not one. It skips a namespace whose managed-by annotation names a different TenantGateway, with a comment saying so, but that check sits on the deletion path alone. The write path above it overwrites the annotation together with the label, so the annotation never stays foreign long enough for the guard to fire. Reading the function quickly gives the impression the collision is handled.
spec.attachedNamespaces is not per tenant. The gateway chart renders it from _cluster.gateway-attached-namespaces (packages/extra/gateway/templates/tenantgateway.yaml), which is a cluster-wide list holding default and the cozy-* namespaces, and every tenant with the Gateway integration enabled gets the same list. So each reconcile of either TenantGateway flips those namespaces to itself.
The label is what the HTTPS-terminate and TLS-passthrough listeners select on through allowedRoutes.namespaces.from: Selector, so the platform routes in default and cozy-* attach to whichever Gateway holds the label at that moment.
The existing admission layers do not cover this. They reject tenant-* entries in the platform's gateway.attachedNamespaces, which is a different misconfiguration; the namespaces at issue here are legitimately on the list.
Environment
- Cozystack main, 2026-09-10
- Two tenants with the Gateway integration enabled
To Reproduce
- Enable the Gateway integration on two tenants, so two TenantGateways render with the same
attachedNamespaces - Watch
namespace.cozystack.io/gatewayondefaultand on acozy-*namespace across reconciles
Expected behaviour
A namespace attaches to one Gateway, and a second TenantGateway claiming the same namespace either loses deterministically or is reported rather than silently taking it.
Actual behaviour
The label and its managed-by annotation flip between the two owners, and the routes in those namespaces follow.
Additional context
Found while reviewing #3342, pre-existing and untouched by that branch. What needs deciding before it can be fixed is which of the two claimants should win: the tenant that publishing.ingressName selects, the first writer, or neither, with the conflict surfaced on the TenantGateway status instead.
Checklist
- I have checked the documentation
- I have searched for similar issues
- I have included all required information
- I have provided clear steps to reproduce
- I have included relevant logs
Activity
lexfrei commentedon Sep 23, 2026
Duplicate of #3869. It describes the same label takeover in
patchNamespaceGatewayLabel, and the question of which tenant should win is already there.