diff --git a/changelog.d/5-internal/WPB-27377-ListenerSet b/changelog.d/5-internal/WPB-27377-ListenerSet new file mode 100644 index 00000000000..1e8a14db3ef --- /dev/null +++ b/changelog.d/5-internal/WPB-27377-ListenerSet @@ -0,0 +1 @@ +Add support for ListenerSet in wire-ingress / EnvoyGateway diff --git a/charts/wire-ingress/README.md b/charts/wire-ingress/README.md index 73433734acc..c22d3763ce6 100644 --- a/charts/wire-ingress/README.md +++ b/charts/wire-ingress/README.md @@ -3,6 +3,8 @@ A Helm chart for Wire server ingress using the **Kubernetes Gateway API**. The chart targets **Envoy Gateway** as the Gateway API controller. +Each release renders one `ListenerSet`; chart-managed Routes attach to that +ListenerSet rather than directly to the Gateway. --- @@ -56,7 +58,8 @@ possible. Most existing values files should work with minimal changes. Add a `gateway` block to your values and review at least the following keys: - `gateway.className` — set to the `GatewayClass` name created during installation (see above). -- `gateway.create` — if `false`, you must create a `Gateway` object yourself and set `gateway.name` to its name. +- `gateway.create` — if `false`, you must create a `Gateway` object yourself and set `gateway.name` + and, if needed, `gateway.namespace` to its name and namespace. - `gateway.listeners.https.hostname` — set to `*.`. This assumes all domains under `config.dns.*` are subdomains of ``. If that is not the case, create your own `Gateway` and set `gateway.create: false`. @@ -78,18 +81,19 @@ name overrides, etc.) can be found in `values.yaml`. | Key | Default | Description | |---|---|---| -| `gateway.create` | `true` | If `false`, no `Gateway` resource is created — set `gateway.name` to reference an existing one. Useful when sharing a Gateway across multiple releases. | +| `gateway.create` | `true` | If `false`, no `Gateway` resource is created. The chart still creates a ListenerSet and you must set `gateway.name` and, if needed, `gateway.namespace` to reference the parent Gateway. Useful when sharing a Gateway across multiple releases. | | `gateway.className` | `""` | **Required.** Name of the `GatewayClass` installed by the Envoy Gateway controller (e.g. `envoy`). Must match the `GatewayClass` object whose `spec.controllerName` is `gateway.envoyproxy.io/gatewayclass-controller`. | -| `gateway.alpn.enabled` | `true` | Enables ALPN configuration via `ClientTrafficPolicy` to support HTTP/2 despite overlapping certificate SANs across multiple service listeners. When disabled, ALPN defaults to HTTP/1.1 only. | +| `gateway.namespace` | `""` | Namespace of the parent Gateway. Defaults to the release namespace when empty. Only needed when `gateway.create: false` points at a Gateway in another namespace. | +| `gateway.alpn.enabled` | `true` | Enables ALPN configuration via a `ClientTrafficPolicy` on the parent Gateway (which Envoy Gateway applies to the ListenerSet's listeners) to support HTTP/2 despite overlapping certificate SANs across multiple service listeners. When disabled, ALPN defaults to HTTP/1.1 only. | | `gateway.alpn.protocols` | `[h2, http/1.1]` | List of ALPN protocols to advertise to clients. Defaults to HTTP/2 with HTTP/1.1 fallback. | -| `gateway.listeners.http.enabled` | `false` | Enables the HTTP listener on port 80. Required for HTTP01 ACME challenges via cert-manager's `gatewayHTTPRoute` solver — see [HTTP01 certificate challenges](#http01-certificate-challenges). | +| `gateway.listeners.http.enabled` | `false` | Enables the HTTP listener on port 80 on the parent Gateway. Required for HTTP01 ACME challenges via cert-manager's `gatewayHTTPRoute` solver. When enabled, the chart annotates the generated Certificate so cert-manager can fall back to the parent Gateway for the ACME HTTPRoute. The parent Gateway must allow HTTPRoutes from the release namespace. See [HTTP01 certificate challenges](#http01-certificate-challenges). | | `gateway.envoyProxy.create` | `true` | If `false`, no `EnvoyProxy` resource is created. Set `gateway.envoyProxy.name` to reference an existing one, or leave it empty to inherit the GatewayClass-level `EnvoyProxy`. | | `gateway.envoyProxy.name` | _(derived)_ | When `create: true` — name of the created resource. When `create: false` — name of an existing `EnvoyProxy` to reference via `infrastructure.parametersRef`. | | `gateway.envoyProxy.spec` | `{}` | Free-form [EnvoyProxySpec](https://gateway.envoyproxy.io/docs/api/extension_types/#envoyproxyspec) merged verbatim. Use to set `mergeGateways`, custom service annotations, etc. | | `gateway.manageServiceType` | `true` | Shorthand that sets `envoyService.type` to `gateway.serviceType`. Disable when managing the service type via `gateway.envoyProxy.spec` directly. | | `gateway.serviceType` | `LoadBalancer` | Service type for the Envoy proxy service. Only used when `gateway.manageServiceType: true`. | | `gateway.infrastructure.annotations` | `{}` | Annotations forwarded to the LoadBalancer Service provisioned by Envoy Gateway — see [Gateway API docs](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayInfrastructure). Use for cloud-specific LB settings (e.g. AWS NLB). | -| `gateway.proxyProtocol.enabled` | `false` | Creates a `ClientTrafficPolicy` enabling PROXY protocol on all listeners. Required when the upstream load balancer is configured to send PROXY protocol headers. | +| `gateway.proxyProtocol.enabled` | `false` | Creates a `ClientTrafficPolicy` enabling PROXY protocol on the parent Gateway (which Envoy Gateway applies to the ListenerSet's listeners). Required when the upstream load balancer is configured to send PROXY protocol headers. | | `gateway.patchPolicies.enabled` | `true` | Controls whether `EnvoyPatchPolicy` resources are created — see [EnvoyPatchPolicy](#envoypatchpolicy). | | `gateway.patchPolicies.targetGatewayClass` | `false` | When `true`, `EnvoyPatchPolicy` targets the `GatewayClass` instead of the `Gateway`. **Required when `gateway.envoyProxy.spec.mergeGateways: true`**: with merged Gateways, policies targeting a `Gateway` are not applied — they must target the `GatewayClass`. Leave `false` for single-Gateway deployments (e.g. integration tests). | | `gateway.controllerNamespace` | `envoy-gateway-system` | Can be ignored, relevant only for integration tests. Namespace where Envoy Gateway runs its proxy pods. Change only if Envoy Gateway was installed into a non-default namespace. | @@ -170,13 +174,55 @@ used where the standard Gateway API has gaps. These resources are clearly marked ### Gateway creation is optional -The chart can optionally create a `Gateway` resource (controlled by `gateway.create: true`). -When `gateway.create: false`, all `HTTPRoute` and policy resources still reference the gateway by -name (`gateway.name`). This allows operators to share a Gateway across multiple charts or manage it -separately. +The chart always creates one `ListenerSet` per release. Chart-managed `HTTPRoute`s attach to that +ListenerSet. -The default values create the Gateway. The default `gateway.name` is derived from the release name, -so that self-referencing is consistent by default. +Chart-managed `HTTPRoute`s attach to the ListenerSet. Envoy Gateway `ClientTrafficPolicy` +resources instead target the parent `Gateway`, because Envoy Gateway v1.8.x does not allow a +`ClientTrafficPolicy` to target a `ListenerSet`. Envoy Gateway merges the ListenerSet's listeners +into the parent Gateway before policy translation, so a Gateway-scoped policy still applies to +them; where a policy must affect only one listener (federator mTLS) it is narrowed with +`sectionName`. + +When `gateway.create: true`, the chart also creates the parent `Gateway` in the release namespace +and configures it to accept ListenerSets from the same namespace. The Gateway keeps a placeholder +listener so current Gateway API validation is satisfied, while the real traffic listeners live in +the ListenerSet. + +When `gateway.create: false`, set `gateway.name` and, if needed, `gateway.namespace` to attach the +ListenerSet to an existing Gateway. That Gateway must already allow ListenerSet attachment. + +#### Shared/demo + +Many releases can share a platform-managed Gateway: + +```yaml +gateway: + create: false + name: shared-wire-gateway + namespace: ingress-system + listeners: + https: + hostname: "*.demo.example.com" +``` + +#### Dedicated/production + +The chart can also create its own dedicated Gateway and ListenerSet: + +```yaml +gateway: + create: true + className: envoy + listeners: + https: + hostname: "*.example.com" + http: + enabled: true +``` + +In this model the Gateway, ListenerSet, Envoy proxy fleet, and LoadBalancer all stay dedicated to +the release. ### EnvoyProxy resource @@ -214,9 +260,12 @@ header. Envoy's virtual-host matching is exact, so the trailing dot causes a `ro error. Adding the FQDN as an additional domain in the route configuration allows Envoy to match both the bare hostname and the FQDN. -The policy patches the `RouteConfiguration` named `//federator`. Route -configuration names are per-namespace even when multiple Gateways share a single Envoy proxy, so -the name is predictable from chart values. +The policy patches the `RouteConfiguration` named +`//federator`. Route configuration names are per-namespace even +when multiple Gateways share a single Envoy proxy, so the name is predictable from chart values. +With the current Envoy Gateway schema, `gateway.patchPolicies.targetGatewayClass: false` only works +when the parent Gateway lives in the release namespace. If the parent Gateway is in another +namespace, set `gateway.patchPolicies.targetGatewayClass: true` or expect the chart to fail fast. **`gateway.patchPolicies.targetGatewayClass`** controls what the policy targets: @@ -262,7 +311,10 @@ disabled — setting both fails template rendering with a clear error. cert-manager can complete ACME HTTP01 challenges through the Gateway using the `gatewayHTTPRoute` solver (cert-manager >= 1.14). The **default solver** in this chart uses `gatewayHTTPRoute` — it -requires the HTTP listener to be enabled: +requires the HTTP listener to be enabled. When `gateway.listeners.http.enabled: true`, the chart +adds `acme.cert-manager.io/http01-parentreffallback: "true"` to the generated Certificate so +cert-manager attaches the challenge HTTPRoute to the parent Gateway instead of the ListenerSet. +That means a shared Gateway must allow HTTPRoutes from the release namespace: ```yaml gateway: @@ -271,6 +323,9 @@ gateway: enabled: true # required for HTTP01 challenges ``` +If you are using a shared Gateway in another namespace, make sure its HTTP listener allows +HTTPRoutes from the release namespace. If that is not acceptable, use a DNS01 solver instead. + If you cannot or do not want to open port 80, use a DNS01 solver instead by setting ```yaml @@ -310,7 +365,10 @@ federator: ### HTTP/2 support with ALPN ClientTrafficPolicy -The chart creates a `ClientTrafficPolicy` resource that explicitly configures ALPN protocols when `gateway.alpn.enabled: true`. This is necessary because when a single certificate with multiple SANs is used across multiple listeners on the same port, Envoy would otherwise disable HTTP/2 as a safety measure to prevent connection coalescing attacks. +The chart creates a `ClientTrafficPolicy` resource that explicitly configures ALPN protocols when +`gateway.alpn.enabled: true`. This is necessary because when a single certificate with multiple SANs +is used across multiple listeners on the same port, Envoy would otherwise disable HTTP/2 as a safety +measure to prevent connection coalescing attacks. The policy sets `spec.tls.alpnProtocols` to: ```yaml @@ -324,9 +382,10 @@ This allows HTTP/2 to be negotiated while maintaining support for older clients Federator mTLS is implemented using: -- `ClientTrafficPolicy` to configure TLS settings on the federator `Gateway` listener (client - certificate validation, verify depth) -- A separate `Gateway` listener for the federator so that mTLS settings apply only to that listener +- `ClientTrafficPolicy` targeting the parent Gateway, scoped via `sectionName` to the federator + listener, to configure TLS settings (client certificate validation, verify depth) +- A separate ListenerSet listener for the federator so that mTLS settings apply only to that + listener - `X-SSL-Certificate` header forwarding is handled via an `EnvoyExtensionPolicy` with an inline Lua filter that reads the URL-encoded PEM client certificate from the connection and injects it as a request header, matching nginx's `$ssl_client_escaped_cert` behaviour diff --git a/charts/wire-ingress/templates/_helpers.tpl b/charts/wire-ingress/templates/_helpers.tpl index 5586e57d02a..5ac3ec9efc1 100644 --- a/charts/wire-ingress/templates/_helpers.tpl +++ b/charts/wire-ingress/templates/_helpers.tpl @@ -69,8 +69,10 @@ Name of the Gateway resource. Uses gateway.name if set, otherwise derives one fr {{- define "wire-ingress.gatewayName" -}} {{- if .Values.gateway.name -}} {{ .Values.gateway.name }} -{{- else -}} +{{- else if .Values.gateway.create -}} {{ include "wire-ingress.fullname" . }}-gateway +{{- else -}} +{{- fail "gateway.name must be set when gateway.create is false" -}} {{- end -}} {{- end -}} @@ -206,3 +208,32 @@ Call with a dict: {https, ssl, base, websockets (bool)}. {{- $csp = printf "%s upgrade-insecure-requests" $csp -}} {{- $csp -}} {{- end -}} + +{{/* +Name of the ListenerSet resource. One ListenerSet is created per release. +*/}} +{{- define "wire-ingress.listenerSetName" -}} +{{ include "wire-ingress.fullname" . }}-listeners +{{- end -}} + +{{/* +Namespace of the parent Gateway. When the chart creates the Gateway, the +parent Gateway is always in the release namespace. When attaching to an +existing Gateway, gateway.namespace can override that default. +*/}} +{{- define "wire-ingress.parentGatewayNamespace" -}} +{{- if .Values.gateway.create -}} +{{ .Release.Namespace }} +{{- else -}} +{{ default .Release.Namespace .Values.gateway.namespace }} +{{- end -}} +{{- end -}} + +{{/* +Hostname for the placeholder Gateway listener required by the current Gateway +API validation. It is intentionally non-routable and must not overlap with the +real ListenerSet hostnames. +*/}} +{{- define "wire-ingress.gatewayDummyHostname" -}} +{{ printf "%s.invalid" (include "wire-ingress.fullname" .) }} +{{- end -}} diff --git a/charts/wire-ingress/templates/certificate.yaml b/charts/wire-ingress/templates/certificate.yaml index 2bb262c7d2f..0c0c2fb9042 100644 --- a/charts/wire-ingress/templates/certificate.yaml +++ b/charts/wire-ingress/templates/certificate.yaml @@ -12,6 +12,10 @@ metadata: chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" release: "{{ $root.Release.Name }}" heritage: "{{ $root.Release.Service }}" + {{- if $root.Values.gateway.listeners.http.enabled }} + annotations: + acme.cert-manager.io/http01-parentreffallback: "true" + {{- end }} spec: issuerRef: name: {{ $domain.issuerName | quote }} diff --git a/charts/wire-ingress/templates/clienttrafficpolicy-alpn.yaml b/charts/wire-ingress/templates/clienttrafficpolicy-alpn.yaml index 4a63f7060a3..184a94fd10d 100644 --- a/charts/wire-ingress/templates/clienttrafficpolicy-alpn.yaml +++ b/charts/wire-ingress/templates/clienttrafficpolicy-alpn.yaml @@ -5,13 +5,17 @@ apiVersion: gateway.envoyproxy.io/v1alpha1 kind: ClientTrafficPolicy metadata: - name: {{ include "wire-ingress.gatewayName" . }}-alpn + name: {{ include "wire-ingress.listenerSetName" . }}-alpn namespace: {{ .Release.Namespace }} labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: + {{/* Envoy Gateway v1.8.x rejects ListenerSet as a ClientTrafficPolicy + targetRef (only Gateway is allowed). Targeting the parent Gateway with + no sectionName applies ALPN to every listener on the Gateway, including + the ones contributed by the ListenerSet. */}} targetRefs: - group: gateway.networking.k8s.io kind: Gateway diff --git a/charts/wire-ingress/templates/clienttrafficpolicy-federator.yaml b/charts/wire-ingress/templates/clienttrafficpolicy-federator.yaml index f51a72058cb..25fafa48867 100644 --- a/charts/wire-ingress/templates/clienttrafficpolicy-federator.yaml +++ b/charts/wire-ingress/templates/clienttrafficpolicy-federator.yaml @@ -1,16 +1,20 @@ {{- if .Values.federator.enabled }} {{/* Envoy Gateway-specific (gateway.envoyproxy.io/v1alpha1). - Enforces mTLS client certificate validation on the federator listener only. */}} + Enforces mTLS client certificate validation on the federator ListenerSet listener only. */}} apiVersion: gateway.envoyproxy.io/v1alpha1 kind: ClientTrafficPolicy metadata: - name: {{ include "wire-ingress.gatewayName" . }}-federator-mtls + name: {{ include "wire-ingress.listenerSetName" . }}-federator-mtls namespace: {{ .Release.Namespace }} labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: + {{/* Envoy Gateway v1.8.x rejects ListenerSet as a ClientTrafficPolicy + targetRef (only Gateway is allowed). The federator listener lives on the + ListenerSet but is merged into the parent Gateway's listener set, so we + target the Gateway and scope to that listener via sectionName. */}} targetRefs: - group: gateway.networking.k8s.io kind: Gateway diff --git a/charts/wire-ingress/templates/clienttrafficpolicy-proxy-protocol.yaml b/charts/wire-ingress/templates/clienttrafficpolicy-proxy-protocol.yaml index ed856304088..60c7683d63a 100644 --- a/charts/wire-ingress/templates/clienttrafficpolicy-proxy-protocol.yaml +++ b/charts/wire-ingress/templates/clienttrafficpolicy-proxy-protocol.yaml @@ -2,13 +2,17 @@ apiVersion: gateway.envoyproxy.io/v1alpha1 kind: ClientTrafficPolicy metadata: - name: {{ include "wire-ingress.gatewayName" . }}-proxy-protocol + name: {{ include "wire-ingress.listenerSetName" . }}-proxy-protocol namespace: {{ .Release.Namespace }} labels: chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" spec: + {{/* Envoy Gateway v1.8.x rejects ListenerSet as a ClientTrafficPolicy + targetRef (only Gateway is allowed). Targeting the parent Gateway with + no sectionName applies PROXY protocol to every listener on the Gateway, + including the ones contributed by the ListenerSet. */}} targetRefs: - group: gateway.networking.k8s.io kind: Gateway diff --git a/charts/wire-ingress/templates/envoypatchpolicy-federator.yaml b/charts/wire-ingress/templates/envoypatchpolicy-federator.yaml index 5b52b9ed938..a5f4a42bd99 100644 --- a/charts/wire-ingress/templates/envoypatchpolicy-federator.yaml +++ b/charts/wire-ingress/templates/envoypatchpolicy-federator.yaml @@ -1,4 +1,7 @@ {{- if and .Values.federator.enabled .Values.gateway.patchPolicies.enabled }} +{{- if and (not .Values.gateway.patchPolicies.targetGatewayClass) (ne (include "wire-ingress.parentGatewayNamespace" .) .Release.Namespace) }} +{{- fail "gateway.patchPolicies.targetGatewayClass must be true when the parent Gateway is in a different namespace" }} +{{- end }} {{/* Adds the FQDN variant (trailing dot) of the federator hostname to the virtual host's domain list so Envoy matches requests whose :authority header carries a trailing dot. @@ -27,13 +30,12 @@ spec: {{- else }} kind: Gateway name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} {{- end }} type: JSONPatch jsonPatches: - type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration" # Route config name: //federator - name: {{ printf "%s/%s/federator" .Release.Namespace (include "wire-ingress.gatewayName" .) | quote }} + name: {{ printf "%s/%s/federator" (include "wire-ingress.parentGatewayNamespace" .) (include "wire-ingress.gatewayName" .) | quote }} operation: op: add path: "/virtual_hosts/0/domains/-" diff --git a/charts/wire-ingress/templates/gateway.yaml b/charts/wire-ingress/templates/gateway.yaml index b547aae3e60..f190c77a8cf 100644 --- a/charts/wire-ingress/templates/gateway.yaml +++ b/charts/wire-ingress/templates/gateway.yaml @@ -13,6 +13,9 @@ metadata: heritage: "{{ .Release.Service }}" spec: gatewayClassName: {{ .Values.gateway.className | quote }} + allowedListeners: + namespaces: + from: Same {{- $envoyProxyName := "" }} {{- if .Values.gateway.envoyProxy.create }} {{- $envoyProxyName = .Values.gateway.envoyProxy.name | default (include "wire-ingress.gatewayName" .) }} @@ -33,29 +36,26 @@ spec: {{- end }} {{- end }} listeners: - {{- $domains := include "wire-ingress.domains" . | fromJsonArray }} - {{- range $domain := $domains }} - - name: {{ $domain.section }} - port: {{ $.Values.gateway.listeners.https.port }} - protocol: HTTPS - hostname: {{ required "an HTTPS listener hostname is required (gateway.listeners.https.hostname for single-domain, or config.domains[].base/hostname)" $domain.hostname | quote }} - tls: - mode: Terminate - certificateRefs: - - name: {{ $domain.secretName | quote }} - kind: Secret - {{- end }} - {{- if .Values.federator.enabled }} - - name: federator + {{/* Placeholder HTTPS listener. The real listeners live on the ListenerSet; + the Gateway API still requires the parent Gateway to declare at least + one listener. It is non-routable (allowedRoutes.namespaces.from: Same + but no route targets its section) and reuses the primary domain's + TLS secret, which is always created (secret.yaml or certificate.yaml) + — unlike a hardcoded certificateSecretName, which a primary domain that + overrides its own tls.secretName would leave uncreated. */}} + {{- $primaryDomain := index (include "wire-ingress.domains" . | fromJsonArray) 0 }} + - name: gateway port: {{ .Values.gateway.listeners.https.port }} protocol: HTTPS - hostname: {{ required "config.dns.federator is required when federator.enabled is true" .Values.config.dns.federator | quote }} + hostname: {{ include "wire-ingress.gatewayDummyHostname" . | quote }} + allowedRoutes: + namespaces: + from: Same tls: mode: Terminate certificateRefs: - - name: {{ required "federator.tls.secretName is required when federator.enabled is true" .Values.federator.tls.secretName | quote }} + - name: {{ $primaryDomain.secretName | quote }} kind: Secret - {{- end }} {{- if .Values.gateway.listeners.http.enabled }} - name: http port: {{ .Values.gateway.listeners.http.port }} diff --git a/charts/wire-ingress/templates/httproute-account-pages.yaml b/charts/wire-ingress/templates/httproute-account-pages.yaml index 385876a2e31..1da1a3f2d64 100644 --- a/charts/wire-ingress/templates/httproute-account-pages.yaml +++ b/charts/wire-ingress/templates/httproute-account-pages.yaml @@ -19,9 +19,9 @@ metadata: heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" $root | quote }} + - name: {{ include "wire-ingress.listenerSetName" $root | quote }} namespace: {{ $root.Release.Namespace | quote }} - kind: Gateway + kind: ListenerSet sectionName: {{ $domain.section }} hostnames: - {{ required "config.dns.accountPages is required when accountPages.enabled is true" $domain.accountPages | quote }} diff --git a/charts/wire-ingress/templates/httproute-federator.yaml b/charts/wire-ingress/templates/httproute-federator.yaml index 792d5045ce9..5c085ade2de 100644 --- a/charts/wire-ingress/templates/httproute-federator.yaml +++ b/charts/wire-ingress/templates/httproute-federator.yaml @@ -14,9 +14,9 @@ metadata: heritage: "{{ .Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" . | quote }} + - name: {{ include "wire-ingress.listenerSetName" . | quote }} namespace: {{ .Release.Namespace | quote }} - kind: Gateway + kind: ListenerSet sectionName: federator hostnames: - {{ .Values.config.dns.federator | quote }} diff --git a/charts/wire-ingress/templates/httproute-nginz-websockets.yaml b/charts/wire-ingress/templates/httproute-nginz-websockets.yaml index 6a49f499e32..24b260064ce 100644 --- a/charts/wire-ingress/templates/httproute-nginz-websockets.yaml +++ b/charts/wire-ingress/templates/httproute-nginz-websockets.yaml @@ -19,9 +19,9 @@ metadata: heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" $root | quote }} + - name: {{ include "wire-ingress.listenerSetName" $root | quote }} namespace: {{ $root.Release.Namespace | quote }} - kind: Gateway + kind: ListenerSet sectionName: {{ $domain.section }} hostnames: - {{ required "config.dns.ssl is required when websockets.enabled is true" $domain.ssl | quote }} diff --git a/charts/wire-ingress/templates/httproute-nginz.yaml b/charts/wire-ingress/templates/httproute-nginz.yaml index 6d020f232a6..0355acc3d10 100644 --- a/charts/wire-ingress/templates/httproute-nginz.yaml +++ b/charts/wire-ingress/templates/httproute-nginz.yaml @@ -17,9 +17,9 @@ metadata: heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" $root | quote }} + - name: {{ include "wire-ingress.listenerSetName" $root | quote }} namespace: {{ $root.Release.Namespace | quote }} - kind: Gateway + kind: ListenerSet sectionName: {{ $domain.section }} hostnames: - {{ required "config.dns.https is required" $domain.https | quote }} diff --git a/charts/wire-ingress/templates/httproute-s3.yaml b/charts/wire-ingress/templates/httproute-s3.yaml index a5e0cd2a3b5..f9c1751b296 100644 --- a/charts/wire-ingress/templates/httproute-s3.yaml +++ b/charts/wire-ingress/templates/httproute-s3.yaml @@ -19,9 +19,9 @@ metadata: heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" $root | quote }} + - name: {{ include "wire-ingress.listenerSetName" $root | quote }} namespace: {{ $root.Release.Namespace | quote }} - kind: Gateway + kind: ListenerSet sectionName: {{ $domain.section }} hostnames: - {{ required "config.dns.fakeS3 is required when fakeS3.enabled is true" $domain.fakeS3 | quote }} diff --git a/charts/wire-ingress/templates/httproute-team-settings.yaml b/charts/wire-ingress/templates/httproute-team-settings.yaml index 54f26f28447..5cf85462d1f 100644 --- a/charts/wire-ingress/templates/httproute-team-settings.yaml +++ b/charts/wire-ingress/templates/httproute-team-settings.yaml @@ -19,9 +19,9 @@ metadata: heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" $root | quote }} + - name: {{ include "wire-ingress.listenerSetName" $root | quote }} namespace: {{ $root.Release.Namespace | quote }} - kind: Gateway + kind: ListenerSet sectionName: {{ $domain.section }} hostnames: - {{ required "config.dns.teamSettings is required when teamSettings.enabled is true" $domain.teamSettings | quote }} diff --git a/charts/wire-ingress/templates/httproute-webapp.yaml b/charts/wire-ingress/templates/httproute-webapp.yaml index 07844ec80a9..d8c834675a4 100644 --- a/charts/wire-ingress/templates/httproute-webapp.yaml +++ b/charts/wire-ingress/templates/httproute-webapp.yaml @@ -19,9 +19,9 @@ metadata: heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" $root | quote }} + - name: {{ include "wire-ingress.listenerSetName" $root | quote }} namespace: {{ $root.Release.Namespace | quote }} - kind: Gateway + kind: ListenerSet sectionName: {{ $domain.section }} hostnames: - {{ required "config.dns.webapp is required when webapp.enabled is true" $domain.webapp | quote }} diff --git a/charts/wire-ingress/templates/issuer.yaml b/charts/wire-ingress/templates/issuer.yaml index 6e6fb4820d6..9a6a3363d91 100644 --- a/charts/wire-ingress/templates/issuer.yaml +++ b/charts/wire-ingress/templates/issuer.yaml @@ -32,7 +32,7 @@ spec: gatewayHTTPRoute: parentRefs: - name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} + namespace: {{ include "wire-ingress.parentGatewayNamespace" . | quote }} kind: Gateway {{- end }} {{- end -}} diff --git a/charts/wire-ingress/templates/listenerset.yaml b/charts/wire-ingress/templates/listenerset.yaml new file mode 100644 index 00000000000..7403c369e47 --- /dev/null +++ b/charts/wire-ingress/templates/listenerset.yaml @@ -0,0 +1,40 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: ListenerSet +metadata: + name: {{ include "wire-ingress.listenerSetName" . | quote }} + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRef: + group: gateway.networking.k8s.io + kind: Gateway + name: {{ include "wire-ingress.gatewayName" . | quote }} + namespace: {{ include "wire-ingress.parentGatewayNamespace" . | quote }} + listeners: + {{- $root := . -}} + {{- $domains := include "wire-ingress.domains" . | fromJsonArray }} + {{- range $domain := $domains }} + - name: {{ $domain.section }} + port: {{ $root.Values.gateway.listeners.https.port }} + protocol: HTTPS + hostname: {{ required "an HTTPS listener hostname is required (gateway.listeners.https.hostname for single-domain, or config.domains[].base/hostname)" $domain.hostname | quote }} + tls: + mode: Terminate + certificateRefs: + - name: {{ $domain.secretName | quote }} + kind: Secret + {{- end }} + {{- if .Values.federator.enabled }} + - name: federator + port: {{ .Values.gateway.listeners.https.port }} + protocol: HTTPS + hostname: {{ required "config.dns.federator is required when federator.enabled is true" .Values.config.dns.federator | quote }} + tls: + mode: Terminate + certificateRefs: + - name: {{ required "federator.tls.secretName is required when federator.enabled is true" .Values.federator.tls.secretName | quote }} + kind: Secret + {{- end }} diff --git a/charts/wire-ingress/templates/service-test-fed.yaml b/charts/wire-ingress/templates/service-test-fed.yaml index 096a9b213ac..6853e651d56 100644 --- a/charts/wire-ingress/templates/service-test-fed.yaml +++ b/charts/wire-ingress/templates/service-test-fed.yaml @@ -34,5 +34,5 @@ spec: targetPort: {{ $httpsPort }} selector: gateway.envoyproxy.io/owning-gateway-name: {{ include "wire-ingress.gatewayName" . }} - gateway.envoyproxy.io/owning-gateway-namespace: {{ .Release.Namespace }} + gateway.envoyproxy.io/owning-gateway-namespace: {{ include "wire-ingress.parentGatewayNamespace" . }} {{- end }} diff --git a/charts/wire-ingress/values.yaml b/charts/wire-ingress/values.yaml index bcd14ac911b..721005ebced 100644 --- a/charts/wire-ingress/values.yaml +++ b/charts/wire-ingress/values.yaml @@ -5,10 +5,17 @@ httpRoute: gateway: # If true, a Gateway resource is created by this chart. - # If false, set gateway.name to reference an existing Gateway. + # The chart always creates a ListenerSet and Routes attach to it. + # If false, set gateway.name and optionally gateway.namespace to reference an + # existing parent Gateway. create: true - # Name of the Gateway. Defaults to -wire-ingress-gateway if empty. + # Name of the parent Gateway. Defaults to -wire-ingress-gateway + # if empty. name: "" + # Namespace of the parent Gateway. Defaults to the release namespace when + # empty. Only needed when referencing an existing Gateway in another + # namespace. + namespace: "" # Name of the GatewayClass installed by the Envoy Gateway controller. className: "" envoyProxy: @@ -28,6 +35,10 @@ gateway: # Free-form EnvoyProxy spec, merged verbatim at the spec root. # Only used when create: true. # See https://gateway.envoyproxy.io/docs/api/extension_types/#envoyproxyspec + # The chart also injects a safe default access log format that strips query + # strings, so access_token query params never appear in Envoy logs. + # Override gateway.envoyProxy.spec.telemetry.accessLog here if you need a + # custom format or different sinks. # Example - shared load balancer across Gateways (single wire-ingress chart): # spec: # mergeGateways: true @@ -58,13 +69,16 @@ gateway: annotations: {} # Enable if your load balancer sends PROXY protocol headers (e.g. Hetzner with # load-balancer.hetzner.cloud/uses-proxyprotocol: "true"). Creates a - # ClientTrafficPolicy targeting the Gateway. + # ClientTrafficPolicy targeting the parent Gateway (which applies to the + # ListenerSet listeners; Envoy Gateway does not allow targeting a ListenerSet + # directly). proxyProtocol: enabled: false # If true, connections without a PROXY protocol header are also accepted. # Use this only if some traffic bypasses the load balancer (e.g. direct node access). optional: false listeners: + # Listener configuration owned by the release's ListenerSet. https: port: 443 # Required. Hostname restriction for the HTTPS listener. @@ -77,8 +91,11 @@ gateway: # Gateway object with multiple listeners. See the Gateway API docs. hostname: "" # Enable the HTTP listener if you want to use HTTP01 challenges via cert-manager's - # gatewayHTTPRoute solver. Requires cert-manager >= 1.14. - # See the README for configuration details. + # gatewayHTTPRoute solver. The chart also annotates the generated Certificate with + # acme.cert-manager.io/http01-parentreffallback: "true" so cert-manager can attach + # the challenge HTTPRoute via the parent Gateway when using ListenerSet support. + # The parent Gateway must allow HTTPRoutes from the release namespace. + # See the README for configuration details and cert-manager version requirements. http: enabled: false port: 80 @@ -88,7 +105,9 @@ gateway: # ALPN configuration for HTTP/2 support with multiple listeners. # When using multiple listeners with overlapping certificate SANs, # Envoy Gateway may downgrade to HTTP/1.1. This ClientTrafficPolicy - # explicitly configures ALPN to allow HTTP/2. + # explicitly configures ALPN. It targets the parent Gateway, which applies + # to every listener including those contributed by the ListenerSet (Envoy + # Gateway does not allow targeting a ListenerSet directly). alpn: enabled: true protocols: @@ -102,7 +121,8 @@ gateway: # Set to true when the GatewayClass uses mergeGateways: true. # With mergeGateways, EnvoyPatchPolicy must target the GatewayClass # rather than the individual Gateway. Default false targets the Gateway, - # which is correct for single-Gateway (non-merged) deployments. + # which is correct for single-Gateway (non-merged) deployments in the + # release namespace. targetGatewayClass: false # NOTE: Please provide names. Here are naming suggestions: @@ -278,4 +298,4 @@ certManager: # # When federator.enabled is true, a ConfigMap named `federator-ca` with a `ca.crt` key # must exist in the release namespace. It is created by the wire-server chart -# and is referenced by the ClientTrafficPolicy for mTLS. +# and is referenced by the ClientTrafficPolicy for mTLS on the parent Gateway.