Repository navigation
gateway: a wildcard hostname claim builds a listener name the CRD rejects, failing the whole reconcile #3787
Description
Activity
- addedkind/bugCategorizes issue or PR as related to a bugCategorizes issue or PR as related to a bugarea/networkingIssues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn)Issues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn)
on Aug 12, 2026 Good write-up. I can confirm the analysis against the code and fill in one detail you flagged as inferred.
The VAP does admit the wildcard
The
cozystack-route-hostname-policyCEL expression only checks suffix containment: for*.foo.example.comwith an apex offoo.example.com,"*.foo.example.com".endsWith(".foo.example.com")→ true. It never inspects whether the first label is*.collectHostnameClaimspasses wildcards straight throughEvery hostname from
route.Spec.Hostnamesis added to the output map with no filter on the leading label [1]. That map feedsdynHostnames, which is passed to bothreconcileGatewayandreconcilePerListenerCertificates[2]. There is no guard between admission and listener construction.perListenerNameproduces the invalid namehostnameFirstLabel("*.foo.example.com")returns"*"[3], soperListenerNamecomposes"https-*-<hex>"[4]. That string fails the Gateway CRD's listener name regex, and because all listeners are written in a singleGatewayobject, the apiserver rejects the entire write — not just the offending entry.The bug surface is HTTP-01 only
Both DNS-01 and existingSecret modes return
nilfromcollectHostnameClaimsimmediately [5], so wildcards from tenant routes never reachperListenerName.On the design question
Your three options map to concrete insertion points:
- Drop silently at collection time — skip any
hwherestrings.HasPrefix(string(h), "*.")in thecollectHostnameClaimsloop. Simplest patch; the route stays admitted but gets no listener or cert, so it sitsAccepted=Falsewith no message explaining why. - Reject at admission — add a CEL clause to the VAP rejecting
h.startsWith("*.")in tenant namespaces. Makes the failure visible to the route author at creation time and preserves the invariant that anything the VAP admits is safe to reconcile. - Map to a stable label — replace
*with a fixed string (e.g."wildcard") insidehostnameFirstLabel. Lowest disruption, but silently swallows the wildcard semantics and could issue an incorrect per-listener certificate.
Option 2 looks cleanest: the reconciler's contract becomes "anything in
dynHostnameshas a safe first label", and the test gap you identified (fake client not validatingSectionName) can be closed with a unit test asserting no*appears in any listener name produced byperListenerName.To reply, just mention @dosu.
Share context across your team and agents. Try Dosu.
- Drop silently at collection time — skip any
- addedtriage/needs-triageIndicates an issue needs triage by a maintainerIndicates an issue needs triage by a maintainer
on Aug 12, 2026 - added a commit that references this issue
on Sep 7, 2026 lexfrei commented
on Sep 17, 2026 ContributorAuthorMore actionsFixed on main by #3342 (2bf9ae4), in both places.
judgeHTTPRouteClaimnow refuses an HTTPRoute wildcard that wants the terminate listener under HTTP-01, and the route gets a status message.hostnameFirstLabelrewrites a leading*towildcard, so the listener name ishttps-wildcard-06a3e25dand the CRD pattern accepts it.The test gap is closed as well.
TestRenderGateway_ListenerNamesAreAdmissibleForEveryRouteHostnamechecks rendered names against the pattern of the shipped Gateway CRD, so the fake client is out of the picture. It passes on main at 8a6fe02 together withTestReconcile_WildcardHTTPRouteHostnameIsRefusedUnderHTTP01.Same caveat as in the report: the apiserver refusing
https-*-<hex>was inferred from the pattern, I never saw it on a live cluster.
A tenant route may claim a wildcard hostname, and the listener name the controller builds from it is rejected by the Gateway CRD schema. The reconcile then fails for the whole TenantGateway, not just for that route.
The chain, each link checked against
main:The route policy admits a wildcard from a tenant namespace.
"*.foo.example.com".endsWith("." + "foo.example.com")is true, so a route declaring*.<apex>passes admission.Nothing filters wildcards out of the claim path. Grepping
internal/controller/tenantgateway/reconciler.gofor wildcard handling returns only the lines that build one for the certificate; none that drop one coming in.hostnameFirstLabeltakes everything before the first dot:For
*.apexthat returns*, andperListenerNamecomposeshttps-+*+-+ hex, givinghttps-*-<hex>.The listener name pattern in the CRD that actually ships (
packages/system/gateway-api-crds/templates/crds-experimental.yaml, same for Gateway v1 and v1beta1) is:(taken from
spec.listeners.nameby walking the schema path, not by grepping the file. A grep lands on.groupfirst, and that one carries an extra^$|alternative because an empty group is legal while an empty listener name is not.)Checked against it directly:
So the Gateway write is refused, and because the controller renders all listeners in one object, the failure is not scoped to the offending route: certificate issuance for every other hostname on that gateway stops with it.
Two things are inferred rather than observed, and worth stating as such. The apiserver's refusal follows from the pattern rather than from a live cluster run. And unit tests will not catch it: the fake client used there does not validate
SectionNameagainst the schema.This is not introduced by any open PR. Neither the missing wildcard filter nor
hostnameFirstLabeldiffers betweenmainand the branches currently in flight.The fix is a filter on the claim path. Whether a wildcard claim should be dropped silently, rejected at admission with a message, or mapped to a stable label is a design call, not an obvious one.