diff --git a/Makefile b/Makefile index e366edbb91e..6ecf57183ef 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ DOCKER_TAG ?= $(USER) # default helm chart version must be 0.0.42 for local development (because 42 is the answer to the universe and everything) HELM_SEMVER ?= 0.0.42 # The list of helm charts needed on internal kubernetes testing environments -CHARTS_INTEGRATION := wire-server databases-ephemeral rabbitmq fake-aws ingress-nginx-controller nginx-ingress-services fluent-bit kibana k8ssandra-test-cluster wire-server-enterprise +CHARTS_INTEGRATION := wire-server databases-ephemeral rabbitmq fake-aws ingress-nginx-controller nginx-ingress-services wire-ingress fluent-bit kibana k8ssandra-test-cluster wire-server-enterprise # The list of helm charts to publish on S3 # FUTUREWORK: after we "inline local subcharts", # (e.g. move charts/brig to charts/wire-server/brig) @@ -18,7 +18,8 @@ fake-aws fake-aws-s3 fake-aws-sqs aws-ingress fluent-bit kibana backoffice \ calling-test demo-smtp elasticsearch-curator elasticsearch-external \ elasticsearch-ephemeral minio-external cassandra-external \ ingress-nginx-controller nginx-ingress-services reaper \ -k8ssandra-test-cluster ldap-scim-bridge wire-server-enterprise +k8ssandra-test-cluster ldap-scim-bridge wire-server-enterprise \ +wire-ingress KIND_CLUSTER_NAME := wire-server HELM_PARALLELISM ?= 1 # 1 for sequential tests; 6 for all-parallel tests PSQL_DB ?= backendA diff --git a/changelog.d/5-internal/WPB-23903 b/changelog.d/5-internal/WPB-23903 new file mode 100644 index 00000000000..c7a1e11ef4f --- /dev/null +++ b/changelog.d/5-internal/WPB-23903 @@ -0,0 +1 @@ +New `wire-ingress` Helm chart — Gateway API / Envoy Gateway replacement for `nginx-ingress-services`. Not yet production-ready. diff --git a/charts/backoffice/templates/tests/configmap.yaml b/charts/backoffice/templates/tests/configmap.yaml index a20785b354e..b4bff0a2e03 100644 --- a/charts/backoffice/templates/tests/configmap.yaml +++ b/charts/backoffice/templates/tests/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: "stern-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation data: integration.yaml: | diff --git a/charts/federator/templates/tests/configmap.yaml b/charts/federator/templates/tests/configmap.yaml index 44146840bd4..4612ab2a3c5 100644 --- a/charts/federator/templates/tests/configmap.yaml +++ b/charts/federator/templates/tests/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: "federator-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation data: integration.yaml: | @@ -23,6 +23,6 @@ data: host: cargohold port: 8080 nginxIngress: - host: federation-test-helper.{{ .Release.Namespace }}.svc.cluster.local + host: {{ .Values.tests.nginxIngressHost }} port: 443 - originDomain: federation-test-helper.{{ .Release.Namespace }}.svc.cluster.local + originDomain: {{ .Values.tests.nginxIngressHost }} diff --git a/charts/federator/values.yaml b/charts/federator/values.yaml index 6632fb21429..c0285e52d8c 100644 --- a/charts/federator/values.yaml +++ b/charts/federator/values.yaml @@ -57,6 +57,10 @@ podSecurityContext: type: RuntimeDefault tests: + # The host used for the nginxIngress endpoint and originDomain in the integration + # test config. Depends on the release name of the "wire-ingress" helm chart + # (see federation-test-helper.yaml in that chart). + nginxIngressHost: "set-me" config: {} # config: # uploadXml: diff --git a/charts/integration/templates/_helpers.tpl b/charts/integration/templates/_helpers.tpl index e3a33787bf9..1c0bd85ad52 100644 --- a/charts/integration/templates/_helpers.tpl +++ b/charts/integration/templates/_helpers.tpl @@ -1,4 +1,49 @@ +{{/* +Name of the Gateway resource for dynamic backends in envoy mode. +*/}} +{{- define "integration.getDynBackendsGatewayName" -}} +{{- if .Values.envoy.gateway.name -}} +{{ .Values.envoy.gateway.name }} +{{- else -}} +{{ .Release.Name }}-dynamic-backends +{{- end -}} +{{- end -}} + +{{/* +Federation origin domain for a given namespace (used as originDomain in the config). +Returns the SRV hostname that other backends use to reach this namespace's federator. +NOTE: Keep the naming assumption %s-fed in sync with the wire-ingress and nginx-ingress-services chart! +Args: list $namespace $envoyEnabled $controllerNamespace +*/}} +{{- define "integration.federationOriginDomain" -}} +{{- $namespace := index . 0 -}} +{{- $envoyEnabled := index . 1 -}} +{{- $controllerNs := index . 2 -}} +{{- if $envoyEnabled -}} +{{- printf "%s-fed.%s.svc.cluster.local" $namespace $controllerNs -}} +{{- else -}} +{{- printf "federation-test-helper.%s.svc.cluster.local" $namespace -}} +{{- end -}} +{{- end -}} + +{{/* +Domain for a dynamic backend. Returns the correct hostname depending on whether +envoy mode is enabled. +Args: list $dynamicBackend $namespace $envoyEnabled $controllerNamespace +*/}} +{{- define "integration.dynamicBackendDomain" -}} +{{- $dynamicBackend := index . 0 -}} +{{- $namespace := index . 1 -}} +{{- $envoyEnabled := index . 2 -}} +{{- $controllerNs := index . 3 -}} +{{- if $envoyEnabled -}} +{{- printf "%s-%s.%s.svc.cluster.local" $dynamicBackend.federatorExternalHostPrefix $namespace $controllerNs -}} +{{- else -}} +{{- printf "%s.%s.svc.cluster.local" $dynamicBackend.federatorExternalHostPrefix $namespace -}} +{{- end -}} +{{- end -}} + {{/* Allow KubeVersion to be overridden. */}} {{- define "kubeVersion" -}} {{- default $.Capabilities.KubeVersion.Version $.Values.kubeVersionOverride -}} diff --git a/charts/integration/templates/configmap.yaml b/charts/integration/templates/configmap.yaml index 82fc9895284..b5f2d351732 100644 --- a/charts/integration/templates/configmap.yaml +++ b/charts/integration/templates/configmap.yaml @@ -77,7 +77,7 @@ data: apiPort: 5380 dohPort: 5381 - originDomain: federation-test-helper.{{ .Release.Namespace }}.svc.cluster.local + originDomain: {{ include "integration.federationOriginDomain" (list .Release.Namespace .Values.envoy.enabled .Values.envoy.controllerNamespace) }} rabbitmq: host: rabbitmq @@ -158,12 +158,12 @@ data: rabbitMqVHost: / - originDomain: federation-test-helper.{{ .Release.Namespace }}-fed2.svc.cluster.local + originDomain: {{ include "integration.federationOriginDomain" (list (printf "%s-fed2" .Release.Namespace) .Values.envoy.enabled .Values.envoy.controllerNamespace) }} dynamicBackends: {{- range $name, $dynamicBackend := .Values.config.dynamicBackends }} {{ $name }}: - domain: {{ $dynamicBackend.federatorExternalHostPrefix }}.{{ $.Release.Namespace }}.svc.cluster.local + domain: {{ include "integration.dynamicBackendDomain" (list $dynamicBackend $.Release.Namespace $.Values.envoy.enabled $.Values.envoy.controllerNamespace) }} federatorExternalPort: {{ $dynamicBackend.federatorExternalPort }} mlsPrivateKeyPaths: removal: diff --git a/charts/integration/templates/envoy-gateway.yaml b/charts/integration/templates/envoy-gateway.yaml new file mode 100644 index 00000000000..98ddae3617e --- /dev/null +++ b/charts/integration/templates/envoy-gateway.yaml @@ -0,0 +1,161 @@ +{{- if .Values.envoy.enabled }} +{{- $gatewayName := include "integration.getDynBackendsGatewayName" . }} +{{- $httpsPort := int .Values.envoy.gateway.listeners.https.port }} +{{- $controllerNs := .Values.envoy.controllerNamespace }} +{{- if lt $httpsPort 1024 }} +{{- fail (printf "envoy.gateway.listeners.https.port is %d (privileged, <1024). Envoy Gateway remaps it to %d on the proxy pod. Set envoy.gateway.listeners.https.port to the actual container port (e.g. %d)." $httpsPort (add $httpsPort 10000) (add $httpsPort 10000)) }} +{{- end }} +--- +# EnvoyProxy configures the proxy deployment/service for the dynamic-backends Gateway. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: {{ $gatewayName }} +spec: + provider: + type: Kubernetes + kubernetes: + envoyService: + # ClusterIP: no external load balancer needed for in-cluster integration tests. + type: ClusterIP +--- +# Gateway for all dynamic backends. A single HTTPS listener covers all backend hostnames. +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ $gatewayName }} +spec: + gatewayClassName: {{ required "envoy.gateway.className is required when envoy.enabled is true" .Values.envoy.gateway.className | quote }} + infrastructure: + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: {{ $gatewayName | quote }} + listeners: + - name: https + port: {{ $httpsPort }} + protocol: HTTPS + tls: + mode: Terminate + certificateRefs: + - name: {{ .Values.envoy.federator.tls.secretName | quote }} + kind: Secret +--- +# ClientTrafficPolicy enforces optional mTLS client cert validation on all dynamic-backend +# connections (mirrors the nginx auth-tls-verify-client: "on" annotation). +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: {{ $gatewayName }}-mtls +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: {{ $gatewayName | quote }} + sectionName: https + tls: + clientValidation: + optional: true + caCertificateRefs: + - name: federator-ca + kind: ConfigMap +--- +{{- $backendNames := keys .Values.config.dynamicBackends | sortAlpha }} +{{- range $index, $name := $backendNames }} +{{- $dynamicBackend := index $.Values.config.dynamicBackends $name }} +{{- $httpRouteName := printf "%s-dynbackend-%s" $gatewayName $name }} +{{- $svcDomain := printf "%s-%s.%s.svc.cluster.local" $dynamicBackend.federatorExternalHostPrefix $.Release.Namespace $controllerNs }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $httpRouteName }} +spec: + parentRefs: + - name: {{ $gatewayName | quote }} + namespace: {{ $.Release.Namespace | quote }} + kind: Gateway + sectionName: https + hostnames: + - {{ $svcDomain | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: integration + port: {{ $dynamicBackend.federatorExternalPort }} + kind: Service +--- +# EnvoyExtensionPolicy injects the mTLS client certificate as X-SSL-Certificate request +# header, matching the nginx $ssl_client_escaped_cert behaviour expected by federator. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy +metadata: + name: {{ $httpRouteName }}-cert-header +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: {{ $httpRouteName }} + lua: + - type: Inline + inline: | + function envoy_on_request(request_handle) + {{/* Strip any client-provided header to prevent spoofing */}} + request_handle:headers():remove("X-SSL-Certificate") + local ssl = request_handle:connection():ssl() + if ssl ~= nil then + local cert = ssl:urlEncodedPemEncodedPeerCertificate() + if cert ~= nil and cert ~= "" then + request_handle:headers():add("X-SSL-Certificate", cert) + end + end + end +--- +# EnvoyPatchPolicy adds the FQDN variant (with trailing dot) of the backend domain +# to the virtual host's domain list. Wire federator resolves targets via DNS SRV records; +# per RFC 2782, SRV record targets are FQDNs (e.g. "backend-fed.ns.svc.cluster.local."). +# HTTP/2 passes that dot in :authority; without this patch the virtual host only matches +# the bare domain and returns route_not_found. Adding the FQDN allows Envoy to match both. +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyPatchPolicy +metadata: + name: {{ $httpRouteName }}-fqdn-domain +spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: {{ $gatewayName | quote }} + type: JSONPatch + jsonPatches: + - type: "type.googleapis.com/envoy.config.route.v3.RouteConfiguration" + # RouteConfiguration is per-listener, named // + name: {{ printf "%s/%s/https" $.Release.Namespace $gatewayName | quote }} + operation: + op: add + # Virtual hosts are indexed in the order of stable key sorting (sortAlpha). + path: {{ printf "/virtual_hosts/%d/domains/-" $index | quote }} + value: {{ printf "%s." $svcDomain | quote }} +--- +# ClusterIP service in {{ $controllerNs }} selects the Envoy proxy pods for this Gateway. +# The service name determines the SRV record used by federation discovery: +# _wire-server-federator._tcp.{{ $svcDomain }} +apiVersion: v1 +kind: Service +metadata: + name: {{ $dynamicBackend.federatorExternalHostPrefix }}-{{ $.Release.Namespace }} + namespace: {{ $controllerNs }} +spec: + type: ClusterIP + ports: + - name: wire-server-federator + port: 443 + protocol: TCP + targetPort: {{ $httpsPort }} + selector: + gateway.envoyproxy.io/owning-gateway-name: {{ $gatewayName }} + gateway.envoyproxy.io/owning-gateway-namespace: {{ $.Release.Namespace }} +{{- end }} +{{- end }} diff --git a/charts/integration/templates/ingress.yaml b/charts/integration/templates/ingress-nginx.yaml similarity index 70% rename from charts/integration/templates/ingress.yaml rename to charts/integration/templates/ingress-nginx.yaml index 362b7b0d8f9..81fc91013a1 100644 --- a/charts/integration/templates/ingress.yaml +++ b/charts/integration/templates/ingress-nginx.yaml @@ -1,3 +1,5 @@ +{{- if not .Values.envoy.enabled }} +{{- $newLabels := eq (include "integrationTestHelperNewLabels" .) "true" -}} {{- range $name, $dynamicBackend := .Values.config.dynamicBackends }} --- apiVersion: networking.k8s.io/v1 @@ -29,4 +31,25 @@ spec: name: integration port: number: {{ $dynamicBackend.federatorExternalPort }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ $dynamicBackend.federatorExternalHostPrefix }} +spec: + ports: + - name: wire-server-federator + port: 443 + protocol: TCP + targetPort: https + selector: + {{- if $newLabels }} + app.kubernetes.io/component: controller + app.kubernetes.io/name: ingress-nginx + {{- else }} + app: nginx-ingress + component: controller + {{- end }} + type: ClusterIP +{{- end }} {{- end }} diff --git a/charts/integration/templates/integration-integration.yaml b/charts/integration/templates/integration-integration.yaml index 7ebad70c179..bf4f676fabd 100644 --- a/charts/integration/templates/integration-integration.yaml +++ b/charts/integration/templates/integration-integration.yaml @@ -168,7 +168,7 @@ spec: integration-dynamic-backends-ses.sh {{ .Values.config.sesEndpointUrl }} integration-dynamic-backends-s3.sh {{ .Values.config.s3EndpointUrl }} {{- range $name, $dynamicBackend := .Values.config.dynamicBackends }} - integration-dynamic-backends-vhosts.sh {{ $.Values.config.rabbitmqPutVHostUrl }} {{ $dynamicBackend.federatorExternalHostPrefix}}.{{ $.Release.Namespace }}.svc.cluster.local + integration-dynamic-backends-vhosts.sh {{ $.Values.config.rabbitmqPutVHostUrl }} {{ include "integration.dynamicBackendDomain" (list $dynamicBackend $.Release.Namespace $.Values.envoy.enabled $.Values.envoy.controllerNamespace) }} {{- end }} resources: requests: diff --git a/charts/integration/templates/service.yaml b/charts/integration/templates/service.yaml index 350b33f11f7..a97d1e58c8e 100644 --- a/charts/integration/templates/service.yaml +++ b/charts/integration/templates/service.yaml @@ -1,4 +1,3 @@ -{{- $newLabels := eq (include "integrationTestHelperNewLabels" .) "true" -}} --- apiVersion: v1 kind: Service @@ -26,26 +25,3 @@ spec: selector: app: integration-integration type: ClusterIP - -{{- range $name, $dynamicBackend := .Values.config.dynamicBackends }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ $dynamicBackend.federatorExternalHostPrefix }} -spec: - ports: - - name: wire-server-federator - port: 443 - protocol: TCP - targetPort: https - selector: - {{- if $newLabels }} - app.kubernetes.io/component: controller - app.kubernetes.io/name: ingress-nginx - {{- else }} - app: nginx-ingress - component: controller - {{- end }} - type: ClusterIP -{{- end }} diff --git a/charts/integration/values.yaml b/charts/integration/values.yaml index 36305b2be75..65c7963b0d8 100644 --- a/charts/integration/values.yaml +++ b/charts/integration/values.yaml @@ -129,4 +129,27 @@ tls: ingress: class: nginx +envoy: + # Set to true to deploy Gateway API resources instead of nginx Ingress objects + # for the dynamic backends. Requires an Envoy Gateway controller in the cluster. + enabled: false + # Namespace where the Envoy Gateway controller runs its proxy pods. + # Change only if you installed Envoy Gateway into a non-default namespace. + controllerNamespace: envoy-gateway-system + gateway: + # Name of the Gateway resource. Defaults to -dynamic-backends if empty. + name: "" + # Name of the GatewayClass installed by the Envoy Gateway controller (e.g. "envoy"). + className: "" + listeners: + https: + # Use a non-privileged port (>=1024) to avoid the +10000 container-port + # remapping applied by Envoy Gateway to privileged ports. + port: 10443 + federator: + tls: + # Name of the TLS Secret presented by the Gateway for the dynamic-backend + # listeners. Must exist before deploying (created by the wire-ingress chart). + secretName: "federator-certificate-secret" + secrets: {} diff --git a/charts/wire-ingress/.helmignore b/charts/wire-ingress/.helmignore new file mode 100644 index 00000000000..f0c13194444 --- /dev/null +++ b/charts/wire-ingress/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/wire-ingress/Chart.yaml b/charts/wire-ingress/Chart.yaml new file mode 100644 index 00000000000..fabe75062d3 --- /dev/null +++ b/charts/wire-ingress/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Wire server ingress using the Kubernetes Gateway API +name: wire-ingress +version: 0.1.0 diff --git a/charts/wire-ingress/README.md b/charts/wire-ingress/README.md new file mode 100644 index 00000000000..c4ec274c6e4 --- /dev/null +++ b/charts/wire-ingress/README.md @@ -0,0 +1,295 @@ +# wire-ingress + +A Helm chart for Wire server ingress using the **Kubernetes Gateway API**. + +The chart targets **Envoy Gateway** as the Gateway API controller. + +--- + +## Status + +**This chart is in development. Don't use it in production yet!** + +--- + +## Prerequisites + +### Gateway API + +Install the [Gateway API](https://gateway-api.sigs.k8s.io/) into your cluster. +This chart makes use of the kinds defined in the `gateway.networking.k8s.io/v1` API. + +### Envoy Gateway + +[Envoy Gateway](https://gateway.envoyproxy.io/) must be installed in the cluster before deploying +this chart. The `EnvoyPatchPolicy` extension API must be enabled (required for federation — see +[EnvoyPatchPolicy](#envoypatchpolicy)): + +```yaml +config: + envoyGateway: + extensionApis: + enableEnvoyPatchPolicy: true +``` + +Also make sure you've created a `GatewayClass` object with +``` +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +``` + +You need to refer to this object in the `gateway.className` parameter. + +--- + +## Backwards compatibility + + +### Migrating from the `nginx-ingress-services` chart + +The chart preserves the `values.yaml` structure of the `nginx-ingress-services` chart wherever +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.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`. +- `gateway.proxyProtocol.enabled` — set to `true` if your load balancer sends PROXY protocol headers. +- `gateway.patchPolicies.targetGatewayClass` — depends on your setup; see [EnvoyPatchPolicy](#envoypatchpolicy). +- `gateway.envoyProxy.create` and `gateway.manageServiceType` — depend on your setup; see the parameter table below. + +`secrets.tlsClientCA` is no longer needed and can be removed. + +### Behavior changes + +* non-tls ingress disabled by default. If you want to make use of automated certificate validation via http01, you need `gateway.listeners.http.enabled: true` +* s3 ingress `/minio/` path blocking. Returns 301 redirect to "/" (was 403). + +### New values (no equivalent in nginx-ingress-services) + +Only values that require explanation are listed. Trivial or self-explanatory values (ports, +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.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.listeners.https.hostname` | `""` | **Required when `federator.enabled: true`.** Restricts the HTTPS listener to a specific hostname (e.g. `*.example.com`). Without this, both the HTTPS and federator listeners are catch-all on the same port, causing Envoy to degrade ALPN to HTTP/1.1-only (`OverlappingTLSConfig`). | +| `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.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.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. | +| `tls.secret.create` | `true` | If `false`, the TLS Secret is not created by this chart. Use when the secret is managed externally (e.g. by another operator). | +| `federator.tls.useCertManager` | `true` | Controls cert-manager for the federator TLS secret independently of `tls.useCertManager`. Requires a private CA — see [Federator TLS certificate](#federator-tls-certificate-federatortlsusecertmanager). | + +### Dropped values + +| 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 | +| `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. | +| `secrets.certManager.customSolversSecret` | No longer supported. Create a custom Issuer instead. | + +### Fully backwards compatible values + +All keys below are accepted unchanged. Their names, types, and semantics are identical to +`nginx-ingress-services`. + +| Key | +|---| +| `nameOverride` | +| `teamSettings.enabled` | +| `accountPages.enabled` | +| `websockets.enabled` | +| `webapp.enabled` | +| `fakeS3.enabled` | +| `federator.enabled` | +| `federator.integrationTestHelper` | +| `federator.tls.duration` | +| `federator.tls.renewBefore` | +| `federator.tls.privateKey.rotationPolicy` | +| `federator.tls.issuer.name` | +| `federator.tls.issuer.kind` | +| `federator.tls.issuer.group` | +| `tls.useCertManager` | +| `tls.createIssuer` | +| `tls.privateKey.rotationPolicy` | +| `tls.privateKey.algorithm` | +| `tls.privateKey.size` | +| `tls.issuer.name` | +| `tls.issuer.kind` | +| `tls.caNamespace` | +| `certManager.inTestMode` | +| `certManager.certmasterEmail` | +| `certManager.customSolvers` | +| `service.webapp.externalPort` | +| `service.s3.externalPort` | +| `service.s3.serviceName` | +| `service.useFakeS3` | +| `service.teamSettings.externalPort` | +| `service.accountPages.externalPort` | +| `config.dns.https` | +| `config.dns.ssl` | +| `config.dns.webapp` | +| `config.dns.fakeS3` | +| `config.dns.federator` | +| `config.dns.certificateDomain` | +| `config.dns.teamSettings` | +| `config.dns.accountPages` | +| `secrets.tlsWildcardCert` | +| `secrets.tlsWildcardKey` | + + +## Design decisions + +### Gateway API controller: Envoy Gateway + +The chart targets [Envoy Gateway](https://gateway.envoyproxy.io/). Implementation-specific +resources (`ClientTrafficPolicy`, `SecurityPolicy`, `HTTPRouteFilter` with `directResponse`) are +used where the standard Gateway API has gaps. These resources are clearly marked in each template. + +### 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 default values create the Gateway. The default `gateway.name` is derived from the release name, +so that self-referencing is consistent by default. + +### EnvoyProxy resource + +The chart creates an `EnvoyProxy` resource (when `gateway.envoyProxy.create: true`) and wires it +to the `Gateway` via `infrastructure.parametersRef`. Use `gateway.envoyProxy.spec` to pass +arbitrary fields from the [EnvoyProxySpec](https://gateway.envoyproxy.io/docs/api/extension_types/#envoyproxyspec). + +Set `gateway.envoyProxy.create: false` when a shared `EnvoyProxy` is managed at the +`GatewayClass` level (e.g. shared load balancer across deployments) — leave `gateway.envoyProxy.name` +empty and the Gateway will have no `infrastructure.parametersRef`, letting the `GatewayClass`-level +`EnvoyProxy` take effect automatically. + +Set `gateway.envoyProxy.name` (with `create: false`) to reference an existing `EnvoyProxy` in the +**same namespace** via `infrastructure.parametersRef`. + +`gateway.manageServiceType: true` (default) is a shorthand that sets +`provider.kubernetes.envoyService.type` to `gateway.serviceType`. Disable it when managing +the service type via `envoyProxy.spec` or a cluster-level `EnvoyProxy`. + +### GatewayClass is not created + +`GatewayClass` is installed by the Envoy Gateway Helm chart and is cluster-scoped. This chart only +references it by name via `gateway.className`. + +### EnvoyPatchPolicy + +When `federator.enabled: true`, the chart creates an `EnvoyPatchPolicy` resource that adds the +FQDN variant of the federator hostname (e.g. `federator.example.com.`, with trailing dot) to the +Envoy virtual host's domain list. + +**Why this is needed:** Wire federation resolves remote backends via DNS SRV records. Per the DNS +specification, SRV record targets are always FQDNs — they include a trailing dot +(e.g. `peer.example.com.`). The federator passes this FQDN directly as the HTTP/2 `:authority` +header. Envoy's virtual-host matching is exact, so the trailing dot causes a `route_not_found` +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. + +**`gateway.patchPolicies.targetGatewayClass`** controls what the policy targets: + +- **`false` (default)** — targets `kind: Gateway` by name. Use for standard single-Gateway + deployments, including integration tests. +- **`true`** — targets `kind: GatewayClass` (using `gateway.className`). **Required when + `gateway.envoyProxy.spec.mergeGateways: true`.** With merged Gateways, all Gateways of the same + GatewayClass share one Envoy proxy. + +> **Future note:** If future versions of the Wire federator stop sending FQDNs in the +> `:authority` header, this patch policy will no longer be needed. `gateway.patchPolicies.enabled` +> exists so it can be disabled at that point without a chart change. + +--- + +### Multi-ingress is out of scope + +Single-domain deployments are the only supported topology. Multi-domain support can be added later. + +### HTTP01 certificate challenges + +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: + +```yaml +gateway: + listeners: + http: + enabled: true # required for HTTP01 challenges +``` + +If you cannot or do not want to open port 80, use a DNS01 solver instead by setting + +```yaml +certManager: + customSolvers: + - dns01: + # .. provider-specific settings +``` + +DNS01 requires credentials for your DNS provider but does not need +port 80 to be open. + +### Federator TLS certificate (`federator.tls.useCertManager`) + +When `federator.tls.useCertManager: true`, cert-manager issues the federator TLS certificate. +The certificate requires both **server auth** and **client auth** Extended Key Usages (EKUs), +because federator connections are mutually authenticated. + +**Most public CAs (including Let's Encrypt) no longer issue certificates with the client auth +EKU.** You will need a **private CA** (e.g. a cert-manager `ClusterIssuer` backed by an internal +CA) to issue the federator certificate. Using the same public ACME issuer as for the main +wildcard certificate will not work. + +A typical setup uses a cert-manager `ClusterIssuer` of type `CA`, referencing a private CA +secret: + +```yaml +federator: + tls: + useCertManager: true + issuer: + name: my-private-ca + kind: ClusterIssuer +``` + +--- + +### Federator mTLS uses Envoy Gateway policies + +Federator mTLS is implemented using: + +- `ClientTrafficPolicy` to configure TLS settings on the federator `Gateway` listener (client + certificate validation, verify depth) +- A separate `Gateway` listener (or dedicated `Gateway`) 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 new file mode 100644 index 00000000000..8a3edc6429a --- /dev/null +++ b/charts/wire-ingress/templates/_helpers.tpl @@ -0,0 +1,75 @@ +{{/* vim: set filetype=mustache: */}} + +{{- define "wire-ingress.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{- define "wire-ingress.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Determine DNS zone based on the HTTPS FQDN (e.g. "nginz-https.example.com" → "example.com") +*/}} +{{- define "wire-ingress.zone" -}} +{{- $zones := splitList "." .Values.config.dns.https -}} +{{- slice $zones 1 | join "." -}} +{{- end -}} + +{{/* +Name of the TLS certificate secret. Differs based on whether cert-manager is used. +*/}} +{{- define "wire-ingress.getCertificateSecretName" -}} +{{- if .Values.tls.secret.nameOverride -}} + {{- .Values.tls.secret.nameOverride -}} +{{- else -}} + {{- $nameParts := list (include "wire-ingress.fullname" .) -}} + {{- if .Values.tls.useCertManager -}} + {{- $nameParts = append $nameParts "managed" -}} + {{- else -}} + {{- $nameParts = append $nameParts "wildcard" -}} + {{- end -}} + {{- $nameParts = append $nameParts "tls-certificate" -}} + {{- join "-" $nameParts -}} +{{- end -}} +{{- end -}} + +{{/* +Name of the custom ACME solver secret. +*/}} +{{- define "wire-ingress.getCustomSolversSecretName" -}} +{{- $nameParts := list (include "wire-ingress.fullname" .) -}} +{{- $nameParts = append $nameParts "cert-manager-custom-solvers" -}} +{{- join "-" $nameParts -}} +{{- end -}} + +{{/* +Returns the Letsencrypt ACME API server URL. +*/}} +{{- define "wire-ingress.certManagerAPIServerURL" -}} +{{- $hostnameParts := list "acme" -}} +{{- if .Values.certManager.inTestMode -}} + {{- $hostnameParts = append $hostnameParts "staging" -}} +{{- end -}} +{{- $hostnameParts = append $hostnameParts "v02" -}} +{{- join "-" $hostnameParts | printf "https://%s.api.letsencrypt.org/directory" -}} +{{- end -}} + +{{/* +Name of the cert-manager Issuer / ClusterIssuer. +*/}} +{{- define "wire-ingress.getIssuerName" -}} +{{ .Values.tls.issuer.name }} +{{- end -}} + +{{/* +Name of the Gateway resource. Uses gateway.name if set, otherwise derives one from the release name. +*/}} +{{- define "wire-ingress.getGatewayName" -}} +{{- if .Values.gateway.name -}} +{{ .Values.gateway.name }} +{{- else -}} +{{ include "wire-ingress.fullname" . }}-gateway +{{- end -}} +{{- end -}} diff --git a/charts/wire-ingress/templates/backendtrafficpolicy-websockets.yaml b/charts/wire-ingress/templates/backendtrafficpolicy-websockets.yaml new file mode 100644 index 00000000000..b9445e5c26d --- /dev/null +++ b/charts/wire-ingress/templates/backendtrafficpolicy-websockets.yaml @@ -0,0 +1,26 @@ +{{- if .Values.websockets.enabled }} +{{/* Disables the stream idle timeout for WebSocket connections. + Envoy's default stream_idle_timeout is 5 minutes; once an HTTP connection + is upgraded to WebSocket, the timer fires if no frames flow in either + direction, closing the connection. Wire clients can be idle much longer + than that (infrequent push notifications), causing unnecessary reconnects. + Setting streamIdleTimeout to "0s" disables the idle timer entirely. + Envoy Gateway-specific (gateway.envoyproxy.io/v1alpha1). */}} +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: {{ include "wire-ingress.fullname" . }}-nginz-websockets + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: {{ include "wire-ingress.fullname" . }}-nginz-websockets + timeout: + http: + streamIdleTimeout: "0s" +{{- end }} diff --git a/charts/wire-ingress/templates/certificate-federator.yaml b/charts/wire-ingress/templates/certificate-federator.yaml new file mode 100644 index 00000000000..b02c257c01f --- /dev/null +++ b/charts/wire-ingress/templates/certificate-federator.yaml @@ -0,0 +1,37 @@ +{{- if and .Values.federator.enabled .Values.federator.tls.useCertManager }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: "federator-{{ include "wire-ingress.zone" . | replace "." "-" }}-csr" + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + issuerRef: + {{- if .Values.federator.tls.issuer.name }} + name: {{ .Values.federator.tls.issuer.name | quote }} + kind: {{ .Values.federator.tls.issuer.kind | default .Values.tls.issuer.kind }} + {{- if .Values.federator.tls.issuer.group }} + group: {{ .Values.federator.tls.issuer.group }} + {{- end }} + {{- else }} + name: {{ include "wire-ingress.getIssuerName" . | quote }} + kind: {{ .Values.tls.issuer.kind }} + {{- end }} + usages: + - server auth + - client auth + duration: {{ .Values.federator.tls.duration }} + renewBefore: {{ .Values.federator.tls.renewBefore }} + isCA: false + secretName: {{ .Values.federator.tls.secretName | quote }} + privateKey: + algorithm: ECDSA + size: 256 + encoding: PKCS1 + rotationPolicy: {{ .Values.federator.tls.privateKey.rotationPolicy }} + dnsNames: + - {{ or .Values.config.dns.certificateDomain .Values.config.dns.federator | quote }} +{{- end }} diff --git a/charts/wire-ingress/templates/certificate.yaml b/charts/wire-ingress/templates/certificate.yaml new file mode 100644 index 00000000000..907ed721cbc --- /dev/null +++ b/charts/wire-ingress/templates/certificate.yaml @@ -0,0 +1,45 @@ +{{- if .Values.tls.useCertManager -}} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: "{{ include "wire-ingress.zone" . | replace "." "-" }}-csr" + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + issuerRef: + name: {{ include "wire-ingress.getIssuerName" . | quote }} + kind: {{ .Values.tls.issuer.kind }} + usages: + - server auth + duration: 2160h # 90d, Letsencrypt default; NOTE: changes are ignored by Letsencrypt + renewBefore: 360h # 15d + isCA: false + secretName: {{ include "wire-ingress.getCertificateSecretName" . | quote }} + + privateKey: + algorithm: {{ .Values.tls.privateKey.algorithm }} + size: {{ .Values.tls.privateKey.size }} + encoding: PKCS1 + rotationPolicy: {{ .Values.tls.privateKey.rotationPolicy }} + + dnsNames: + - {{ .Values.config.dns.https }} + {{- if .Values.websockets.enabled }} + - {{ .Values.config.dns.ssl }} + {{- end }} + {{- if .Values.webapp.enabled }} + - {{ .Values.config.dns.webapp }} + {{- end }} + {{- if .Values.fakeS3.enabled }} + - {{ .Values.config.dns.fakeS3 }} + {{- end }} + {{- if .Values.teamSettings.enabled }} + - {{ .Values.config.dns.teamSettings }} + {{- end }} + {{- if .Values.accountPages.enabled }} + - {{ .Values.config.dns.accountPages }} + {{- end }} +{{- end -}} diff --git a/charts/wire-ingress/templates/clienttrafficpolicy-federator.yaml b/charts/wire-ingress/templates/clienttrafficpolicy-federator.yaml new file mode 100644 index 00000000000..a2fa7413314 --- /dev/null +++ b/charts/wire-ingress/templates/clienttrafficpolicy-federator.yaml @@ -0,0 +1,28 @@ +{{- if .Values.federator.enabled }} +{{/* Envoy Gateway-specific (gateway.envoyproxy.io/v1alpha1). + Enforces mTLS client certificate validation on the federator listener only. */}} +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: {{ include "wire-ingress.getGatewayName" . }}-federator-mtls + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: {{ include "wire-ingress.getGatewayName" . | quote }} + sectionName: federator + tls: + {{/* Optional at the ingress level so the federator service can throw the + appropriate error when a cert is missing. Lua filter strips spoofed + X-SSL-Certificate header if no cert was actually provided. */}} + clientValidation: + optional: true + caCertificateRefs: + - name: federator-ca + kind: ConfigMap +{{- end }} diff --git a/charts/wire-ingress/templates/clienttrafficpolicy-proxy-protocol.yaml b/charts/wire-ingress/templates/clienttrafficpolicy-proxy-protocol.yaml new file mode 100644 index 00000000000..4969d3fbf2e --- /dev/null +++ b/charts/wire-ingress/templates/clienttrafficpolicy-proxy-protocol.yaml @@ -0,0 +1,18 @@ +{{- if .Values.gateway.proxyProtocol.enabled }} +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: {{ include "wire-ingress.getGatewayName" . }}-proxy-protocol + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: {{ include "wire-ingress.getGatewayName" . | quote }} + proxyProtocol: + optional: {{ .Values.gateway.proxyProtocol.optional | default false }} +{{- end }} diff --git a/charts/wire-ingress/templates/envoyextensionpolicy-federator.yaml b/charts/wire-ingress/templates/envoyextensionpolicy-federator.yaml new file mode 100644 index 00000000000..fa1db73025f --- /dev/null +++ b/charts/wire-ingress/templates/envoyextensionpolicy-federator.yaml @@ -0,0 +1,33 @@ +{{- if .Values.federator.enabled }} +{{/* Injects the mTLS client certificate as X-SSL-Certificate request header, + matching nginx's $ssl_client_escaped_cert behaviour. + Envoy Gateway-specific (gateway.envoyproxy.io/v1alpha1). */}} +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy +metadata: + name: {{ include "wire-ingress.fullname" . }}-federator-cert-header + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: {{ include "wire-ingress.fullname" . }}-federator + lua: + - type: Inline + inline: | + function envoy_on_request(request_handle) + {{/* Strip any client-provided header to prevent spoofing */}} + request_handle:headers():remove("X-SSL-Certificate") + local ssl = request_handle:connection():ssl() + if ssl ~= nil then + local cert = ssl:urlEncodedPemEncodedPeerCertificate() + if cert ~= nil and cert ~= "" then + request_handle:headers():add("X-SSL-Certificate", cert) + end + end + end +{{- end }} diff --git a/charts/wire-ingress/templates/envoypatchpolicy-federator.yaml b/charts/wire-ingress/templates/envoypatchpolicy-federator.yaml new file mode 100644 index 00000000000..15bbbfae6f9 --- /dev/null +++ b/charts/wire-ingress/templates/envoypatchpolicy-federator.yaml @@ -0,0 +1,41 @@ +{{- if and .Values.federator.enabled .Values.gateway.patchPolicies.enabled }} +{{/* 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. + Wire federator resolves federation targets via DNS SRV lookups; per + RFC 2782, SRV records return FQDNs (e.g. "federator.example.com."). + HTTP/2 passes that dot in :authority; without this patch the virtual + host only matches "federator.example.com" and returns route_not_found. + Envoy Gateway-specific (gateway.envoyproxy.io/v1alpha1). + Requires extensionApis.enableEnvoyPatchPolicy: true in the + EnvoyGateway ConfigMap. */}} +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyPatchPolicy +metadata: + name: {{ include "wire-ingress.fullname" . }}-federator-fqdn-domain + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + targetRef: + group: gateway.networking.k8s.io + {{- if .Values.gateway.patchPolicies.targetGatewayClass }} + kind: GatewayClass + name: {{ .Values.gateway.className | quote }} + {{- else }} + kind: Gateway + name: {{ include "wire-ingress.getGatewayName" . | 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.getGatewayName" .) | quote }} + operation: + op: add + path: "/virtual_hosts/0/domains/-" + value: {{ printf "%s." .Values.config.dns.federator | quote }} +{{- end }} diff --git a/charts/wire-ingress/templates/envoyproxy.yaml b/charts/wire-ingress/templates/envoyproxy.yaml new file mode 100644 index 00000000000..2f05f7046f2 --- /dev/null +++ b/charts/wire-ingress/templates/envoyproxy.yaml @@ -0,0 +1,23 @@ +{{- if (and .Values.gateway.create .Values.gateway.envoyProxy.create) }} +{{- $name := .Values.gateway.envoyProxy.name | default (include "wire-ingress.getGatewayName" .) }} +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: {{ $name | quote }} + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + {{- with .Values.gateway.envoyProxy.spec }} + {{- toYaml . | nindent 2 }} + {{- end }} + {{- if .Values.gateway.manageServiceType }} + provider: + type: Kubernetes + kubernetes: + envoyService: + type: {{ .Values.gateway.serviceType | quote }} + {{- end }} +{{- end }} diff --git a/charts/wire-ingress/templates/gateway.yaml b/charts/wire-ingress/templates/gateway.yaml new file mode 100644 index 00000000000..dfd954b094c --- /dev/null +++ b/charts/wire-ingress/templates/gateway.yaml @@ -0,0 +1,64 @@ +{{- if .Values.gateway.create }} +{{- if not .Values.gateway.className }} +{{- fail "gateway.className must be set when gateway.create is true (set it to the name of your GatewayClass, e.g. 'envoy')" }} +{{- end }} +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + gatewayClassName: {{ .Values.gateway.className | quote }} + {{- $envoyProxyName := "" }} + {{- if .Values.gateway.envoyProxy.create }} + {{- $envoyProxyName = .Values.gateway.envoyProxy.name | default (include "wire-ingress.getGatewayName" .) }} + {{- else if .Values.gateway.envoyProxy.name }} + {{- $envoyProxyName = .Values.gateway.envoyProxy.name }} + {{- end }} + {{- if (or .Values.gateway.infrastructure.annotations $envoyProxyName) }} + infrastructure: + {{- if .Values.gateway.infrastructure.annotations }} + annotations: + {{- toYaml .Values.gateway.infrastructure.annotations | nindent 6 }} + {{- end }} + {{- if $envoyProxyName }} + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: {{ $envoyProxyName | quote }} + {{- end }} + {{- end }} + listeners: + - name: https + 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 }} + tls: + mode: Terminate + certificateRefs: + - name: {{ include "wire-ingress.getCertificateSecretName" . | quote }} + kind: Secret + {{- 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 }} + {{- if .Values.gateway.listeners.http.enabled }} + - name: http + port: {{ .Values.gateway.listeners.http.port }} + protocol: HTTP + {{- if .Values.gateway.listeners.http.hostname }} + hostname: {{ .Values.gateway.listeners.http.hostname | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-account-pages.yaml b/charts/wire-ingress/templates/httproute-account-pages.yaml new file mode 100644 index 00000000000..d1f14db10f2 --- /dev/null +++ b/charts/wire-ingress/templates/httproute-account-pages.yaml @@ -0,0 +1,28 @@ +{{- if .Values.accountPages.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "wire-ingress.fullname" . }}-account-pages + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway + sectionName: https + hostnames: + - {{ required "config.dns.accountPages is required when accountPages.enabled is true" .Values.config.dns.accountPages | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: account-pages-http + port: {{ .Values.service.accountPages.externalPort }} + kind: Service +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-federator.yaml b/charts/wire-ingress/templates/httproute-federator.yaml new file mode 100644 index 00000000000..67f32ea5565 --- /dev/null +++ b/charts/wire-ingress/templates/httproute-federator.yaml @@ -0,0 +1,28 @@ +{{- if .Values.federator.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "wire-ingress.fullname" . }}-federator + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway + sectionName: federator + hostnames: + - {{ .Values.config.dns.federator | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: federator + port: {{ .Values.service.federator.externalPort }} + kind: Service +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-minio.yaml b/charts/wire-ingress/templates/httproute-minio.yaml new file mode 100644 index 00000000000..25bf6510000 --- /dev/null +++ b/charts/wire-ingress/templates/httproute-minio.yaml @@ -0,0 +1,39 @@ +{{- if .Values.fakeS3.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "wire-ingress.fullname" . }}-minio + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway + sectionName: https + hostnames: + - {{ required "config.dns.fakeS3 is required when fakeS3.enabled is true" .Values.config.dns.fakeS3 | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: /minio/ + filters: + - type: RequestRedirect + requestRedirect: + path: + type: ReplaceFullPath + replaceFullPath: / + statusCode: 301 + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: {{ .Values.service.s3.serviceName }} + port: {{ .Values.service.s3.externalPort }} + kind: Service +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-nginz-websockets.yaml b/charts/wire-ingress/templates/httproute-nginz-websockets.yaml new file mode 100644 index 00000000000..58a1b5d5664 --- /dev/null +++ b/charts/wire-ingress/templates/httproute-nginz-websockets.yaml @@ -0,0 +1,28 @@ +{{- if .Values.websockets.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "wire-ingress.fullname" . }}-nginz-websockets + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway + sectionName: https + hostnames: + - {{ required "config.dns.ssl is required when websockets.enabled is true" .Values.config.dns.ssl | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: nginz + port: {{ .Values.service.nginz.wsPort }} + kind: Service +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-nginz.yaml b/charts/wire-ingress/templates/httproute-nginz.yaml new file mode 100644 index 00000000000..ae320181c83 --- /dev/null +++ b/charts/wire-ingress/templates/httproute-nginz.yaml @@ -0,0 +1,26 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "wire-ingress.fullname" . }}-nginz + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway + sectionName: https + hostnames: + - {{ required "config.dns.https is required" .Values.config.dns.https | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: nginz + port: {{ .Values.service.nginz.httpPort }} + kind: Service diff --git a/charts/wire-ingress/templates/httproute-team-settings.yaml b/charts/wire-ingress/templates/httproute-team-settings.yaml new file mode 100644 index 00000000000..35d1e5bd1fb --- /dev/null +++ b/charts/wire-ingress/templates/httproute-team-settings.yaml @@ -0,0 +1,28 @@ +{{- if .Values.teamSettings.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "wire-ingress.fullname" . }}-team-settings + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway + sectionName: https + hostnames: + - {{ required "config.dns.teamSettings is required when teamSettings.enabled is true" .Values.config.dns.teamSettings | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: team-settings-http + port: {{ .Values.service.teamSettings.externalPort }} + kind: Service +{{- end }} diff --git a/charts/wire-ingress/templates/httproute-webapp.yaml b/charts/wire-ingress/templates/httproute-webapp.yaml new file mode 100644 index 00000000000..e130a9dd268 --- /dev/null +++ b/charts/wire-ingress/templates/httproute-webapp.yaml @@ -0,0 +1,28 @@ +{{- if .Values.webapp.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "wire-ingress.fullname" . }}-webapp + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway + sectionName: https + hostnames: + - {{ required "config.dns.webapp is required when webapp.enabled is true" .Values.config.dns.webapp | quote }} + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: webapp-http + port: {{ .Values.service.webapp.externalPort }} + kind: Service +{{- end }} diff --git a/charts/wire-ingress/templates/issuer.yaml b/charts/wire-ingress/templates/issuer.yaml new file mode 100644 index 00000000000..a71c7f9f662 --- /dev/null +++ b/charts/wire-ingress/templates/issuer.yaml @@ -0,0 +1,38 @@ +{{- if and .Values.tls.useCertManager .Values.tls.createIssuer -}} +apiVersion: cert-manager.io/v1 +{{- if or (eq .Values.tls.issuer.kind "Issuer") (eq .Values.tls.issuer.kind "ClusterIssuer") }} +kind: "{{ .Values.tls.issuer.kind }}" +{{- else }} +{{- fail (cat ".tls.issuer.kind can only be one of Issuer or ClusterIssuer, got: " .Values.tls.issuer.kind) }} +{{- end }} +metadata: + name: {{ include "wire-ingress.getIssuerName" . | quote }} + {{- if eq .Values.tls.issuer.kind "Issuer" }} + namespace: {{ .Release.Namespace }} + {{- end }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + acme: + server: {{ include "wire-ingress.certManagerAPIServerURL" . | quote }} + email: {{ required "Missing value: certmasterEmail" .Values.certManager.certmasterEmail | quote }} + # NOTE: this secret doesn't need to be created manually, cert-manager manages it + privateKeySecretRef: + name: {{ include "wire-ingress.getIssuerName" . -}}-account-key + solvers: +{{- if .Values.certManager.customSolvers }} +{{ toYaml .Values.certManager.customSolvers | indent 6 }} +{{- else }} +{{- if not .Values.gateway.listeners.http.enabled }} +{{- fail "The default HTTP01 solver requires gateway.listeners.http.enabled=true. Either enable the HTTP listener or supply certManager.customSolvers with a DNS01 solver." }} +{{- end }} + - http01: + gatewayHTTPRoute: + parentRefs: + - name: {{ include "wire-ingress.getGatewayName" . | quote }} + namespace: {{ .Release.Namespace | quote }} + kind: Gateway +{{- end }} +{{- end -}} diff --git a/charts/wire-ingress/templates/secret.yaml b/charts/wire-ingress/templates/secret.yaml new file mode 100644 index 00000000000..8d3ccb4af32 --- /dev/null +++ b/charts/wire-ingress/templates/secret.yaml @@ -0,0 +1,17 @@ +{{- if and (not .Values.tls.useCertManager) .Values.tls.secret.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "wire-ingress.getCertificateSecretName" . | quote }} + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +type: kubernetes.io/tls +data: + {{- with .Values.secrets }} + tls.crt: {{ .tlsWildcardCert | b64enc | quote }} + tls.key: {{ .tlsWildcardKey | b64enc | quote }} + {{- end -}} +{{- end -}} diff --git a/charts/wire-ingress/templates/service-account-pages.yaml b/charts/wire-ingress/templates/service-account-pages.yaml new file mode 100644 index 00000000000..7a77dbea0d6 --- /dev/null +++ b/charts/wire-ingress/templates/service-account-pages.yaml @@ -0,0 +1,18 @@ +{{- if .Values.accountPages.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: account-pages-http + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + type: ClusterIP + ports: + - port: {{ .Values.service.accountPages.externalPort }} + targetPort: 8080 + selector: + app: account-pages +{{- end }} diff --git a/charts/wire-ingress/templates/service-team-settings.yaml b/charts/wire-ingress/templates/service-team-settings.yaml new file mode 100644 index 00000000000..08b1fe6dfb1 --- /dev/null +++ b/charts/wire-ingress/templates/service-team-settings.yaml @@ -0,0 +1,18 @@ +{{- if .Values.teamSettings.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: team-settings-http + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + type: ClusterIP + ports: + - port: {{ .Values.service.teamSettings.externalPort }} + targetPort: 8080 + selector: + app: team-settings +{{- end }} diff --git a/charts/wire-ingress/templates/service-test-fed.yaml b/charts/wire-ingress/templates/service-test-fed.yaml new file mode 100644 index 00000000000..38ae03cfc73 --- /dev/null +++ b/charts/wire-ingress/templates/service-test-fed.yaml @@ -0,0 +1,38 @@ +{{- if (and .Values.federator.enabled .Values.federator.integrationTestHelper) }} +# This is used only for integration tests. +# Envoy Gateway runs proxy pods in {controllerNamespace}, not in the release namespace. +# Kubernetes Services cannot select pods across namespaces, so we create a ClusterIP +# Service in {controllerNamespace} that selects the proxy pods by their owning-gateway +# labels (set by Envoy Gateway). This service becomes the SRV target for Wire federation +# discovery: +# _wire-server-federator._tcp.-fed.{controllerNamespace}.svc.cluster.local +# +# targetPort must match the port the Envoy proxy pod actually listens on. +# When gateway.listeners.https.port is privileged (<1024), Envoy Gateway remaps it +# by adding 10000 on the container (e.g. 443→10443). Set gateway.listeners.https.port +# to the actual container port in your values (e.g. 10443) to avoid this. +{{- $httpsPort := int .Values.gateway.listeners.https.port }} +{{- if lt $httpsPort 1024 }} +{{- fail (printf "service-test-fed: gateway.listeners.https.port is %d (privileged, <1024). Envoy Gateway remaps it to %d on the proxy pod. Set gateway.listeners.https.port to the actual container port (e.g. %d)." $httpsPort (add $httpsPort 10000) (add $httpsPort 10000)) }} +{{- end }} +apiVersion: v1 +kind: Service +metadata: + # NOTE: keep this name in sync with with the "integrations" helm chart + name: {{ .Release.Namespace }}-fed + namespace: {{ .Values.gateway.controllerNamespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + type: ClusterIP + ports: + - name: wire-server-federator + port: 443 + protocol: TCP + targetPort: {{ $httpsPort }} + selector: + gateway.envoyproxy.io/owning-gateway-name: {{ include "wire-ingress.getGatewayName" . }} + gateway.envoyproxy.io/owning-gateway-namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/wire-ingress/templates/service-webapp.yaml b/charts/wire-ingress/templates/service-webapp.yaml new file mode 100644 index 00000000000..832e5b0b030 --- /dev/null +++ b/charts/wire-ingress/templates/service-webapp.yaml @@ -0,0 +1,19 @@ +{{- if .Values.webapp.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: webapp-http + namespace: {{ .Release.Namespace }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + type: ClusterIP + ports: + - port: {{ .Values.service.webapp.externalPort }} + targetPort: 8080 + selector: + app: webapp +{{- end }} diff --git a/charts/wire-ingress/values.yaml b/charts/wire-ingress/values.yaml new file mode 100644 index 00000000000..9da99a9e2fa --- /dev/null +++ b/charts/wire-ingress/values.yaml @@ -0,0 +1,208 @@ +# Default values for wire-ingress +gateway: + # If true, a Gateway resource is created by this chart. + # If false, set gateway.name to reference an existing Gateway. + create: true + # Name of the Gateway. Defaults to -wire-ingress-gateway if empty. + name: "" + # Name of the GatewayClass installed by the Envoy Gateway controller. + className: "" + envoyProxy: + # If true, an EnvoyProxy resource is created by this chart and the Gateway + # references it via infrastructure.parametersRef. + # Set to false when the GatewayClass already has a cluster-level EnvoyProxy + # (e.g. a shared load balancer managed by the cluster operator), or when you + # want to reference an existing EnvoyProxy via envoyProxy.name. + create: true + # Name of the EnvoyProxy resource. + # When create: true — the created resource uses this name (defaults to the Gateway name). + # When create: false — if non-empty, the Gateway references this existing EnvoyProxy + # via infrastructure.parametersRef (must be in the same namespace). + # If empty, no parametersRef is set on the Gateway (GatewayClass + # level EnvoyProxy takes effect). + name: "" + # 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 + # Example - shared load balancer across Gateways (single wire-ingress chart): + # spec: + # mergeGateways: true + # Example - ClusterIP service (integration tests / no external LB): + # spec: + # provider: + # type: Kubernetes + # kubernetes: + # envoyService: + # type: ClusterIP + spec: {} + # Convenience shorthand for provider.kubernetes.envoyService.type. + # Only takes effect when manageServiceType: true. + # Do not combine with envoyProxy.spec.provider.kubernetes.envoyService. + # Namespace where the Envoy Gateway controller runs its proxy pods. + # Change only if you installed Envoy Gateway into a non-default namespace. + controllerNamespace: envoy-gateway-system + manageServiceType: true + serviceType: LoadBalancer + # Annotations to add to the Service created by Envoy Gateway. + # Requires Gateway API CRDs v1.1+. + # Example for AWS NLB: + # infrastructure: + # annotations: + # service.beta.kubernetes.io/aws-load-balancer-type: external + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip + infrastructure: + 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. + 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: + https: + port: 443 + # Required. Hostname restriction for the HTTPS listener. + # Must be a wildcard or exact hostname covering this deployment's domains, + # e.g. "*.example.com". This prevents OverlappingTLSConfig with the federator + # listener (which uses the federation domain on the same port) — without an + # explicit hostname both listeners are catch-all and Envoy degrades ALPN to + # HTTP/1.1-only. + # If your domains in config.dns span multiple subdomains, you need a custom + # 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. + http: + enabled: false + port: 80 + # Optional hostname restriction for the HTTP listener. Set alongside + # listeners.https.hostname when using mergeGateways. + hostname: "" + # Set to false to skip creating EnvoyPatchPolicy resources. + # EnvoyPatchPolicy requires extensionApis.enableEnvoyPatchPolicy: true + # in the EnvoyGateway ConfigMap (see README). + patchPolicies: + enabled: true + # 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. + targetGatewayClass: false + +# NOTE: Please provide names. Here are naming suggestions: +# You need to reference those in the nginz helm chart nginx_conf.deeplink.endpoints +# config: +# dns: +# https: nginz-https. +# ssl: nginz-ssl. # ignored if websockets.enabled == false +# webapp: webapp. # ignored if webapp.enabled == false +# fakeS3: assets. # ignored if fakeS3.enabled == false +# federator: federator. # ignored unless federator.enabled == true +# certificateDomain: federator. # domain to use in the federator CSR +# teamSettings: teams. # ignored unless teamSettings.enabled == true +# accountPages: account. # ignored unless accountPages.enabled == true + +websockets: + enabled: true +webapp: + enabled: true +fakeS3: + enabled: true +teamSettings: + enabled: false +accountPages: + enabled: false + +service: + webapp: + externalPort: 8080 + teamSettings: + externalPort: 8080 + accountPages: + externalPort: 8080 + s3: + externalPort: 9000 + serviceName: fake-aws-s3 + # Set this to false if minio is not provided by the fake-aws-s3 chart + useFakeS3: true + federator: + # Must match service.externalFederatorPort in the federator chart. + externalPort: 8081 + nginz: + # Update this if you've overwritten the default HTTP port in nginz. + httpPort: 8080 + # Update this if you've overwritten the default WebSocket port in nginz. + wsPort: 8081 + +# Federator + +federator: + enabled: false + integrationTestHelper: false + tls: + # If true, a cert-manager Certificate is created for the federator TLS secret. + # Independent of the global tls.useCertManager setting. + useCertManager: true + # Name of the TLS Secret for the federator listener. + # When useCertManager is true, cert-manager writes the certificate into this secret. + # When useCertManager is false, the secret must exist before deploying. + secretName: "federator-certificate-secret" + duration: 2160h + renewBefore: 360h + privateKey: + rotationPolicy: Always + # Issuer for federator certificate (mTLS with Client Auth EKU). + # If not set, uses global tls.issuer configuration. + issuer: {} + # name: "" + # kind: "" + # group: "" + +# TLS settings + +tls: + secret: + # If true, a kubernetes.io/tls Secret is created from secrets.tlsWildcardCert + # and secrets.tlsWildcardKey. Set to false if the secret is managed externally. + create: true + # Override the name of the Secret. If not set, the name is derived from the release name. + nameOverride: "" + + # If set to true create Certificate object that request from tls.issuer + useCertManager: false + privateKey: + rotationPolicy: Always + algorithm: ECDSA + size: 384 # 521 is not supported by Let's Encrypt + + createIssuer: true + issuer: + name: letsencrypt-http01 + kind: Issuer # Issuer | ClusterIssuer + +certManager: + # If true, uses the Let's Encrypt staging API (certificates are NOT trusted): + # https://acme-staging-v02.api.letsencrypt.org/directory + # If false (default), uses the production API (certificates are trusted): + # https://acme-v02.api.letsencrypt.org/directory + inTestMode: false + certmasterEmail: + customSolvers: + +# For TLS (manual mode, tls.useCertManager: false, tls.secret.create: false): +# secrets: +# tlsWildcardCert: | +# -----BEGIN CERTIFICATE----- +# ... +# -----END CERTIFICATE----- +# tlsWildcardKey: | +# -----BEGIN PRIVATE KEY----- +# ... +# -----END PRIVATE KEY----- +# +# 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. diff --git a/charts/wire-server/templates/brig/tests/brig-integration.yaml b/charts/wire-server/templates/brig/tests/brig-integration.yaml index c2c9372217b..ce8eeadc0fd 100644 --- a/charts/wire-server/templates/brig/tests/brig-integration.yaml +++ b/charts/wire-server/templates/brig/tests/brig-integration.yaml @@ -3,7 +3,7 @@ kind: Service metadata: name: "brig-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation labels: app: brig-integration diff --git a/charts/wire-server/templates/brig/tests/configmap.yaml b/charts/wire-server/templates/brig/tests/configmap.yaml index e7540dc7c59..b327f32dbde 100644 --- a/charts/wire-server/templates/brig/tests/configmap.yaml +++ b/charts/wire-server/templates/brig/tests/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: "brig-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation data: integration.yaml: | diff --git a/charts/wire-server/templates/brig/tests/nginz-service.yaml b/charts/wire-server/templates/brig/tests/nginz-service.yaml index 6eda016c82d..b7895d51415 100644 --- a/charts/wire-server/templates/brig/tests/nginz-service.yaml +++ b/charts/wire-server/templates/brig/tests/nginz-service.yaml @@ -6,7 +6,7 @@ kind: Service metadata: name: nginz-integration-http annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation spec: type: ClusterIP diff --git a/charts/wire-server/templates/brig/tests/secret.yaml b/charts/wire-server/templates/brig/tests/secret.yaml index 86177dd7d3e..5bda33f6be2 100644 --- a/charts/wire-server/templates/brig/tests/secret.yaml +++ b/charts/wire-server/templates/brig/tests/secret.yaml @@ -8,7 +8,7 @@ metadata: release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation type: Opaque data: diff --git a/charts/wire-server/templates/cargohold/tests/configmap.yaml b/charts/wire-server/templates/cargohold/tests/configmap.yaml index 1542a14aa06..71a5db1bc33 100644 --- a/charts/wire-server/templates/cargohold/tests/configmap.yaml +++ b/charts/wire-server/templates/cargohold/tests/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: "cargohold-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation data: integration.yaml: | diff --git a/charts/wire-server/templates/galley/tests/configmap.yaml b/charts/wire-server/templates/galley/tests/configmap.yaml index 86be4122364..deb81aaeabb 100644 --- a/charts/wire-server/templates/galley/tests/configmap.yaml +++ b/charts/wire-server/templates/galley/tests/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: "galley-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation data: integration.yaml: | diff --git a/charts/wire-server/templates/galley/tests/galley-integration.yaml b/charts/wire-server/templates/galley/tests/galley-integration.yaml index 9256af1db6a..6135bff3878 100644 --- a/charts/wire-server/templates/galley/tests/galley-integration.yaml +++ b/charts/wire-server/templates/galley/tests/galley-integration.yaml @@ -3,7 +3,7 @@ kind: Service metadata: name: "galley-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation labels: app: galley-integration diff --git a/charts/wire-server/templates/galley/tests/secret.yaml b/charts/wire-server/templates/galley/tests/secret.yaml index b204dd4eddc..cf8e2e04d0c 100644 --- a/charts/wire-server/templates/galley/tests/secret.yaml +++ b/charts/wire-server/templates/galley/tests/secret.yaml @@ -8,7 +8,7 @@ metadata: release: "{{ .Release.Name }}" heritage: "{{ .Release.Service }}" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation type: Opaque data: diff --git a/charts/wire-server/templates/gundeck/tests/configmap.yaml b/charts/wire-server/templates/gundeck/tests/configmap.yaml index acba48d7f16..c8c23ce5185 100644 --- a/charts/wire-server/templates/gundeck/tests/configmap.yaml +++ b/charts/wire-server/templates/gundeck/tests/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: "gundeck-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation data: integration.yaml: | diff --git a/charts/wire-server/templates/spar/tests/configmap.yaml b/charts/wire-server/templates/spar/tests/configmap.yaml index 2eb1966099a..9cc5c79b796 100644 --- a/charts/wire-server/templates/spar/tests/configmap.yaml +++ b/charts/wire-server/templates/spar/tests/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: "spar-integration" annotations: - "helm.sh/hook": post-install + "helm.sh/hook": post-install,post-upgrade "helm.sh/hook-delete-policy": before-hook-creation data: integration.yaml: | diff --git a/hack/bin/integration-setup-federation.sh b/hack/bin/integration-setup-federation.sh index 355abb417ab..39c97d76ccd 100755 --- a/hack/bin/integration-setup-federation.sh +++ b/hack/bin/integration-setup-federation.sh @@ -7,9 +7,24 @@ TOP_LEVEL="$DIR/../.." export NAMESPACE=${NAMESPACE:-test-integration} # Available $HELMFILE_ENV profiles: default, default-ssl, kind, kind-ssl HELMFILE_ENV=${HELMFILE_ENV:-default} +# This controls if integration tests run against ingress-nginx or envoy-gateway +WIRE_INGRESS_MODE=${WIRE_INGRESS_MODE:-envoy} +export WIRE_INGRESS_MODE +ENVOY_GATEWAY_NAMESPACE=${ENVOY_GATEWAY_NAMESPACE:-envoy-gateway-system} +export ENVOY_GATEWAY_NAMESPACE CHARTS_DIR="${TOP_LEVEL}/.local/charts" HELM_PARALLELISM=${HELM_PARALLELISM:-1} +changed_files=$(git --no-pager diff-tree --no-commit-id -r --name-only HEAD) + +if [[ "$WIRE_INGRESS_MODE" != "nginx" ]] && echo "$changed_files" | grep -q "^charts/nginx-ingress-services"; then + echo "ERROR: Changes detected in charts/nginx-ingress-services but WIRE_INGRESS_MODE is '${WIRE_INGRESS_MODE}'." + echo "This failure is intentional: changes to nginx-ingress-services are not exercised by the" + echo "integration test suite when running in envoy mode, and would be merged without any test coverage." + echo "To test these changes, re-run with WIRE_INGRESS_MODE=nginx." + exit 1 +fi + # shellcheck disable=SC1091 . "$DIR/helm_overrides.sh" "${DIR}"/integration-cleanup.sh @@ -22,17 +37,24 @@ HELM_PARALLELISM=${HELM_PARALLELISM:-1} # script beforehand on all relevant charts to download the nested dependencies # (e.g. cassandra from underneath databases-ephemeral) echo "updating recursive dependencies ..." -charts=(fake-aws databases-ephemeral rabbitmq wire-server ingress-nginx-controller nginx-ingress-services) +charts=(fake-aws databases-ephemeral rabbitmq wire-server ingress-nginx-controller nginx-ingress-services wire-ingress) mkdir -p ~/.parallel && touch ~/.parallel/will-cite printf '%s\n' "${charts[@]}" | parallel -P "${HELM_PARALLELISM}" "$DIR/update.sh" "$CHARTS_DIR/{}" export NAMESPACE_1="$NAMESPACE" -export FEDERATION_DOMAIN_BASE_1="$NAMESPACE_1.svc.cluster.local" -export FEDERATION_DOMAIN_1="federation-test-helper.$FEDERATION_DOMAIN_BASE_1" - export NAMESPACE_2="$NAMESPACE-fed2" -export FEDERATION_DOMAIN_BASE_2="$NAMESPACE_2.svc.cluster.local" -export FEDERATION_DOMAIN_2="federation-test-helper.$FEDERATION_DOMAIN_BASE_2" + +if [[ "$WIRE_INGRESS_MODE" == "nginx" ]]; then + export FEDERATION_DOMAIN_BASE_1="${NAMESPACE_1}.svc.cluster.local" + export FEDERATION_DOMAIN_1="federation-test-helper.${FEDERATION_DOMAIN_BASE_1}" + export FEDERATION_DOMAIN_BASE_2="${NAMESPACE_2}.svc.cluster.local" + export FEDERATION_DOMAIN_2="federation-test-helper.${FEDERATION_DOMAIN_BASE_2}" +else + export FEDERATION_DOMAIN_BASE_1="${ENVOY_GATEWAY_NAMESPACE}.svc.cluster.local" + export FEDERATION_DOMAIN_1="${NAMESPACE_1}-fed.${FEDERATION_DOMAIN_BASE_1}" + export FEDERATION_DOMAIN_BASE_2="${ENVOY_GATEWAY_NAMESPACE}.svc.cluster.local" + export FEDERATION_DOMAIN_2="${NAMESPACE_2}-fed.${FEDERATION_DOMAIN_BASE_2}" +fi echo "Fetch federation-ca secret from cert-manager namespace" FEDERATION_CA_CERTIFICATE=$(kubectl -n cert-manager get secrets federation-ca -o json -o jsonpath="{.data['tls\.crt']}" | base64 -d) diff --git a/hack/helm_vars/wire-ingress/values.yaml.gotmpl b/hack/helm_vars/wire-ingress/values.yaml.gotmpl new file mode 100644 index 00000000000..b4cf20a464b --- /dev/null +++ b/hack/helm_vars/wire-ingress/values.yaml.gotmpl @@ -0,0 +1,42 @@ +teamSettings: + enabled: true +accountPages: + enabled: true +federator: + enabled: true + integrationTestHelper: true + tls: + useCertManager: true + issuer: + name: federation + kind: ClusterIssuer +tls: + useCertManager: true + issuer: + name: federation + kind: ClusterIssuer + createIssuer: false + +gateway: + className: "envoy" + controllerNamespace: {{ env "ENVOY_GATEWAY_NAMESPACE" }} + manageServiceType: true + serviceType: ClusterIP + listeners: + https: + # Envoy Gateway remaps privileged ports (+10000) on the proxy pod container. + # We use 10443 directly so targetPort in the service-test-fed.yaml matches the + # actual container port without needing a separate override. + port: 10443 + hostname: "*.{{ .Release.Namespace }}-integration.example.com" + +config: + dns: + https: "nginz-https.{{ .Release.Namespace }}-integration.example.com" + ssl: "nginz-ssl.{{ .Release.Namespace }}-integration.example.com" + webapp: "webapp.{{ .Release.Namespace }}-integration.example.com" + fakeS3: "assets.{{ .Release.Namespace }}-integration.example.com" + teamSettings: "teams.{{ .Release.Namespace }}-integration.example.com" + accountPages: "account.{{ .Release.Namespace }}-integration.example.com" + # federator: dynamically set by hack/helmfile.yaml.gotmpl + # certificateDomain: dynamically set by hack/helmfile.yaml.gotmpl diff --git a/hack/helm_vars/wire-server/values.yaml.gotmpl b/hack/helm_vars/wire-server/values.yaml.gotmpl index fe0a19d0682..a300d893702 100644 --- a/hack/helm_vars/wire-server/values.yaml.gotmpl +++ b/hack/helm_vars/wire-server/values.yaml.gotmpl @@ -614,8 +614,9 @@ federator: optSettings: useSystemCAStore: false logLevel: Debug - {{- if .Values.uploadXml }} tests: + nginxIngressHost: {{ .Values.federationDomain1 }} + {{- if .Values.uploadXml }} config: uploadXml: baseUrl: {{ .Values.uploadXml.baseUrl }} @@ -679,8 +680,15 @@ background-worker: password: {{ .Values.rabbitmqPassword }} integration: + envoy: + enabled: {{ ne (env "WIRE_INGRESS_MODE") "nginx" }} + controllerNamespace: {{ env "ENVOY_GATEWAY_NAMESPACE" }} + gateway: + className: "envoy" + {{- if eq (env "WIRE_INGRESS_MODE") "nginx" }} ingress: class: "nginx-{{ .Release.Namespace }}" + {{- end }} config: cassandra: host: {{ .Values.cassandraHost }} diff --git a/hack/helmfile-federation-v0.yaml.gotmpl b/hack/helmfile-federation-v0.yaml.gotmpl deleted file mode 100644 index 5400307d84b..00000000000 --- a/hack/helmfile-federation-v0.yaml.gotmpl +++ /dev/null @@ -1,110 +0,0 @@ ---- -helmDefaults: - wait: true - timeout: 600 - devel: true - createNamespace: true - -environments: - default: - values: - - federationCACertificate: {{ readFile "../services/nginz/integration-test/conf/nginz/integration-ca.pem" | quote }} - - rabbitmqUsername: guest - - rabbitmqPassword: guest ---- -repositories: - - name: jetstack - url: 'https://charts.jetstack.io' - - - name: bedag - url: 'https://bedag.github.io/helm-charts/' - - - name: wire - url: 'https://s3-eu-west-1.amazonaws.com/public.wire.com/charts-develop' - -releases: - - name: 'cert-manager' - namespace: cert-manager - chart: jetstack/cert-manager - set: - - name: installCRDs - value: true - - - name: 'federation-certs' - namespace: cert-manager - chart: bedag/raw - values: - - resources: - - apiVersion: v1 - kind: Secret - metadata: - name: federation-ca - namespace: cert-manager - data: - tls.crt: {{ readFile "../services/nginz/integration-test/conf/nginz/integration-ca.pem" | b64enc | quote }} - tls.key: {{ readFile "../services/nginz/integration-test/conf/nginz/integration-ca-key.pem" | b64enc | quote }} - - apiVersion: cert-manager.io/v1 - kind: ClusterIssuer - metadata: - name: federation - spec: - ca: - secretName: federation-ca - needs: - - 'cert-manager/cert-manager' - - - name: 'fake-aws' - namespace: wire-federation-v0 - chart: wire/fake-aws - version: 4.38.0-mandarin.14 - values: - - './helm_vars/fake-aws/values.yaml' - - - name: 'databases-ephemeral' - namespace: wire-federation-v0 - chart: 'wire/databases-ephemeral' - version: 4.38.0-mandarin.14 - - - name: 'rabbitmq' - namespace: wire-federation-v0 - chart: 'wire/rabbitmq' - version: 4.38.0-mandarin.14 - values: - - './helm_vars/rabbitmq/values.yaml.gotmpl' - - - name: 'ingress' - namespace: wire-federation-v0 - chart: 'wire/ingress-nginx-controller' - version: 4.38.0-mandarin.14 - values: - - './helm_vars/ingress-nginx-controller/values.yaml.gotmpl' - - - name: 'ingress-svc' - namespace: wire-federation-v0 - chart: 'wire/nginx-ingress-services' - version: 4.38.0-mandarin.14 - values: - - './helm_vars/nginx-ingress-services/values.yaml.gotmpl' - set: - # Federation domain is also the SRV record created by the - # federation-test-helper service. Maybe we can find a way to make these - # differ, so we don't make any silly assumptions in the code. - - name: config.dns.federator - value: wire-federation-v0.svc.cluster.local - - name: config.dns.certificateDomain - value: '*.wire-federation-v0.svc.cluster.local' - needs: - - 'ingress' - - 'cert-manager/cert-manager' - - 'cert-manager/federation-certs' - - - name: wire-server - namespace: wire-federation-v0 - chart: wire/wire-server - version: 4.38.0-mandarin.14 - values: - - './helm_vars/wire-federation-v0/values.yaml.gotmpl' - needs: - - 'cert-manager/cert-manager' - - 'cert-manager/federation-certs' - diff --git a/hack/helmfile.yaml.gotmpl b/hack/helmfile.yaml.gotmpl index fbd83410307..bb1bedad9bc 100644 --- a/hack/helmfile.yaml.gotmpl +++ b/hack/helmfile.yaml.gotmpl @@ -276,37 +276,57 @@ releases: values: - './helm_vars/{{ .Values.ingressChart }}/values.yaml.gotmpl' + {{- if eq (env "WIRE_INGRESS_MODE") "nginx" }} - name: 'ingress-svc' namespace: '{{ .Values.namespace1 }}' chart: '../.local/charts/nginx-ingress-services' values: - './helm_vars/nginx-ingress-services/values.yaml.gotmpl' set: - # Federation domain is also the SRV record created by the - # federation-test-helper service. Maybe we can find a way to make these - # differ, so we don't make any silly assumptions in the code. - name: config.dns.federator value: '{{ .Values.federationDomain1 }}' - name: config.dns.certificateDomain value: '*.{{ .Values.federationDomainBase1 }}' needs: - - 'ingress' + - ingress + {{- else }} + - name: 'ingress-svc' + namespace: '{{ .Values.namespace1 }}' + chart: '../.local/charts/wire-ingress' + values: + - './helm_vars/wire-ingress/values.yaml.gotmpl' + set: + - name: config.dns.federator + value: '{{ .Values.federationDomain1 }}' + - name: config.dns.certificateDomain + value: '*.{{ .Values.federationDomainBase1 }}' + {{- end }} + {{- if eq (env "WIRE_INGRESS_MODE") "nginx" }} - name: 'ingress-svc' namespace: '{{ .Values.namespace2 }}' chart: '../.local/charts/nginx-ingress-services' values: - './helm_vars/nginx-ingress-services/values.yaml.gotmpl' set: - # Federation domain is also the SRV record created by the - # federation-test-helper service. Maybe we can find a way to make these - # differ, so we don't make any silly assumptions in the code. - name: config.dns.federator value: '{{ .Values.federationDomain2 }}' - name: config.dns.certificateDomain value: '*.{{ .Values.federationDomainBase2 }}' needs: - - 'ingress' + - ingress + {{- else }} + - name: 'ingress-svc' + namespace: '{{ .Values.namespace2 }}' + chart: '../.local/charts/wire-ingress' + values: + - './helm_vars/wire-ingress/values.yaml.gotmpl' + set: + - name: config.dns.federator + value: '{{ .Values.federationDomain2 }}' + - name: config.dns.certificateDomain + value: '*.{{ .Values.federationDomainBase2 }}' + {{- end }} # Note that wire-server depends on databases-ephemeral being up; and in some # cases on nginx-ingress also being up. If installing helm charts in a diff --git a/integration/test/Testlib/ModService.hs b/integration/test/Testlib/ModService.hs index a28f3505365..cd6dc14d0f1 100644 --- a/integration/test/Testlib/ModService.hs +++ b/integration/test/Testlib/ModService.hs @@ -645,12 +645,12 @@ prepareNginzRuntimeFiles resource = do sm <- getServiceMap domain mBaseDir <- asks (.servicesCwdBase) case mBaseDir of - Nothing -> liftIO $ prepareNginzK8sRuntimeFiles domain sm + Nothing -> liftIO $ prepareNginzK8sRuntimeFiles sm Just basedir -> liftIO $ prepareNginzLocalRuntimeFiles resource sm basedir -prepareNginzK8sRuntimeFiles :: String -> ServiceMap -> IO (FilePath, FilePath, FilePath) -prepareNginzK8sRuntimeFiles domain sm = do - tmpDir <- createTempDirectory "/tmp" ("nginz" <> "-" <> domain) +prepareNginzK8sRuntimeFiles :: ServiceMap -> IO (FilePath, FilePath, FilePath) +prepareNginzK8sRuntimeFiles sm = do + tmpDir <- createTempDirectory "/tmp" "nginz-" copyDirectoryRecursively "/etc/wire/nginz/" tmpDir let nginxConfFile = tmpDir "conf" "nginx.conf" @@ -669,12 +669,10 @@ prepareNginzK8sRuntimeFiles domain sm = do prepareNginzLocalRuntimeFiles :: BackendResource -> ServiceMap -> FilePath -> IO (FilePath, FilePath, FilePath) prepareNginzLocalRuntimeFiles resource sm basedir = do - let domain = berDomain resource - -- Create a whole temporary directory and copy all nginx's config files. -- This is necessary because nginx assumes local imports are relative to -- the location of the main configuration file. - tmpDir <- createTempDirectory "/tmp" ("nginz" <> "-" <> domain) + tmpDir <- createTempDirectory "/tmp" "nginz-" -- copy all config files into the tmp dir let from = basedir "nginz" "integration-test"