[platform, system, apps] Add Gateway API HTTPRoute/TLSRoute support for all components#2213
[platform, system, apps] Add Gateway API HTTPRoute/TLSRoute support for all components#2213sircthulhu wants to merge 19 commits intomainfrom
Conversation
📝 WalkthroughWalkthroughThis pull request introduces comprehensive Gateway API integration across the platform. It adds Gateway API Custom Resource Definitions (CRDs) to the platform, enables conditional Gateway API support in the tenant application, introduces a new cozystack-gateway system component, and adds HTTPRoute and TLSRoute resources for multiple system components and applications to expose services via Gateway API instead of or alongside traditional Ingress. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the platform's networking capabilities by integrating the Kubernetes Gateway API. It provides a modern and flexible alternative to traditional Ingress resources, facilitating a smoother migration path to Cilium Gateway API. The changes introduce a structured approach for both system-wide and tenant-specific traffic management, allowing for fine-grained control over routing and load balancing while ensuring backward compatibility with existing Ingress configurations. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for the Gateway API across system and tenant components, providing a clear migration path from ingress-nginx. The changes are well-structured, with a central gateway for system services and per-tenant gateways for tenant services, which is a solid design. The use of HTTPRoute and TLSRoute is appropriate for the different services.
I have a few suggestions for improvement:
- My main concern is the use of the deprecated
gateway.networking.k8s.io/v1alpha2API version forTLSRouteresources. I strongly recommend updating this to a stable version likev1orv1beta1for future compatibility. - I've also identified a couple of areas with repetitive code in the Helm templates where using helper templates or loops could improve maintainability.
Overall, this is a great enhancement. Addressing these points will make the implementation more robust and easier to maintain.
| namespaces: | ||
| from: Same | ||
| --- | ||
| apiVersion: gateway.networking.k8s.io/v1alpha2 |
There was a problem hiding this comment.
The apiVersion for TLSRoute is set to gateway.networking.k8s.io/v1alpha2, which is from an old experimental channel of the Gateway API and has been deprecated. It's recommended to use a more recent, stable version. Given that HTTPRoute resources in this PR are using v1, for consistency and future compatibility, you should update TLSRoute to gateway.networking.k8s.io/v1 if your cluster's Gateway API support is at v1.0.0 or newer. If not, v1beta1 would be a better choice than v1alpha2. This applies to all new TLSRoute resources in this pull request (cozystack-api/templates/api-tlsroute.yaml, kubevirt-cdi/templates/cdi-uploadproxy-tlsroute.yaml, and kubevirt/templates/vm-exportproxy-tlsroute.yaml).
apiVersion: gateway.networking.k8s.io/v1| {{- $gateway := $parentNamespace.gateway | default "" }} | ||
| {{- if .Values.gateway }} | ||
| {{- $gateway = $tenantName }} | ||
| {{- end }} |
There was a problem hiding this comment.
This logic for determining the service reference is repeated for etcd, ingress, monitoring, and seaweedfs. To improve maintainability and reduce code duplication, consider creating a shared helper template. For example, you could define a helper in a common _helpers.tpl file:
{{- define "tenant.serviceRef" -}}
{{- $serviceName := .serviceName -}}
{{- $ref := index .parentNamespace $serviceName | default "" -}}
{{- if index .values $serviceName -}}
{{- $ref = .tenantName -}}
{{- end -}}
{{- $ref -}}
{{- end -}}
And then use it like this for each service:
{{- $gateway := include "tenant.serviceRef" (dict "serviceName" "gateway" "tenantName" $tenantName "parentNamespace" $parentNamespace "values" .Values) -}}
This would make the template cleaner and easier to manage.
| {{- if has "dashboard" $exposeServices }} | ||
| - name: https-dashboard | ||
| protocol: HTTPS | ||
| port: 443 | ||
| hostname: {{ index $hostnames "dashboard" | default (printf "dashboard.%s" $rootHost) | quote }} | ||
| tls: | ||
| mode: Terminate | ||
| certificateRefs: | ||
| - name: gateway-dashboard-tls | ||
| allowedRoutes: | ||
| namespaces: | ||
| from: All | ||
| {{- end }} | ||
| {{- if eq $oidcEnabled "true" }} | ||
| - name: https-keycloak | ||
| protocol: HTTPS | ||
| port: 443 | ||
| hostname: {{ index $hostnames "keycloak" | default (printf "keycloak.%s" $rootHost) | quote }} | ||
| tls: | ||
| mode: Terminate | ||
| certificateRefs: | ||
| - name: gateway-keycloak-tls | ||
| allowedRoutes: | ||
| namespaces: | ||
| from: All | ||
| {{- end }} | ||
| {{- if has "api" $exposeServices }} | ||
| - name: tls-passthrough-api | ||
| protocol: TLS | ||
| port: 443 | ||
| hostname: {{ index $hostnames "api" | default (printf "api.%s" $rootHost) | quote }} | ||
| tls: | ||
| mode: Passthrough | ||
| allowedRoutes: | ||
| namespaces: | ||
| from: All | ||
| {{- end }} | ||
| {{- if has "vm-exportproxy" $exposeServices }} | ||
| - name: tls-passthrough-vm-exportproxy | ||
| protocol: TLS | ||
| port: 443 | ||
| hostname: {{ index $hostnames "vm-exportproxy" | default (printf "vm-exportproxy.%s" $rootHost) | quote }} | ||
| tls: | ||
| mode: Passthrough | ||
| allowedRoutes: | ||
| namespaces: | ||
| from: All | ||
| {{- end }} | ||
| {{- if has "cdi-uploadproxy" $exposeServices }} | ||
| - name: tls-passthrough-cdi-uploadproxy | ||
| protocol: TLS | ||
| port: 443 | ||
| hostname: {{ index $hostnames "cdi-uploadproxy" | default (printf "cdi-uploadproxy.%s" $rootHost) | quote }} | ||
| tls: | ||
| mode: Passthrough | ||
| allowedRoutes: | ||
| namespaces: | ||
| from: All | ||
| {{- end }} |
There was a problem hiding this comment.
The listener definitions for different services are very repetitive. To improve maintainability, you could refactor this by defining a list or map of services and their properties, and then looping through them to generate the listener configurations. This would reduce code duplication and make it easier to add or modify services in the future.
3b366e2 to
8c3ad83
Compare
- Upgrade Gateway API CRDs from v1.2.0 to v1.5.0 (experimental channel) - Add gateway-api-crds as a component in all networking variants, with Cilium depending on it to ensure CRDs are available first - Add gateway.gatewayAPI platform config (enables Cilium gatewayAPI.enabled=true), can be used alongside gateway.ingress - Enable Gateway API support in cert-manager Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
When gateway.gatewayAPI is true, pass gateway-httproute source to the system external-dns package via components values override. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
…ayAPI Previously enableGatewayAPI was hardcoded to true in cert-manager values, causing startup failures in isp-hosted variant where Gateway API CRDs are not installed. Now passed via component override only when gateway.gatewayAPI is enabled. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Add cozystack-gateway system package that creates a shared Gateway resource with per-hostname HTTPS listeners and TLS passthrough listeners for system services. Listeners are dynamically generated from expose-services config. Platform changes: - Add gateway-api flag to _cluster config for component awareness - Add gateway.name/namespace to platform values for parentRef config - Add publishing.hostnames map for per-service hostname overrides - Wire cozystack-gateway package into system bundle (conditional) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Add Gateway API route templates alongside existing Ingress templates
for all system-level services. Routes are conditional on
_cluster.gateway-api being enabled.
HTTPRoute (TLS terminate via central Gateway):
- dashboard: routes to incloud-web-gatekeeper:8000
- keycloak: routes to keycloak-http:80
TLSRoute (TLS passthrough via central Gateway):
- cozystack-api: passthrough to kubernetes:443
- kubevirt vm-exportproxy: passthrough to vm-exportproxy:443
- kubevirt-cdi uploadproxy: passthrough to cdi-uploadproxy:443
All routes reference the central Gateway via parentRefs with
sectionName for listener matching. Hostname resolution follows
convention ({service}.{root-host}) with override support via
_cluster.hostnames map.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Add gateway field to tenant namespace configuration, following the same inheritance pattern as ingress: - tenant-root defaults to gateway: tenant-root - Sub-tenants with gateway: true get their own LB - Sub-tenants without gateway inherit from parent Add infrastructure.labels to central Gateway for shared LB with tenant-root services (cozystack.io/gateway: tenant-root). Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Add per-component Gateway + HTTPRoute/TLSRoute for tenant-level
services. Each component creates its own Gateway with specific
hostname HTTPS listener and cert-manager annotation for automatic
certificate provisioning via HTTP01 or DNS01.
All Gateways within a tenant share a single LoadBalancer IP through
infrastructure.labels (cozystack.io/gateway: {tenant-name}), matching
the current ingress-nginx model of one LB per tenant.
HTTPRoute (per-component Gateway with TLS terminate):
- monitoring/grafana: routes to grafana-service:3000
- monitoring/alerta: routes to alerta:80
- bucket: routes to {bucketName}-ui:8080
- apps/harbor: routes to {release}:80
- extra/bootbox: routes to bootbox:8080
TLSRoute (per-component Gateway with TLS passthrough):
- extra/seaweedfs: passthrough to filer-external:18888
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
- Add HTTP-to-HTTPS redirect HTTPRoute for all Gateways (central and per-tenant) to ensure plain HTTP requests are properly redirected - Add oidc-enabled check to keycloak HTTPRoute condition to match the Gateway listener condition - Use gateway-name from cluster config instead of hardcoded value in central Gateway template - Remove redundant namespace from TLSRoute metadata where it matches the HelmRelease install namespace (kubevirt, kubevirt-cdi) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
v1alpha2 is deprecated in Gateway API v1.5.0 with a removal warning. Update all TLSRoute resources to use gateway.networking.k8s.io/v1. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Replace hardcoded `gatewayClassName: cilium` in all 7 Gateway templates with a configurable value from _cluster.gateway-class-name. Default remains "cilium" for backward compatibility. This enables custom GatewayClass resources with different parameters (e.g. NodePort via CiliumGatewayClassConfig) and maintains vendor-neutrality for non-Cilium Gateway API controllers. Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Add cozy-lib.gateway.httpRoute and cozy-lib.gateway.tlsRoute helpers that encapsulate the full Gateway + HTTPRoute/TLSRoute pattern with HTTP-to-HTTPS redirect. This eliminates ~350 lines of duplicated boilerplate across 6 tenant-level components. Add cozy-lib as dependency to bucket, monitoring, harbor, bootbox, seaweedfs, and cozystack-gateway charts. Update PackageSource definitions for cozystack-gateway and bucket-system to include cozy-lib library. Add ns-gateway helper to _cozyconfig.tpl for consistency with existing ns-ingress, ns-etcd, etc. Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
When gateway.gatewayAPI is enabled in platform config: - Set Cilium envoy.streamIdleTimeoutDurationSeconds to 86400 (24h) to support long-lived WebSocket (dashboard/noVNC) and large file uploads (bucket, harbor, seaweedfs). Cilium does not support per-route timeouts (GEP-1742), so a global override is needed. - Enable gatewayAPI.enableAlpn for HTTP/2 and gRPC support through Gateway API listeners (required by seaweedfs filer gRPC backend). - Disable Keycloak sticky session route attachment (KC_SPI_STICKY_SESSION_ENCODER_INFINISPAN_SHOULD_ATTACH_ROUTE=false) so Keycloak works without cookie-based session affinity. Infinispan distributed cache handles session replication across nodes. This removes the dependency on nginx cookie affinity annotations. Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Match the existing symlink convention used by other charts (e.g. keycloak, tenant) where the cozy-lib symlink target includes a trailing slash: ../../../library/cozy-lib/ Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Revert cozy-lib refactoring to debug platform upgrade issue. Gateway templates restored to inline form. cozy-lib dependency removed from bucket, cozystack-gateway, monitoring, harbor, bootbox, seaweedfs charts. Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
…kage
cozystack.platform.package renders `components: {}` when given an
empty dict, which causes the operator post-render to fail with YAML
unmarshal errors. Use package.default (no components) when the dict
is empty, package (with components) only when non-empty.
Fixes cert-manager and external-dns conditional components blocks
that pass an empty dict when gateway.gatewayAPI is false.
Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
…ingress disabled When ingress is disabled and Gateway API is used, create a CiliumLoadBalancerIPPool from publishing.externalIPs so that Cilium assigns those IPs to the Gateway's LoadBalancer Service. Priority logic: ingress takes externalIPs when enabled; Gateway gets them only when ingress is off. Propagate gateway.ingress as ingress-enabled in _cluster values so the gateway chart can make this decision. Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Cover central Gateway, system HTTPRoutes/TLSRoutes, HTTP-to-HTTPS redirect, TLS passthrough to API server, per-tenant Grafana Gateway, and ingress coexistence verification. Assisted-By: Claude AI Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
17f38a2 to
d0cc82b
Compare
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (1)
packages/system/cozystack-gateway/Chart.yaml (1)
1-3: Consider adding a description field.While not required, adding a
descriptionfield would improve discoverability and help users understand the chart's purpose.📝 Suggested enhancement
apiVersion: v2 name: cozy-cozystack-gateway +description: Central Gateway API Gateway for system-level HTTP and TLS routing version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/system/cozystack-gateway/Chart.yaml` around lines 1 - 3, Add a human-readable description field to Chart.yaml to improve discoverability and explain the chart's purpose; in the Chart.yaml near the existing apiVersion/name/version entries, add a top-level description: "<brief one-line summary of cozy-cozystack-gateway's purpose>" (use clear, concise wording) so consumers see what cozy-cozystack-gateway does when viewing the chart metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hack/e2e-install-cozystack.bats`:
- Around line 259-267: The curl probes use the IP in the URL so TLS SNI is sent
as the IP (causing hostname-based routing/cert selection to fail); update the
curl invocations that use "https://${gateway_ip}" (the blocks that set http_code
and check Dashboard and Kubernetes API responses) to instead request the
hostname in the URL (e.g., https://dashboard.example.org and
https://api.example.org) and add curl's --resolve option to map each hostname to
${gateway_ip} so the hostname is preserved for SNI while resolving to the
gateway IP; modify both the Dashboard probe (where http_code is set for
dashboard.example.org) and the Kubernetes API probe (and the similar block
around the second occurrence noted) to use --resolve with the appropriate
hostname and ${gateway_ip}.
In `@packages/core/platform/templates/bundles/system.yaml`:
- Around line 173-186: Update the v1.0.0 migration/upgrade notes to document
that external-dns is now opt-in by requiring cozystack.external-dns to be added
to bundles.enabledPackages (which defaults to an empty list), and add a short
operator guidance block instructing anyone upgrading from v0.x to check if they
depend on external-dns and, if so, explicitly add "cozystack.external-dns" to
bundles.enabledPackages in their Platform values before upgrading.
In `@packages/extra/bootbox/templates/matchbox/httproute.yaml`:
- Around line 74-76: The HTTPRoute backendRefs currently references name:
bootbox with port: 8080 which does not match the Service bootbox exposed port
80; update the HTTPRoute backendRefs entry (backendRefs / name: bootbox / port)
in matchbox/httproute.yaml to use port: 80 so it matches the Service definition
in packages/extra/bootbox/templates/matchbox/service.yaml and ensures proper
route resolution.
In `@packages/extra/seaweedfs/templates/tlsroute.yaml`:
- Line 7: The top render condition currently requires (.Values.filer.grpcHost)
which prevents the template from rendering when grpcHost is empty but a fallback
host exists; update the initial if-condition (the line with and (eq $gatewayAPI
"true") (ne $gateway "") (not (eq .Values.topology "Client"))
(.Values.filer.grpcHost)) to instead allow either .Values.filer.grpcHost or the
fallback host (e.g., check for (.Values.filer.grpcHost) OR (. _namespace.host) /
the same host variable used in the fallback at the usages around the
filer.<host> logic on lines referencing the fallback) so the block renders when
a fallback host is available.
- Line 1: Normalize the gateway-api check by converting the value to a string
before comparing and ensure the grpcHost fallback is considered in the
conditional: change the template to set $gatewayAPI := printf "%v" (index
.Values._cluster "gateway-api" | default false) and then compute a $grpcHost
variable using default .Values.filer.grpcHost with the same fallback expression
used later in the template (the explicit fallback hostnames present where
grpcHost is used), and use $grpcHost in the if-condition that currently compares
$gatewayAPI to "true" so boolean/unquoted values and the fallback host are both
respected.
In `@packages/system/cozystack-gateway/templates/gateway.yaml`:
- Around line 111-113: The template loop that emits "cidr: {{ . }}/32" from "{{-
range splitList \",\" $exposeExternalIPs }}" can produce "cidr: /32" for empty
items; update the range to skip empty/whitespace entries by checking the trimmed
value before emitting. Specifically, inside the range over splitList ","
$exposeExternalIPs, wrap the cidr block in a conditional like "if (ne (trim .)
\"\")" (or equivalent trim+not-empty check) so only non-empty values produce
"cidr: {{ . }}/32".
In `@packages/system/gateway-api-crds/Makefile`:
- Line 9: The Makefile now kustomizes Gateway API CRDs to v1.5.0 (the kubectl
kustomize
"github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v1.5.0"
line) but v1.5.0 has breaking changes (TLSRoute and ListenerSet moved from
Experimental->Standard, new validating admission policy
safe-upgrades.gateway.networking.k8s.io, and TLSRoute CEL requiring Kubernetes
>=1.31); before changing the ref, either keep the existing v1.2.0 CRD ref or
update other CRD targets so Standard and Experimental CRDs are applied together,
scan the cluster for resources using TLSRoute v1alpha2 and migrate them to the
new v1 types (or plan removal), ensure target clusters run Kubernetes >=1.31 if
using TLSRoute CEL, and apply CRDs with kubectl apply --server-side=true to
avoid VAP conflicts; also remove or adjust generation of
templates/crds-experimental.yaml if you intend to fully migrate to the Standard
channel so you don't install mixed channels separately which will be blocked by
the new VAP.
In `@packages/system/keycloak/templates/httproute.yaml`:
- Around line 27-29: The HTTPRoute's backendRefs currently points to port 80
which does not exist on the keycloak-http Service (service.yaml exposes port
8080); update the HTTPRoute backendRefs entry for name: keycloak-http to use
port: 8080 (replace the existing port: 80) so the route matches the Service's
exposed port and routing will succeed.
---
Nitpick comments:
In `@packages/system/cozystack-gateway/Chart.yaml`:
- Around line 1-3: Add a human-readable description field to Chart.yaml to
improve discoverability and explain the chart's purpose; in the Chart.yaml near
the existing apiVersion/name/version entries, add a top-level description:
"<brief one-line summary of cozy-cozystack-gateway's purpose>" (use clear,
concise wording) so consumers see what cozy-cozystack-gateway does when viewing
the chart metadata.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d3b12f3a-79b0-42fa-9d84-ffce62178120
📒 Files selected for processing (30)
hack/e2e-install-cozystack.batspackages/apps/harbor/templates/httproute.yamlpackages/apps/tenant/README.mdpackages/apps/tenant/templates/namespace.yamlpackages/apps/tenant/values.schema.jsonpackages/apps/tenant/values.yamlpackages/core/platform/sources/cozystack-gateway.yamlpackages/core/platform/sources/networking.yamlpackages/core/platform/templates/apps.yamlpackages/core/platform/templates/bundles/system.yamlpackages/core/platform/values.yamlpackages/extra/bootbox/templates/matchbox/httproute.yamlpackages/extra/seaweedfs/templates/tlsroute.yamlpackages/system/bucket/templates/httproute.yamlpackages/system/cert-manager/values.yamlpackages/system/cozystack-api/templates/api-tlsroute.yamlpackages/system/cozystack-basics/templates/cozystack-values-secret.yamlpackages/system/cozystack-gateway/Chart.yamlpackages/system/cozystack-gateway/templates/gateway.yamlpackages/system/cozystack-gateway/values.yamlpackages/system/dashboard/templates/httproute.yamlpackages/system/gateway-api-crds/Makefilepackages/system/gateway-api-crds/templates/crds-experimental.yamlpackages/system/keycloak/templates/httproute.yamlpackages/system/keycloak/templates/sts.yamlpackages/system/kubevirt-cdi/templates/cdi-uploadproxy-tlsroute.yamlpackages/system/kubevirt/templates/vm-exportproxy-tlsroute.yamlpackages/system/monitoring/templates/alerta/httproute.yamlpackages/system/monitoring/templates/grafana/httproute.yamlpackages/system/tenant-rd/cozyrds/tenant.yaml
| http_code=$(curl -sS -k "https://${gateway_ip}" -H 'Host: dashboard.example.org' --max-time 30 -o /dev/null -w '%{http_code}') | ||
| if [ "$http_code" != "200" ] && [ "$http_code" != "302" ] && [ "$http_code" != "303" ]; then | ||
| echo "Failed to access Dashboard via Gateway, got HTTP ${http_code}" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Kubernetes API via TLS passthrough (401/403 expected without credentials) | ||
| http_code=$(curl -sS -k "https://${gateway_ip}" -H 'Host: api.example.org' --max-time 30 -o /dev/null -w '%{http_code}') | ||
| if [ "$http_code" != "401" ] && [ "$http_code" != "403" ]; then |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does curl set TLS SNI based on the URL hostname, and does -H 'Host: ...' change SNI?
💡 Result:
Yes, curl sets the TLS SNI based on the hostname from the URL. No, the -H 'Host: ...' header does not change the SNI; it only affects the HTTP Host header sent after the TLS handshake.
Citations:
- 1: https://everything.curl.dev/usingcurl/connections/name.html?highlight=resolve
- 2: https://everything.curl.dev/usingcurl/connections/name.html
- 3: https://sathyasays.com/2023/07/18/curl-tls-sni-hostname-spoof/
- 4: https://stackoverflow.com/questions/50279275/curl-how-to-specify-target-hostname-for-https-request/50279590
- 5: https://superuser.com/questions/793600/curl-and-sni-enabled-server
Use SNI-correct HTTPS probes in Gateway tests.
The current pattern curl https://<ip> -H 'Host: ...' sends TLS SNI as the IP address, not the target hostname. Gateway API TLS listeners use SNI for hostname-based routing and certificate selection; mismatched SNI causes these checks to fail or produce false negatives even when routes are healthy.
Use --resolve to map the hostname to the IP while preserving the hostname in the URL, ensuring correct SNI:
🔧 Suggested fix
- http_code=$(curl -sS -k "https://${gateway_ip}" -H 'Host: dashboard.example.org' --max-time 30 -o /dev/null -w '%{http_code}')
+ http_code=$(curl -sS -k --resolve "dashboard.example.org:443:${gateway_ip}" \
+ "https://dashboard.example.org" --max-time 30 -o /dev/null -w '%{http_code}')
- http_code=$(curl -sS -k "https://${gateway_ip}" -H 'Host: api.example.org' --max-time 30 -o /dev/null -w '%{http_code}')
+ http_code=$(curl -sS -k --resolve "api.example.org:443:${gateway_ip}" \
+ "https://api.example.org" --max-time 30 -o /dev/null -w '%{http_code}')
- if ! curl -sS -k "https://${grafana_gw_ip}" -H 'Host: grafana.example.org' --max-time 30 | grep -q Found; then
+ if ! curl -sS -k --resolve "grafana.example.org:443:${grafana_gw_ip}" \
+ "https://grafana.example.org" --max-time 30 | grep -q Found; then
- if ! curl -sS -k "https://${ingress_ip}" -H 'Host: grafana.example.org' --max-time 30 | grep -q Found; then
+ if ! curl -sS -k --resolve "grafana.example.org:443:${ingress_ip}" \
+ "https://grafana.example.org" --max-time 30 | grep -q Found; thenAlso applies to: 298-307
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@hack/e2e-install-cozystack.bats` around lines 259 - 267, The curl probes use
the IP in the URL so TLS SNI is sent as the IP (causing hostname-based
routing/cert selection to fail); update the curl invocations that use
"https://${gateway_ip}" (the blocks that set http_code and check Dashboard and
Kubernetes API responses) to instead request the hostname in the URL (e.g.,
https://dashboard.example.org and https://api.example.org) and add curl's
--resolve option to map each hostname to ${gateway_ip} so the hostname is
preserved for SNI while resolving to the gateway IP; modify both the Dashboard
probe (where http_code is set for dashboard.example.org) and the Kubernetes API
probe (and the similar block around the second occurrence noted) to use
--resolve with the appropriate hostname and ${gateway_ip}.
| {{- $externalDnsComponents := dict -}} | ||
| {{- if .Values.gateway.gatewayAPI -}} | ||
| {{- $externalDnsValues := dict "external-dns" (dict "sources" (list "service" "ingress" "gateway-httproute")) -}} | ||
| {{- $_ := set $externalDnsComponents "external-dns" (dict "values" $externalDnsValues) -}} | ||
| {{- end -}} | ||
| {{- $disabled := default (list) .Values.bundles.disabledPackages -}} | ||
| {{- $enabled := default (list) .Values.bundles.enabledPackages -}} | ||
| {{- if and (has "cozystack.external-dns" $enabled) (not (has "cozystack.external-dns" $disabled)) -}} | ||
| {{- if gt (len $externalDnsComponents) 0 }} | ||
| {{include "cozystack.platform.package" (list "cozystack.external-dns" "default" $ $externalDnsComponents) }} | ||
| {{- else }} | ||
| {{include "cozystack.platform.package.default" (list "cozystack.external-dns" $) }} | ||
| {{- end }} | ||
| {{- end }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check if external-dns enablement change is documented in release notes or migration docs
# Search for documentation about this change
rg -i "external-dns" -g "*.md" -g "CHANGELOG*" -g "RELEASE*" -C 3Repository: cozystack/cozystack
Length of output: 50377
🏁 Script executed:
# Check the platform values.yaml file around lines 11-22
head -30 packages/core/platform/values.yaml | cat -nRepository: cozystack/cozystack
Length of output: 1068
🏁 Script executed:
# Search for migration documentation at repo root
find . -maxdepth 2 -type f \( -name "*MIGRATION*" -o -name "*CHANGELOG*" -o -name "*RELEASE*" \) | head -20Repository: cozystack/cozystack
Length of output: 45
🏁 Script executed:
# Check if there's any documentation about the bundles.enabledPackages change
rg "enabledPackages" -A 2 -B 2 --max-count 20Repository: cozystack/cozystack
Length of output: 3756
🏁 Script executed:
# Search for CHANGELOG, MIGRATION, or release documentation at root level
find . -maxdepth 1 -type f \( -iname "*changelog*" -o -iname "*migration*" -o -iname "*release*" -o -iname "*NEWS*" \)Repository: cozystack/cozystack
Length of output: 45
🏁 Script executed:
# Check the migration script to understand the enabledPackages context
cat hack/migrate-to-version-1.0.sh | head -100Repository: cozystack/cozystack
Length of output: 4438
🏁 Script executed:
# Look for any documentation about bundles and enabledPackages in the templates
rg "Optional System Packages|enabledPackages" packages/core/platform/templates/bundles/system.yaml -B 5 -A 5Repository: cozystack/cozystack
Length of output: 2147
🏁 Script executed:
# Check if there's a README in packages/core/platform explaining the bundles
head -100 packages/core/platform/README.md 2>/dev/null || echo "No README found"Repository: cozystack/cozystack
Length of output: 78
🏁 Script executed:
# Search for migration documentation in the repository
find . -type f \( -name "*MIGRATION*" -o -name "*UPGRADE*" -o -name "*v1.0*" -o -name "*release*" \) | grep -iE "(md|txt|rst)" | head -20Repository: cozystack/cozystack
Length of output: 526
🏁 Script executed:
# Check if there's documentation about the v1.0 migration or upgrade path
rg -i "migration|upgrade|breaking" --type md | head -30Repository: cozystack/cozystack
Length of output: 5099
🏁 Script executed:
# Look for any documentation that mentions external-dns being optional or breaking change
rg "external-dns" --type md -B 3 -A 3 | head -50Repository: cozystack/cozystack
Length of output: 5408
🏁 Script executed:
# Check what the template_helpers says about optional packages
rg "cozystack.platform.package.optional" packages/core/platform/templates/_helpers.tpl -B 5 -A 10Repository: cozystack/cozystack
Length of output: 1333
🏁 Script executed:
# Check the v1.0.0 changelog to see if external-dns being optional is documented
cat docs/changelogs/v1.0.0.md | head -200Repository: cozystack/cozystack
Length of output: 29028
🏁 Script executed:
# Search for external-dns in all changelog files
rg "external-dns" docs/changelogs/ -B 2 -A 2Repository: cozystack/cozystack
Length of output: 45
🏁 Script executed:
# Check if there's a breaking changes section in the current or recent changelogs
rg -i "breaking|breaking change" docs/changelogs/ -B 2 -A 2Repository: cozystack/cozystack
Length of output: 16771
Document external-dns enablement requirement in migration guide.
External-dns is now optional and only deployed when explicitly added to bundles.enabledPackages. Since enabledPackages defaults to an empty list (per packages/core/platform/values.yaml:22), existing deployments upgrading from v0.x will lose external-dns unless they explicitly enable it. This behavioral change is not documented in the v1.0.0 "Breaking Changes & Upgrade Notes" section despite being a breaking change for existing clusters.
Add guidance to the migration notes instructing operators upgrading from v0.x to check if external-dns is needed and explicitly add cozystack.external-dns to bundles.enabledPackages in their Platform values if they rely on it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/core/platform/templates/bundles/system.yaml` around lines 173 - 186,
Update the v1.0.0 migration/upgrade notes to document that external-dns is now
opt-in by requiring cozystack.external-dns to be added to
bundles.enabledPackages (which defaults to an empty list), and add a short
operator guidance block instructing anyone upgrading from v0.x to check if they
depend on external-dns and, if so, explicitly add "cozystack.external-dns" to
bundles.enabledPackages in their Platform values before upgrading.
| backendRefs: | ||
| - name: bootbox | ||
| port: 8080 |
There was a problem hiding this comment.
Fix backendRef port to match the Service’s exposed port.
Line 76 uses port: 8080, but packages/extra/bootbox/templates/matchbox/service.yaml exposes Service bootbox on port: 80 (Line 10). HTTPRoute.backendRefs.port must match the Service port, so this can cause route resolution/routing failure.
🔧 Proposed fix
- name: bootbox
- port: 8080
+ port: 80📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| backendRefs: | |
| - name: bootbox | |
| port: 8080 | |
| backendRefs: | |
| - name: bootbox | |
| port: 80 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/extra/bootbox/templates/matchbox/httproute.yaml` around lines 74 -
76, The HTTPRoute backendRefs currently references name: bootbox with port: 8080
which does not match the Service bootbox exposed port 80; update the HTTPRoute
backendRefs entry (backendRefs / name: bootbox / port) in
matchbox/httproute.yaml to use port: 80 so it matches the Service definition in
packages/extra/bootbox/templates/matchbox/service.yaml and ensures proper route
resolution.
| @@ -0,0 +1,43 @@ | |||
| {{- $gatewayAPI := (index .Values._cluster "gateway-api") | default "false" }} | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find gateway-api definitions and example values =="
rg -n -C2 'gateway-api' -g '*.yaml' -g '*.yml' -g '*.json'
echo
echo "== Find how _cluster.gateway-api is consumed in templates =="
rg -n -C2 '_cluster|gateway-api|eq \$gatewayAPI "true"' -g '*.yaml' -g '*.tpl'Repository: cozystack/cozystack
Length of output: 50376
🏁 Script executed:
cat -n packages/extra/seaweedfs/templates/tlsroute.yamlRepository: cozystack/cozystack
Length of output: 1582
🏁 Script executed:
# Find values files where gateway-api is configured
find . -name "values*.yaml" -o -name "values*.yml" | head -30 | xargs grep -l "gateway-api" 2>/dev/null | head -10
# Then examine how the value is set (boolean vs string)
echo "=== Checking how gateway-api is provided in values ==="
find . -name "values*.yaml" -o -name "values*.yml" | xargs grep -A2 -B2 "gateway-api:" 2>/dev/null | head -50Repository: cozystack/cozystack
Length of output: 117
🏁 Script executed:
# Search for where gateway.gatewayAPI is defined
echo "=== Looking for gateway.gatewayAPI definition ==="
rg -n "gateway\.gatewayAPI|gatewayAPI:" -A2 -B2 | head -60
# Also search for how _cluster values are passed/injected
echo ""
echo "=== Looking for _cluster value injection ==="
rg -n "\"_cluster\"|_cluster:" -l | head -10
# Check the platform values to understand gateway configuration
echo ""
echo "=== Platform gateway configuration ==="
find . -path "*/platform/*" -name "values*.yaml" | xargs cat 2>/dev/null | grep -A 10 "gateway:" | head -30Repository: cozystack/cozystack
Length of output: 5718
🏁 Script executed:
# Check if other templates have similar grpcHost gates
echo "=== Checking for grpcHost usage in other templates ==="
rg -n "grpcHost" -B2 -A2
# Also check other TLSRoute files to compare their gate conditions
echo ""
echo "=== Comparing gate conditions in similar TLSRoute templates ==="
fd "tlsroute.yaml|httproute.yaml" -x head -10 {} +Repository: cozystack/cozystack
Length of output: 51185
🏁 Script executed:
# Check if seaweedfs ingress.yaml has the same grpcHost gate
echo "=== Checking seaweedfs ingress.yaml for grpcHost gate ==="
sed -n '1,20p' packages/extra/seaweedfs/templates/ingress.yaml
echo ""
echo "=== Checking if other packages with external endpoints use similar gates ==="
rg "grpcHost" packages/extra/seaweedfs/templates/Repository: cozystack/cozystack
Length of output: 1998
Normalize gateway-api type before comparison and make the grpcHost fallback actually reachable.
Line 7 compares $gatewayAPI to the string "true". If _cluster.gateway-api is ever passed as a boolean (not quoted), this comparison silently fails and resources don't render. Additionally, the same line requires (.Values.filer.grpcHost) to be truthy, but lines 22 and 38 provide a fallback hostname—making the gate inconsistent with the template's intent.
🔧 Proposed fix
-{{- $gatewayAPI := (index .Values._cluster "gateway-api") | default "false" }}
+{{- $gatewayAPI := (index .Values._cluster "gateway-api") | default false | toString | lower }}
{{- $gateway := .Values._namespace.gateway | default "" }}
{{- $host := .Values._namespace.host }}
{{- $gatewayClassName := (index .Values._cluster "gateway-class-name") | default "cilium" }}
-{{- if and (eq $gatewayAPI "true") (ne $gateway "") (not (eq .Values.topology "Client")) (.Values.filer.grpcHost) }}
+{{- if and (eq $gatewayAPI "true") (ne $gateway "") (not (eq .Values.topology "Client")) (or .Values.filer.grpcHost $host) }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {{- $gatewayAPI := (index .Values._cluster "gateway-api") | default "false" }} | |
| {{- $gatewayAPI := (index .Values._cluster "gateway-api") | default false | toString | lower }} |
🧰 Tools
🪛 YAMLlint (1.38.0)
[error] 1-1: syntax error: expected the node content, but found '-'
(syntax)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/extra/seaweedfs/templates/tlsroute.yaml` at line 1, Normalize the
gateway-api check by converting the value to a string before comparing and
ensure the grpcHost fallback is considered in the conditional: change the
template to set $gatewayAPI := printf "%v" (index .Values._cluster "gateway-api"
| default false) and then compute a $grpcHost variable using default
.Values.filer.grpcHost with the same fallback expression used later in the
template (the explicit fallback hostnames present where grpcHost is used), and
use $grpcHost in the if-condition that currently compares $gatewayAPI to "true"
so boolean/unquoted values and the fallback host are both respected.
|
|
||
| {{- $gatewayClassName := (index .Values._cluster "gateway-class-name") | default "cilium" }} | ||
|
|
||
| {{- if and (eq $gatewayAPI "true") (ne $gateway "") (not (eq .Values.topology "Client")) (.Values.filer.grpcHost) }} |
There was a problem hiding this comment.
Render gate blocks the documented/default hostname path.
Line 7 requires .Values.filer.grpcHost, but Lines 22 and 38 already support fallback to filer.<host>. With grpcHost empty and _namespace.host set, this template won’t render at all.
🔧 Proposed fix
{{- $gatewayClassName := (index .Values._cluster "gateway-class-name") | default "cilium" }}
+{{- $filerHost := .Values.filer.grpcHost | default (printf "filer.%s" $host) }}
-{{- if and (eq $gatewayAPI "true") (ne $gateway "") (not (eq .Values.topology "Client")) (.Values.filer.grpcHost) }}
+{{- if and (eq $gatewayAPI "true") (ne $gateway "") (not (eq .Values.topology "Client")) (or .Values.filer.grpcHost $host) }}
@@
- hostname: {{ .Values.filer.grpcHost | default (printf "filer.%s" $host) | quote }}
+ hostname: {{ $filerHost | quote }}
@@
- - {{ .Values.filer.grpcHost | default (printf "filer.%s" $host) | quote }}
+ - {{ $filerHost | quote }}Also applies to: 22-22, 38-38
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/extra/seaweedfs/templates/tlsroute.yaml` at line 7, The top render
condition currently requires (.Values.filer.grpcHost) which prevents the
template from rendering when grpcHost is empty but a fallback host exists;
update the initial if-condition (the line with and (eq $gatewayAPI "true") (ne
$gateway "") (not (eq .Values.topology "Client")) (.Values.filer.grpcHost)) to
instead allow either .Values.filer.grpcHost or the fallback host (e.g., check
for (.Values.filer.grpcHost) OR (. _namespace.host) / the same host variable
used in the fallback at the usages around the filer.<host> logic on lines
referencing the fallback) so the block renders when a fallback host is
available.
| {{- range splitList "," $exposeExternalIPs }} | ||
| - cidr: {{ . }}/32 | ||
| {{- end }} |
There was a problem hiding this comment.
Guard against empty IP entries when generating CIDRs.
If expose-external-ips has a trailing comma, this renders an invalid block (cidr: /32). Please skip empty items before emitting blocks.
🔧 Proposed fix
- {{- range splitList "," $exposeExternalIPs }}
- - cidr: {{ . }}/32
- {{- end }}
+ {{- range $ip := splitList "," $exposeExternalIPs }}
+ {{- if $ip }}
+ - cidr: {{ $ip }}/32
+ {{- end }}
+ {{- end }}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {{- range splitList "," $exposeExternalIPs }} | |
| - cidr: {{ . }}/32 | |
| {{- end }} | |
| {{- range $ip := splitList "," $exposeExternalIPs }} | |
| {{- if $ip }} | |
| - cidr: {{ $ip }}/32 | |
| {{- end }} | |
| {{- end }} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/system/cozystack-gateway/templates/gateway.yaml` around lines 111 -
113, The template loop that emits "cidr: {{ . }}/32" from "{{- range splitList
\",\" $exposeExternalIPs }}" can produce "cidr: /32" for empty items; update the
range to skip empty/whitespace entries by checking the trimmed value before
emitting. Specifically, inside the range over splitList "," $exposeExternalIPs,
wrap the cidr block in a conditional like "if (ne (trim .) \"\")" (or equivalent
trim+not-empty check) so only non-empty values produce "cidr: {{ . }}/32".
| rm -rf templates | ||
| mkdir templates | ||
| kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v1.2.0" > templates/crds-experimental.yaml | ||
| kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v1.5.0" > templates/crds-experimental.yaml |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What is the latest version of Kubernetes Gateway API and when was v1.5.0 released?
💡 Result:
The latest version of Kubernetes Gateway API is v1.5.1, released on March 14, 2026. The v1.5.0 version was released on February 26, 2026.
Citations:
- 1: https://github.com/kubernetes-sigs/gateway-api/releases
- 2: https://newreleases.io/project/github/kubernetes-sigs/gateway-api/release/v1.5.0
- 3: https://pkg.go.dev/sigs.k8s.io/gateway-api@v1.5.0
🌐 Web query:
Kubernetes Gateway API v1.5.0 release notes breaking changes changelog
💡 Result:
Kubernetes Gateway API v1.5.0 was released around February 26-27, 2026. Official release notes are available at https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.5.0. ## Breaking Changes 1. TLSRoute v1alpha2 and XListenerSet removal from Experimental channel: TLSRoute and ListenerSet have graduated to the Standard channel as v1. TLSRoute v1alpha2 is only in Experimental channel in v1.5.0 and will be removed in v1.6. Additionally, TLSRoute's CEL validation requires Kubernetes 1.31+. 2. New Validating Admission Policy (VAP): Introduces safe-upgrades.gateway.networking.k8s.io VAP to prevent: - Downgrading to versions prior to 1.5 after installing v1.5. - Installing Experimental CRDs after Standard CRDs. Workaround: Delete the VAP if needed for specific operations. ## Major Changes Since v1.4.1 (Standard Channel Graduations) - TLSRoute v1 (GEP-2643) - HTTPRoute CORS filter (GEP-1767) - ListenerSet support (GEP-1713) - Certificate selection for Gateway TLS origination (GEP-3155) - ReferenceGrant to v1 ## Experimental - Gateway/HTTPRoute level authentication (GEP-1494) Full changelog: kubernetes-sigs/gateway-api@v1.4.1...v1.5.0 Note: Use server-side apply for installation due to large CRD sizes: kubectl apply --server-side=true -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/[standard|experimental]-install.yaml. A patch release v1.5.1 followed with minor fixes.
Citations:
- 1: https://github.com/kubernetes-sigs/gateway-api/releases
- 2: https://newreleases.io/project/github/kubernetes-sigs/gateway-api/release/v1.5.0
Address breaking changes in Gateway API v1.5.0 before upgrading.
This updates Gateway API CRDs from v1.2.0 to v1.5.0 (released Feb 26, 2026). While the version is valid, this release includes breaking changes that require attention:
- TLSRoute and ListenerSet: Both graduated from Experimental to Standard channel as v1. TLSRoute v1alpha2 is deprecated and will be removed in v1.6.
- New Validating Admission Policy: A VAP (
safe-upgrades.gateway.networking.k8s.io) now prevents downgrading to pre-1.5 versions and mixing Experimental/Standard CRDs installed separately. - Kubernetes 1.31+ requirement: TLSRoute CEL validation requires Kubernetes 1.31 or later.
Consider whether existing resources depend on the experimental v1alpha2 types and verify cluster compatibility before applying. Use kubectl apply --server-side=true due to large CRD sizes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/system/gateway-api-crds/Makefile` at line 9, The Makefile now
kustomizes Gateway API CRDs to v1.5.0 (the kubectl kustomize
"github.com/kubernetes-sigs/gateway-api/config/crd/experimental?ref=v1.5.0"
line) but v1.5.0 has breaking changes (TLSRoute and ListenerSet moved from
Experimental->Standard, new validating admission policy
safe-upgrades.gateway.networking.k8s.io, and TLSRoute CEL requiring Kubernetes
>=1.31); before changing the ref, either keep the existing v1.2.0 CRD ref or
update other CRD targets so Standard and Experimental CRDs are applied together,
scan the cluster for resources using TLSRoute v1alpha2 and migrate them to the
new v1 types (or plan removal), ensure target clusters run Kubernetes >=1.31 if
using TLSRoute CEL, and apply CRDs with kubectl apply --server-side=true to
avoid VAP conflicts; also remove or adjust generation of
templates/crds-experimental.yaml if you intend to fully migrate to the Standard
channel so you don't install mixed channels separately which will be blocked by
the new VAP.
| backendRefs: | ||
| - name: keycloak-http | ||
| port: 80 |
There was a problem hiding this comment.
Critical: Port mismatch will cause routing failure.
The HTTPRoute references port 80, but per packages/system/keycloak/templates/service.yaml, the keycloak-http Service exposes port 8080:
# From service.yaml
ports:
- name: http
port: 8080Traffic to this HTTPRoute will fail because port 80 doesn't exist on the backend Service.
🐛 Proposed fix
backendRefs:
- name: keycloak-http
- port: 80
+ port: 8080🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/system/keycloak/templates/httproute.yaml` around lines 27 - 29, The
HTTPRoute's backendRefs currently points to port 80 which does not exist on the
keycloak-http Service (service.yaml exposes port 8080); update the HTTPRoute
backendRefs entry for name: keycloak-http to use port: 8080 (replace the
existing port: 80) so the route matches the Service's exposed port and routing
will succeed.
What this PR does
Adds Gateway API (HTTPRoute/TLSRoute) support alongside existing Ingress resources for all system and tenant-level components, enabling a migration path from ingress-nginx to Cilium Gateway API.
Architecture
System services use a central Gateway (
cozystack-gatewaypackage incozy-gatewaynamespace) with per-hostname HTTPS listeners and TLS passthrough listeners. Components create HTTPRoute/TLSRoute resources that attach to this central Gateway via parentRefs.Tenant services (monitoring, bucket, harbor, bootbox, seaweedfs) create per-component Gateways with
infrastructure.labelsto share a single LoadBalancer IP per tenant. The labelcozystack.io/gateway: <tenant-name>is the Gateway API equivalent of the IngressClass-per-tenant pattern.Tenant gateway model
A new
gatewayfield is added to tenant values (default:false). When set, the tenant namespace getsnamespace.cozystack.io/gatewaylabel and_namespace.gatewayconfig propagated to child packages. This follows the same inheritance model as the existingingressfield — child tenants inherit the parent's gateway name, or can setgateway: trueto get their own dedicated LoadBalancer.ExternalIPs handling
publishing.externalIPsare shared between ingress and gateway with ingress taking priority:CiliumLoadBalancerIPPoolis created with the externalIPs, matched to the Gateway's LoadBalancer Service viacozystack.io/gatewaylabelKeycloak changes
Keycloak sticky session route encoding is disabled (
KC_SPI_STICKY_SESSION_ENCODER_INFINISPAN_SHOULD_ATTACH_ROUTE=false). Cilium Gateway API does not support cookie-based session affinity. Keycloak with Infinispan distributed cache works without sticky sessions — sessions are replicated across nodes, adding at most one extra network hop to the session owner.Cilium configuration
gatewayAPI.enabled: truewhengateway.gatewayAPIis setenvoy.streamIdleTimeoutDurationSeconds: 86400— increased from default 300s to support long-lived connections (WebSocket/noVNC, large uploads)gatewayAPI.enableAlpn: true— required for gRPC backend protocol support (seaweedfs)Components covered
All Gateway API resources are conditional on
gateway.gatewayAPI: true. Existing Ingress templates are untouched — both can coexist simultaneously.Release note
Summary by CodeRabbit
New Features
Tests