Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2-features/WPB-25475
Original file line number Diff line number Diff line change
@@ -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.
29 changes: 23 additions & 6 deletions charts/wire-ingress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down Expand Up @@ -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-<name>`, 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

Expand Down
113 changes: 113 additions & 0 deletions charts/wire-ingress/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 `-<name>` suffix, its
own Gateway listener (`https-<name>`), 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 -}}
49 changes: 27 additions & 22 deletions charts/wire-ingress/templates/certificate.yaml
Original file line number Diff line number Diff line change
@@ -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 -}}
11 changes: 7 additions & 4 deletions charts/wire-ingress/templates/gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
35 changes: 25 additions & 10 deletions charts/wire-ingress/templates/httproute-account-pages.yaml
Original file line number Diff line number Diff line change
@@ -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 }}
27 changes: 17 additions & 10 deletions charts/wire-ingress/templates/httproute-nginz-websockets.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
{{- 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:
type: PathPrefix
value: /
backendRefs:
- name: nginz
port: {{ .Values.service.nginz.wsPort }}
port: {{ $root.Values.service.nginz.wsPort }}
kind: Service
{{- end }}
{{- end }}
{{- end }}
Loading