diff --git a/changelog.d/2-features/WPB-25475 b/changelog.d/2-features/WPB-25475 new file mode 100644 index 0000000000..a784e638b7 --- /dev/null +++ b/changelog.d/2-features/WPB-25475 @@ -0,0 +1 @@ +Add multi-domain support to the `wire-ingress` (Envoy Gateway) chart. Set `config.domains` to serve several backend domains from one release, each with its own listener, certificate, and injected per-domain CSP. Single-domain deployments via `config.dns` are unchanged. diff --git a/charts/wire-ingress/README.md b/charts/wire-ingress/README.md index d107cfa3a7..2fb3000c4d 100644 --- a/charts/wire-ingress/README.md +++ b/charts/wire-ingress/README.md @@ -101,10 +101,10 @@ name overrides, etc.) can be found in `values.yaml`. | Old key | Reason | |---|---| | `config.ingressClass` | | -| `ingressName` | Multi-ingress out of scope | -| `config.isAdditionalIngress` | Multi-ingress out of scope | -| `config.renderCSPInIngress` | Multi-ingress out of scope | -| `config.dns.base` | Only used for CSP header rendering, which is a multi-ingress feature | +| `ingressName` | Replaced by `config.domains[].name` — see [Multi-ingress (multiple backend domains)](#multi-ingress-multiple-backend-domains) | +| `config.isAdditionalIngress` | Implicit — every `config.domains` entry after the first is an additional ingress | +| `config.renderCSPInIngress` | CSP is injected automatically on additional domains; opt out per-domain with `config.domains[].renderCSP: false` | +| `config.dns.base` | Replaced by `config.domains[].base` (used for the per-domain CSP wildcard) | | `tls.verify_depth` | Envoy Gateway `ClientTrafficPolicy` does not expose a direct verify-depth knob; the CA chain itself controls this | | `tls.enabled` | Removed — had no effect; all routes are always TLS-terminated | | `secrets.tlsClientCA` | No longer supplied via values. The `federator-ca` ConfigMap is created by the wire-server chart and referenced directly. | @@ -232,9 +232,26 @@ the name is predictable from chart values. --- -### Multi-ingress is out of scope +### Multi-ingress (multiple backend domains) -Single-domain deployments are the only supported topology. Multi-domain support can be added later. +Set `config.domains` **instead of** `config.dns` to serve several domains from one release: + +```yaml +config: + domains: + - name: blueberry + base: blueberry.example.com + dns: { https: nginz-https.blueberry.example.com, ssl: nginz-ssl.blueberry.example.com, webapp: webapp.blueberry.example.com } + - name: red + base: red.example.org + dns: { https: nginz-https.red.example.org, ssl: nginz-ssl.red.example.org, webapp: webapp.red.example.org } + tls: { issuer: { name: letsencrypt-red, kind: ClusterIssuer } } # optional per-domain issuer +``` + +First entry = primary (listener `https`, un-suffixed names, no injected CSP — apps set their own). +Each additional entry gets its own listener `https-`, cert/secret, suffixed routes, and an +injected per-domain CSP header on the webapp/team-settings/account-pages routes (opt out with +`renderCSP: false`). `federator` stays single-domain on the primary listener. ### HTTP01 certificate challenges diff --git a/charts/wire-ingress/templates/_helpers.tpl b/charts/wire-ingress/templates/_helpers.tpl index 263f190c90..1a2fdf4b63 100644 --- a/charts/wire-ingress/templates/_helpers.tpl +++ b/charts/wire-ingress/templates/_helpers.tpl @@ -73,3 +73,116 @@ Name of the Gateway resource. Uses gateway.name if set, otherwise derives one fr {{ include "wire-ingress.fullname" . }}-gateway {{- end -}} {{- end -}} + +{{/* +Normalized list of ingress domains, returned as a JSON array so callers can +`fromJsonArray` and range over it. + +Back-compat: when `config.domains` is NOT set, a single "primary" entry is +derived from the legacy scalar `config.dns` + `gateway.listeners.https.hostname`, +so existing single-domain deployments render exactly as before. + +Multi-domain: `config.domains` is a list; the FIRST entry is the primary +(its resources keep the un-suffixed names, and its frontend apps set their own +CSP so no CSP is injected). Every additional entry gets a `-` suffix, its +own Gateway listener (`https-`), its own certificate/secret, and — being +an "additional ingress" — a per-domain CSP header injected on the app routes. + +Each entry has: suffix, section, hostname, https, ssl, webapp, teamSettings, +accountPages, fakeS3, base, secretName, certName, issuerName, issuerKind, +primary (bool), csp (bool). +*/}} +{{- define "wire-ingress.domains" -}} +{{- $root := . -}} +{{- $fullname := include "wire-ingress.fullname" . -}} +{{- $out := list -}} +{{- if .Values.config.domains -}} + {{- range $i, $domain := .Values.config.domains -}} + {{- $primary := eq $i 0 -}} + {{- $name := required "each config.domains entry requires a 'name'" $domain.name -}} + {{- $base := required (printf "config.domains[%d] (%s) requires a 'base' domain" $i $name) $domain.base -}} + {{- $dns := required (printf "config.domains[%d] (%s) requires a 'dns' map" $i $name) $domain.dns -}} + {{- $tls := $domain.tls | default dict -}} + {{- $issuer := $tls.issuer | default dict -}} + {{- $suffix := ternary "" (printf "-%s" $name) $primary -}} + {{- $section := ternary "https" (printf "https-%s" $name) $primary -}} + {{- $secretName := "" -}} + {{- if $tls.secretName -}}{{- $secretName = $tls.secretName -}} + {{- else if $primary -}}{{- $secretName = include "wire-ingress.certificateSecretName" $root -}} + {{- else -}}{{- $secretName = printf "%s-%s-tls-certificate" $fullname $name -}}{{- end -}} + {{- $cspFlag := true -}} + {{- if hasKey $domain "renderCSP" -}}{{- $cspFlag = $domain.renderCSP -}}{{- end -}} + {{- $entry := dict + "suffix" $suffix + "section" $section + "hostname" ($domain.hostname | default (printf "*.%s" $base)) + "https" (required (printf "config.domains[%d] (%s) requires dns.https" $i $name) $dns.https) + "ssl" ($dns.ssl | default "") + "webapp" ($dns.webapp | default "") + "teamSettings" ($dns.teamSettings | default "") + "accountPages" ($dns.accountPages | default "") + "fakeS3" ($dns.fakeS3 | default "") + "base" $base + "secretName" $secretName + "certName" (printf "%s-csr" ($base | replace "." "-")) + "issuerName" ($issuer.name | default $root.Values.tls.issuer.name) + "issuerKind" ($issuer.kind | default $root.Values.tls.issuer.kind) + "primary" $primary + "csp" (and (not $primary) $cspFlag) -}} + {{- $out = append $out $entry -}} + {{- end -}} +{{- else -}} + {{- $dns := .Values.config.dns -}} + {{- $base := include "wire-ingress.zone" . -}} + {{- $entry := dict + "suffix" "" + "section" "https" + "hostname" .Values.gateway.listeners.https.hostname + "https" (required "config.dns.https is required" $dns.https) + "ssl" ($dns.ssl | default "") + "webapp" ($dns.webapp | default "") + "teamSettings" ($dns.teamSettings | default "") + "accountPages" ($dns.accountPages | default "") + "fakeS3" ($dns.fakeS3 | default "") + "base" $base + "secretName" (include "wire-ingress.certificateSecretName" .) + "certName" (printf "%s-csr" ($base | replace "." "-")) + "issuerName" .Values.tls.issuer.name + "issuerKind" .Values.tls.issuer.kind + "primary" true + "csp" false -}} + {{- $out = append $out $entry -}} +{{- end -}} +{{- $out | toJson -}} +{{- end -}} + +{{/* +Content-Security-Policy header value for an "additional ingress" domain. +This mirrors the approximation the legacy nginx-ingress-services chart injected +for multi-ingress domains (charts/nginx-ingress-services/templates/ingress.yaml), +where the primary domain's frontend apps set CSP themselves but additional +domains need the header set at the front door. + +Call with a dict: {https, ssl, base, websockets (bool)}. +*/}} +{{- define "wire-ingress.cspHeader" -}} +{{- $csp := printf "connect-src 'self' blob: data: https://*.giphy.com https://%s" .https -}} +{{- if and .websockets .ssl -}}{{- $csp = printf "%s wss://%s" $csp .ssl -}}{{- end -}} +{{- $csp = printf "%s https://*.%s;" $csp .base -}} +{{- $csp = printf "%s default-src 'self';" $csp -}} +{{- $csp = printf "%s font-src 'self' data:;" $csp -}} +{{- $csp = printf "%s frame-src https://*.soundcloud.com https://*.spotify.com https://*.vimeo.com https://*.youtube-nocookie.com;" $csp -}} +{{- $csp = printf "%s img-src 'self' blob: data: https://*.giphy.com https://*.%s;" $csp .base -}} +{{- $csp = printf "%s manifest-src 'self';" $csp -}} +{{- $csp = printf "%s media-src 'self' blob: data:;" $csp -}} +{{- $csp = printf "%s object-src 'none';" $csp -}} +{{- $csp = printf "%s script-src 'self' 'unsafe-eval' https://*.%s;" $csp .base -}} +{{- $csp = printf "%s style-src 'self' 'unsafe-inline';" $csp -}} +{{- $csp = printf "%s worker-src 'self' blob:;" $csp -}} +{{- $csp = printf "%s base-uri 'self';" $csp -}} +{{- $csp = printf "%s form-action 'self';" $csp -}} +{{- $csp = printf "%s frame-ancestors 'self';" $csp -}} +{{- $csp = printf "%s script-src-attr 'none';" $csp -}} +{{- $csp = printf "%s upgrade-insecure-requests" $csp -}} +{{- $csp -}} +{{- end -}} diff --git a/charts/wire-ingress/templates/certificate.yaml b/charts/wire-ingress/templates/certificate.yaml index 61ee9cb272..2bb262c7d2 100644 --- a/charts/wire-ingress/templates/certificate.yaml +++ b/charts/wire-ingress/templates/certificate.yaml @@ -1,45 +1,50 @@ {{- if .Values.tls.useCertManager -}} +{{- $root := . -}} +{{- $domains := include "wire-ingress.domains" . | fromJsonArray -}} +{{- range $domain := $domains }} +--- apiVersion: cert-manager.io/v1 kind: Certificate metadata: - name: "{{ include "wire-ingress.zone" . | replace "." "-" }}-csr" - namespace: {{ .Release.Namespace }} + name: "{{ $domain.certName }}" + namespace: {{ $root.Release.Namespace }} labels: - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" + chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + release: "{{ $root.Release.Name }}" + heritage: "{{ $root.Release.Service }}" spec: issuerRef: - name: {{ include "wire-ingress.issuerName" . | quote }} - kind: {{ .Values.tls.issuer.kind }} + name: {{ $domain.issuerName | quote }} + kind: {{ $domain.issuerKind }} usages: - server auth duration: 2160h # 90d, Letsencrypt default; NOTE: changes are ignored by Letsencrypt renewBefore: 360h # 15d isCA: false - secretName: {{ include "wire-ingress.certificateSecretName" . | quote }} + secretName: {{ $domain.secretName | quote }} privateKey: - algorithm: {{ .Values.tls.privateKey.algorithm }} - size: {{ .Values.tls.privateKey.size }} + algorithm: {{ $root.Values.tls.privateKey.algorithm }} + size: {{ $root.Values.tls.privateKey.size }} encoding: PKCS1 - rotationPolicy: {{ .Values.tls.privateKey.rotationPolicy }} + rotationPolicy: {{ $root.Values.tls.privateKey.rotationPolicy }} dnsNames: - - {{ .Values.config.dns.https }} - {{- if .Values.websockets.enabled }} - - {{ .Values.config.dns.ssl }} + - {{ $domain.https }} + {{- if and $root.Values.websockets.enabled $domain.ssl }} + - {{ $domain.ssl }} {{- end }} - {{- if .Values.webapp.enabled }} - - {{ .Values.config.dns.webapp }} + {{- if and $root.Values.webapp.enabled $domain.webapp }} + - {{ $domain.webapp }} {{- end }} - {{- if .Values.fakeS3.enabled }} - - {{ .Values.config.dns.fakeS3 }} + {{- if and $root.Values.fakeS3.enabled $domain.fakeS3 }} + - {{ $domain.fakeS3 }} {{- end }} - {{- if .Values.teamSettings.enabled }} - - {{ .Values.config.dns.teamSettings }} + {{- if and $root.Values.teamSettings.enabled $domain.teamSettings }} + - {{ $domain.teamSettings }} {{- end }} - {{- if .Values.accountPages.enabled }} - - {{ .Values.config.dns.accountPages }} + {{- if and $root.Values.accountPages.enabled $domain.accountPages }} + - {{ $domain.accountPages }} {{- end }} +{{- end }} {{- end -}} diff --git a/charts/wire-ingress/templates/gateway.yaml b/charts/wire-ingress/templates/gateway.yaml index 16b3f7bb72..b547aae3e6 100644 --- a/charts/wire-ingress/templates/gateway.yaml +++ b/charts/wire-ingress/templates/gateway.yaml @@ -33,15 +33,18 @@ spec: {{- end }} {{- end }} listeners: - - name: https - port: {{ .Values.gateway.listeners.https.port }} + {{- $domains := include "wire-ingress.domains" . | fromJsonArray }} + {{- range $domain := $domains }} + - name: {{ $domain.section }} + port: {{ $.Values.gateway.listeners.https.port }} protocol: HTTPS - hostname: {{ required "gateway.listeners.https.hostname is required (see values.yaml for details)" .Values.gateway.listeners.https.hostname | quote }} + 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: {{ include "wire-ingress.certificateSecretName" . | quote }} + - name: {{ $domain.secretName | quote }} kind: Secret + {{- end }} {{- if .Values.federator.enabled }} - name: federator port: {{ .Values.gateway.listeners.https.port }} diff --git a/charts/wire-ingress/templates/httproute-account-pages.yaml b/charts/wire-ingress/templates/httproute-account-pages.yaml index c3ef24a374..6774da0ee7 100644 --- a/charts/wire-ingress/templates/httproute-account-pages.yaml +++ b/charts/wire-ingress/templates/httproute-account-pages.yaml @@ -1,28 +1,43 @@ {{- if .Values.accountPages.enabled }} +{{- $root := . -}} +{{- $domains := include "wire-ingress.domains" . | fromJsonArray -}} +{{- range $domain := $domains }} +{{- if $domain.accountPages }} +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "wire-ingress.fullname" . }}-account-pages - namespace: {{ .Release.Namespace }} + name: {{ include "wire-ingress.fullname" $root }}-account-pages{{ $domain.suffix }} + namespace: {{ $root.Release.Namespace }} labels: - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" + chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + release: "{{ $root.Release.Name }}" + heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} + - name: {{ include "wire-ingress.gatewayName" $root | quote }} + namespace: {{ $root.Release.Namespace | quote }} kind: Gateway - sectionName: https + sectionName: {{ $domain.section }} hostnames: - - {{ required "config.dns.accountPages is required when accountPages.enabled is true" .Values.config.dns.accountPages | quote }} + - {{ required "config.dns.accountPages is required when accountPages.enabled is true" $domain.accountPages | quote }} rules: - matches: - path: type: PathPrefix value: / + {{- if $domain.csp }} + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: Content-Security-Policy + value: {{ include "wire-ingress.cspHeader" (dict "https" $domain.https "ssl" $domain.ssl "base" $domain.base "websockets" $root.Values.websockets.enabled) | quote }} + {{- end }} backendRefs: - name: account-pages-http - port: {{ .Values.service.accountPages.externalPort }} + port: {{ $root.Values.service.accountPages.externalPort }} kind: Service {{- end }} +{{- end }} +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-nginz-websockets.yaml b/charts/wire-ingress/templates/httproute-nginz-websockets.yaml index 5d9881d98d..241c59de4d 100644 --- a/charts/wire-ingress/templates/httproute-nginz-websockets.yaml +++ b/charts/wire-ingress/templates/httproute-nginz-websockets.yaml @@ -1,21 +1,26 @@ {{- if .Values.websockets.enabled }} +{{- $root := . -}} +{{- $domains := include "wire-ingress.domains" . | fromJsonArray -}} +{{- range $domain := $domains }} +{{- if $domain.ssl }} +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "wire-ingress.fullname" . }}-nginz-websockets - namespace: {{ .Release.Namespace }} + name: {{ include "wire-ingress.fullname" $root }}-nginz-websockets{{ $domain.suffix }} + namespace: {{ $root.Release.Namespace }} labels: - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" + chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + release: "{{ $root.Release.Name }}" + heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} + - name: {{ include "wire-ingress.gatewayName" $root | quote }} + namespace: {{ $root.Release.Namespace | quote }} kind: Gateway - sectionName: https + sectionName: {{ $domain.section }} hostnames: - - {{ required "config.dns.ssl is required when websockets.enabled is true" .Values.config.dns.ssl | quote }} + - {{ required "config.dns.ssl is required when websockets.enabled is true" $domain.ssl | quote }} rules: - matches: - path: @@ -23,6 +28,8 @@ spec: value: / backendRefs: - name: nginz - port: {{ .Values.service.nginz.wsPort }} + port: {{ $root.Values.service.nginz.wsPort }} kind: Service {{- end }} +{{- end }} +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-nginz.yaml b/charts/wire-ingress/templates/httproute-nginz.yaml index 7bc50bb289..5f3a50f287 100644 --- a/charts/wire-ingress/templates/httproute-nginz.yaml +++ b/charts/wire-ingress/templates/httproute-nginz.yaml @@ -1,20 +1,24 @@ +{{- $root := . -}} +{{- $domains := include "wire-ingress.domains" . | fromJsonArray -}} +{{- range $domain := $domains }} +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "wire-ingress.fullname" . }}-nginz - namespace: {{ .Release.Namespace }} + name: {{ include "wire-ingress.fullname" $root }}-nginz{{ $domain.suffix }} + namespace: {{ $root.Release.Namespace }} labels: - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" + chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + release: "{{ $root.Release.Name }}" + heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} + - name: {{ include "wire-ingress.gatewayName" $root | quote }} + namespace: {{ $root.Release.Namespace | quote }} kind: Gateway - sectionName: https + sectionName: {{ $domain.section }} hostnames: - - {{ required "config.dns.https is required" .Values.config.dns.https | quote }} + - {{ required "config.dns.https is required" $domain.https | quote }} rules: - matches: - path: @@ -22,5 +26,6 @@ spec: value: / backendRefs: - name: nginz - port: {{ .Values.service.nginz.httpPort }} + port: {{ $root.Values.service.nginz.httpPort }} kind: Service +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-s3.yaml b/charts/wire-ingress/templates/httproute-s3.yaml index afb68412d7..302b722b0e 100644 --- a/charts/wire-ingress/templates/httproute-s3.yaml +++ b/charts/wire-ingress/templates/httproute-s3.yaml @@ -1,30 +1,37 @@ {{- if .Values.fakeS3.enabled }} +{{- $root := . -}} +{{- $domains := include "wire-ingress.domains" . | fromJsonArray -}} +{{- range $domain := $domains }} +{{- if $domain.fakeS3 }} +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "wire-ingress.fullname" . }}-minio - namespace: {{ .Release.Namespace }} + name: {{ include "wire-ingress.fullname" $root }}-minio{{ $domain.suffix }} + namespace: {{ $root.Release.Namespace }} labels: - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" + chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + release: "{{ $root.Release.Name }}" + heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} + - name: {{ include "wire-ingress.gatewayName" $root | quote }} + namespace: {{ $root.Release.Namespace | quote }} kind: Gateway - sectionName: https + sectionName: {{ $domain.section }} hostnames: - - {{ required "config.dns.fakeS3 is required when fakeS3.enabled is true" .Values.config.dns.fakeS3 | quote }} + - {{ required "config.dns.fakeS3 is required when fakeS3.enabled is true" $domain.fakeS3 | quote }} rules: - {{- toYaml .Values.fakeS3.guardingRules | nindent 4 }} + {{- toYaml $root.Values.fakeS3.guardingRules | nindent 4 }} {{/* Default catch-all rule routes to the S3 backend */}} - matches: - path: type: PathPrefix value: / backendRefs: - - name: {{ .Values.service.s3.serviceName }} - port: {{ .Values.service.s3.externalPort }} + - name: {{ $root.Values.service.s3.serviceName }} + port: {{ $root.Values.service.s3.externalPort }} kind: Service {{- end }} +{{- end }} +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-team-settings.yaml b/charts/wire-ingress/templates/httproute-team-settings.yaml index 2de4cc0ea4..c5ff374b0e 100644 --- a/charts/wire-ingress/templates/httproute-team-settings.yaml +++ b/charts/wire-ingress/templates/httproute-team-settings.yaml @@ -1,28 +1,43 @@ {{- if .Values.teamSettings.enabled }} +{{- $root := . -}} +{{- $domains := include "wire-ingress.domains" . | fromJsonArray -}} +{{- range $domain := $domains }} +{{- if $domain.teamSettings }} +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "wire-ingress.fullname" . }}-team-settings - namespace: {{ .Release.Namespace }} + name: {{ include "wire-ingress.fullname" $root }}-team-settings{{ $domain.suffix }} + namespace: {{ $root.Release.Namespace }} labels: - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" + chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + release: "{{ $root.Release.Name }}" + heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} + - name: {{ include "wire-ingress.gatewayName" $root | quote }} + namespace: {{ $root.Release.Namespace | quote }} kind: Gateway - sectionName: https + sectionName: {{ $domain.section }} hostnames: - - {{ required "config.dns.teamSettings is required when teamSettings.enabled is true" .Values.config.dns.teamSettings | quote }} + - {{ required "config.dns.teamSettings is required when teamSettings.enabled is true" $domain.teamSettings | quote }} rules: - matches: - path: type: PathPrefix value: / + {{- if $domain.csp }} + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: Content-Security-Policy + value: {{ include "wire-ingress.cspHeader" (dict "https" $domain.https "ssl" $domain.ssl "base" $domain.base "websockets" $root.Values.websockets.enabled) | quote }} + {{- end }} backendRefs: - name: team-settings-http - port: {{ .Values.service.teamSettings.externalPort }} + port: {{ $root.Values.service.teamSettings.externalPort }} kind: Service {{- end }} +{{- end }} +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-webapp.yaml b/charts/wire-ingress/templates/httproute-webapp.yaml index 158836040d..bfde5ec114 100644 --- a/charts/wire-ingress/templates/httproute-webapp.yaml +++ b/charts/wire-ingress/templates/httproute-webapp.yaml @@ -1,28 +1,43 @@ {{- if .Values.webapp.enabled }} +{{- $root := . -}} +{{- $domains := include "wire-ingress.domains" . | fromJsonArray -}} +{{- range $domain := $domains }} +{{- if $domain.webapp }} +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: - name: {{ include "wire-ingress.fullname" . }}-webapp - namespace: {{ .Release.Namespace }} + name: {{ include "wire-ingress.fullname" $root }}-webapp{{ $domain.suffix }} + namespace: {{ $root.Release.Namespace }} labels: - chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - release: "{{ .Release.Name }}" - heritage: "{{ .Release.Service }}" + chart: "{{ $root.Chart.Name }}-{{ $root.Chart.Version }}" + release: "{{ $root.Release.Name }}" + heritage: "{{ $root.Release.Service }}" spec: parentRefs: - - name: {{ include "wire-ingress.gatewayName" . | quote }} - namespace: {{ .Release.Namespace | quote }} + - name: {{ include "wire-ingress.gatewayName" $root | quote }} + namespace: {{ $root.Release.Namespace | quote }} kind: Gateway - sectionName: https + sectionName: {{ $domain.section }} hostnames: - - {{ required "config.dns.webapp is required when webapp.enabled is true" .Values.config.dns.webapp | quote }} + - {{ required "config.dns.webapp is required when webapp.enabled is true" $domain.webapp | quote }} rules: - matches: - path: type: PathPrefix value: / + {{- if $domain.csp }} + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: Content-Security-Policy + value: {{ include "wire-ingress.cspHeader" (dict "https" $domain.https "ssl" $domain.ssl "base" $domain.base "websockets" $root.Values.websockets.enabled) | quote }} + {{- end }} backendRefs: - name: webapp-http - port: {{ .Values.service.webapp.externalPort }} + port: {{ $root.Values.service.webapp.externalPort }} kind: Service {{- end }} +{{- end }} +{{- end }} diff --git a/charts/wire-ingress/values.yaml b/charts/wire-ingress/values.yaml index 0e6e901d26..e7e875f613 100644 --- a/charts/wire-ingress/values.yaml +++ b/charts/wire-ingress/values.yaml @@ -113,6 +113,42 @@ gateway: # certificateDomain: federator. # domain to use in the federator CSR # teamSettings: teams. # ignored unless teamSettings.enabled == true # accountPages: account. # ignored unless accountPages.enabled == true +# +# MULTI-INGRESS (multiple backend domains from one release) +# --------------------------------------------------------- +# To serve the same backend on several domains, set config.domains INSTEAD of +# config.dns. It is a list; the FIRST entry is the primary (its resources keep +# the un-suffixed names, and its frontend apps set their own CSP). Every +# additional entry gets its own Gateway HTTPS listener (`https-`), its own +# certificate/secret, and — being an "additional ingress" — a per-domain +# Content-Security-Policy header injected on the webapp/team-settings/ +# account-pages routes (mirrors the legacy nginx-ingress-services behaviour). +# +# config.dns and config.domains are mutually exclusive; config.domains wins. +# federator is single-domain and always attaches to the primary domain. +# +# config: +# domains: +# - name: blueberry # required; used for resource-name suffix & listener section +# base: blueberry.example.com # required; CSP wildcard (*.base) and default listener hostname (*.base) +# # hostname: "*.blueberry.example.com" # optional listener hostname override (defaults to *.base) +# dns: +# https: nginz-https.blueberry.example.com +# ssl: nginz-ssl.blueberry.example.com +# webapp: webapp.blueberry.example.com +# # teamSettings / accountPages / fakeS3 as needed +# - name: red +# base: red.example.org +# dns: +# https: nginz-https.red.example.org +# ssl: nginz-ssl.red.example.org +# webapp: webapp.red.example.org +# # renderCSP: false # optional: disable the injected CSP for this domain +# tls: +# # secretName: "" # optional TLS secret name override (defaults to a per-domain name) +# issuer: # optional per-domain cert-manager issuer override (defaults to tls.issuer) +# name: letsencrypt-red +# kind: ClusterIssuer websockets: enabled: true