From efbbe8617cf193d5f5cbc7a8d4b1baf5d8667b7b Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Tue, 13 Oct 2026 13:31:30 +0200 Subject: [PATCH 01/14] =?UTF-8?q?feat(tenants):=20tenant.extraHostnames=20?= =?UTF-8?q?voor=20meerdere=20hostnamen=20op=20=C3=A9=C3=A9n=20tenant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optioneel veld tenant.extraHostnames. De ApplicationSet zet de extra hosts in nextcloud.trustedDomains en rendert via extraManifests een tweede Ingress nextcloud-extra-hosts, met één TLS-blok (en dus één HTTP-01-Certificate) per host. Chart 8.9.0 zet in zijn eigen Ingress alleen nextcloud.host als rule. Annotaties, ingressClassName en poort komen via de tpl van de chart uit dezelfde values als de hoofd-Ingress. Bestaande tenants renderen byte-identiek: alle 89 tenantbestanden op main en op deze branch gerenderd en vergeleken, values en manifests gelijk. validate-values.sh controleert het veld; fixtures in tests/cases/tenant-extra-hostnames-*. Docs: ADDING-TENANT, CHECKS-AND-BALANCES, GATEWAY-API. Changelog-fragment in changelog.d/. --- changelog.d/tenant-extra-hostnames.md | 29 ++++++++++ docs/ADDING-TENANT.md | 47 +++++++++++++++- docs/CHECKS-AND-BALANCES.md | 8 ++- docs/GATEWAY-API.md | 7 ++- .../applicationsets/nextcloud-tenants.yaml | 50 ++++++++++++++++- nextcloud-platform/scripts/validate-values.sh | 55 +++++++++++++++++++ .../tenant-extra-hostnames-dubbel.expect | 1 + .../cases/tenant-extra-hostnames-dubbel.yaml | 14 +++++ .../tenant-extra-hostnames-geen-lijst.expect | 1 + .../tenant-extra-hostnames-geen-lijst.yaml | 12 ++++ .../tenant-extra-hostnames-geldig.expect | 1 + .../cases/tenant-extra-hostnames-geldig.yaml | 14 +++++ .../tenant-extra-hostnames-hoofdletter.expect | 1 + .../tenant-extra-hostnames-hoofdletter.yaml | 13 +++++ ...enant-extra-hostnames-primaire-host.expect | 1 + .../tenant-extra-hostnames-primaire-host.yaml | 13 +++++ 16 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 changelog.d/tenant-extra-hostnames.md create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.yaml diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md new file mode 100644 index 0000000..fc4055d --- /dev/null +++ b/changelog.d/tenant-extra-hostnames.md @@ -0,0 +1,29 @@ +### Toegevoegd — 2026-10-13 (tenant.extraHostnames) + +Een tenant kan nu op meer dan één hostnaam antwoorden. Nieuw optioneel veld +`tenant.extraHostnames` (lijst). De ApplicationSet `nextcloud-tenants`: + +- zet elke extra host achter de primaire host in `nextcloud.trustedDomains` + (en daarmee in `NEXTCLOUD_TRUSTED_DOMAINS`); +- rendert via `extraManifests` een tweede Ingress `nextcloud-extra-hosts`, + omdat chart 8.9.0 in zijn eigen Ingress alleen `nextcloud.host` als rule zet + (`ingress.hosts` uit de ApplicationSet leest die chart niet); +- geeft elke extra host een eigen TLS-blok en dus een eigen Certificate via + HTTP-01, zodat één host met nog niet kloppende DNS de andere niet tegenhoudt; +- neemt annotaties, `ingressClassName` en poort via de `tpl` van de chart over + uit dezelfde values als de hoofd-Ingress: geen tweede kopie van de + nginx-instellingen. + +**Bestaande tenants renderen byte-identiek.** Het blok wordt alleen geëmit als +de lijst gezet is. Gemeten: alle 89 tenantbestanden op main en op deze branch +gerenderd (Argo-goTemplate met sprig en `missingkey=default`, daarna +`helm template` met chart 8.9.0 en dezelfde valueFiles-volgorde), values en +manifests per tenant byte voor byte gelijk. + +`validate-values.sh` controleert het nieuwe veld (lijst, geldige DNS-namen, +geen dubbele, niet de primaire host, secretnaam ≤ 253), met fixtures in +`nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. + +Bekende beperkingen: `trusted_domains` wordt alleen bij de eerste installatie +geschreven; `charts/tenant-httproute` neemt nog één host. Zie +`docs/ADDING-TENANT.md`. diff --git a/docs/ADDING-TENANT.md b/docs/ADDING-TENANT.md index f7d3fb3..feb6ead 100644 --- a/docs/ADDING-TENANT.md +++ b/docs/ADDING-TENANT.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-08-26 +last_reviewed: 2026-10-13 owner: info@conduction.nl --- @@ -180,6 +180,51 @@ soap-enabled build from `ConductionNL/nextcloud-images`. `validate-values.sh` has no top-level key allowlist, so an `image:` block passes validation as-is — the rules above are not machine-checked. +#### Extra hostnames on one tenant + +One Nextcloud can answer on more than one hostname, for example a workplace on +`cloud.` and a public site served by an app on `` and +`www.`. Set the primary host as usual and list the others: + +```yaml +tenant: + hostname: cloud.example.nl + hostnameOverride: true + extraHostnames: + - example.nl + - www.example.nl +``` + +What the ApplicationSet does with it (`argo/applicationsets/nextcloud-tenants.yaml`): + +| Effect | Detail | +|---|---| +| `trusted_domains` | every extra host is added to `nextcloud.trustedDomains`, after the primary host | +| routing | a second Ingress `nextcloud-extra-hosts`, because chart 8.9.0 puts only `nextcloud.host` in its own Ingress rules | +| certificates | one TLS entry per host, secret `nextcloud---tls`, so one cert-manager Certificate per host via HTTP-01 (`letsencrypt-prod`). A host whose DNS is not ready does not block the others | +| annotations | taken from `ingress.annotations`, `ingress.className` and `service.port` through the chart's `tpl`, so they always match the main Ingress | + +A tenant without `extraHostnames` renders byte-identical to before; the block is +only emitted when the list is set. + +Four things to know: + +- **DNS is yours when the zone is not ours.** external-dns only manages + `commonground.nu`, `openwoo.app` and `opencatalogi.nl` + (`cluster-infra/external-dns/values.yaml`). For any other zone the zone owner + creates the records by hand, pointing at the ingress-nginx load balancer, and + keeps them **DNS only** (not proxied): Cloudflare caps request bodies at 100 MB + against our 16G, and HTTP-01 has to reach the Ingress. +- **A CAA record on the zone must allow `letsencrypt.org`**, or issuance fails. +- **`trusted_domains` is written at first install only.** The Nextcloud image + reads `NEXTCLOUD_TRUSTED_DOMAINS` when it installs; adding a host to a running + tenant later also needs `occ config:system:set trusted_domains --value=` + in the pod (same pattern as `cutover-tenant.sh`). Put the full list in the file + before the tenant is first created. +- **Gateway API routes do not use this list.** `charts/tenant-httproute` takes one + host; a tenant with `gateway.nextcloud: true` gets a route for the primary host + only. + Validate before pushing: ```bash diff --git a/docs/CHECKS-AND-BALANCES.md b/docs/CHECKS-AND-BALANCES.md index f4f43c2..5bdd456 100644 --- a/docs/CHECKS-AND-BALANCES.md +++ b/docs/CHECKS-AND-BALANCES.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-08-10 +last_reviewed: 2026-10-13 owner: info@conduction.nl --- @@ -35,6 +35,12 @@ Before merge, CI runs `scripts/verify.sh`, which wraps: and patterns across all tenant files - `nextcloud-platform/scripts/smoke-checks.sh` — Helm chart renders +`validate-values.sh` also checks the optional `tenant.extraHostnames` list (a +list, valid DNS names, no duplicates, not the primary host, a derived TLS secret +name Kubernetes accepts). A valid list still gives a warning, because DNS and +HTTP-01 for those hosts are outside what CI can see. Fixtures for every branch +are in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. + This catches invalid values and Helm/rendering failures early. Both are dry-run: CI needs no cluster access and no secrets. The same gate runs locally via `./scripts/verify.sh` before pushing. diff --git a/docs/GATEWAY-API.md b/docs/GATEWAY-API.md index df57279..554ba1b 100644 --- a/docs/GATEWAY-API.md +++ b/docs/GATEWAY-API.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-08-17 +last_reviewed: 2026-10-13 owner: info@conduction.nl --- @@ -47,6 +47,11 @@ een tenant kan zijn WOO-frontend eerder migreren dan zijn Nextcloud. Zonder `gateway:`-blok rendert er niets extra's, ook geen `enabled: false`. Een blok dat altijd meegaat zou alle 84 Applications tegelijk laten hersyncen. +`tenant.extraHostnames` (zie `docs/ADDING-TENANT.md`) gaat hier niet mee: de +route krijgt alleen de primaire host. Een tenant met extra hostnamen blijft voor +die hosts op de nginx-Ingress tot deze chart een lijst hostnames aanneemt, met +per host een listener op de Gateway. + ## Waarom dit nog niet schaalt De frontends hebben het makkelijk: `*.openwoo.app` en `*.accept.openwoo.app` diff --git a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml index 85dea1c..6dc3b9f 100644 --- a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml +++ b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml @@ -97,6 +97,7 @@ spec: {{- $appsStr := join " " $apps -}} {{- $appapiEnabled := default false .tenant.features.appapi -}} {{- $phpMemoryLimit := default "2048M" .tenant.phpMemoryLimit -}} + {{- $extraHosts := default (list) .tenant.extraHostnames -}} fullnameOverride: "nextcloud" commonLabels: nextcloud.platform/tenant: "{{ .tenant.name }}" @@ -106,6 +107,9 @@ spec: host: "{{ $host }}" trustedDomains: - "{{ $host }}" + {{- range $extraHosts }} + - "{{ . }}" + {{- end }} extraEnv: - name: PHP_MEMORY_LIMIT value: "{{ $phpMemoryLimit }}" @@ -184,7 +188,51 @@ spec: s3: bucket: "nextcloud" prefix: "{{ $name }}/" - + {{- if $extraHosts }} + # tenant.extraHostnames: extra hostnamen op DEZELFDE Nextcloud. + # De chart (8.9.0) zet in zijn eigen Ingress alleen nextcloud.host + # als rule, dus extra hosts krijgen hier een tweede Ingress. Eén + # TLS-blok per host, dus één Certificate per host: een host waarvan + # DNS nog niet klopt houdt de andere niet tegen. Annotaties, + # ingressClassName en poort komen via de tpl van de chart uit + # dezelfde values als de hoofd-Ingress, dus geen tweede kopie van + # de nginx-instellingen. Alleen geëmit als de lijst gezet is: + # bestaande tenants renderen byte-identiek. + extraManifests: + - | + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: nextcloud-extra-hosts + labels: + app.kubernetes.io/name: nextcloud + app.kubernetes.io/part-of: nextcloud-platform + nextcloud.platform/tenant: "{{ $name }}" + annotations: + {{`{{- toYaml .Values.ingress.annotations | nindent 4 }}`}} + spec: + ingressClassName: {{`{{ .Values.ingress.className }}`}} + tls: + {{- range $extraHosts }} + - secretName: "nextcloud-{{ $name }}-{{ replace "." "-" . }}-tls" + hosts: + - "{{ . }}" + {{- end }} + rules: + {{- range $extraHosts }} + - host: "{{ . }}" + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: nextcloud + port: + number: {{`{{ .Values.service.port }}`}} + {{- end }} + {{- end }} + # Our GitOps repo for values files # # Drie refs, drie stappen: diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 292236d..d95335a 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -405,6 +405,60 @@ validate_hostname_convention() { return 0 } +# Validate tenant.extraHostnames (optioneel) +# +# Extra hostnamen op dezelfde Nextcloud. De ApplicationSet zet ze in +# trustedDomains en rendert er een tweede Ingress voor, met één TLS-secret +# "nextcloud---tls" per host. Gecontroleerd: +# lijstvorm, geldige DNS-naam, geen dubbele, niet gelijk aan de primaire host, +# en een secretnaam die Kubernetes accepteert (DNS-subdomein, max 253). +validate_extra_hostnames() { + local file="$1" + local kind name primary + kind=$(yq eval '.tenant.extraHostnames | type' "$file" 2>/dev/null) + + if [ "$kind" = "!!null" ] || [ -z "$kind" ]; then + return 0 + fi + + if [ "$kind" != "!!seq" ]; then + log_error "$file: tenant.extraHostnames moet een lijst zijn (got $kind)" + return 1 + fi + + name=$(yq eval '.tenant.name' "$file" 2>/dev/null) + primary=$(yq eval '.tenant.hostname // ""' "$file" 2>/dev/null) + + local has_error=0 host seen=" " secret + while IFS= read -r host; do + if ! [[ "$host" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ ]]; then + log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host'" + has_error=1 + continue + fi + if [ -n "$primary" ] && [ "$host" = "$primary" ]; then + log_error "$file: tenant.extraHostnames bevat de primaire host '$host' (die staat al in tenant.hostname)" + has_error=1 + fi + if [[ "$seen" == *" $host "* ]]; then + log_error "$file: tenant.extraHostnames bevat '$host' dubbel" + has_error=1 + fi + seen="${seen}${host} " + secret="nextcloud-${name}-${host//./-}-tls" + if [ ${#secret} -gt 253 ]; then + log_error "$file: TLS-secretnaam '$secret' voor '$host' is langer dan 253 tekens" + has_error=1 + fi + done < <(yq eval '.tenant.extraHostnames[]' "$file" 2>/dev/null) + + if [ $has_error -eq 0 ]; then + log_warning "$file: tenant.extraHostnames gezet; DNS en HTTP-01 per host moeten kloppen (zie docs/ADDING-TENANT.md)" + fi + + return $has_error +} + # Validate wave number validate_wave() { local file="$1" @@ -640,6 +694,7 @@ validate_tenant_file() { validate_chart_version_format "$file" || ((file_errors++)) validate_hostname "$file" || ((file_errors++)) validate_hostname_convention "$file" || ((file_errors++)) + validate_extra_hostnames "$file" || ((file_errors++)) validate_wave "$file" || ((file_errors++)) validate_bucket "$file" || ((file_errors++)) validate_frontend_image "$file" || ((file_errors++)) diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.expect new file mode 100644 index 0000000..170c639 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.expect @@ -0,0 +1 @@ +bevat 'www.voorbeeld.nl' dubbel diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.yaml new file mode 100644 index 0000000..15150ec --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-dubbel.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - www.voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.expect new file mode 100644 index 0000000..e6660db --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.expect @@ -0,0 +1 @@ +tenant.extraHostnames moet een lijst zijn diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.yaml new file mode 100644 index 0000000..6151b85 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geen-lijst.yaml @@ -0,0 +1,12 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.expect new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.expect @@ -0,0 +1 @@ +PASS diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml new file mode 100644 index 0000000..f0fa171 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.expect new file mode 100644 index 0000000..8d8debd --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam 'Www.Voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.yaml new file mode 100644 index 0000000..2b4133e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-hoofdletter.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - Www.Voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.expect new file mode 100644 index 0000000..47f9a5f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.expect @@ -0,0 +1 @@ +bevat de primaire host 'cloud.voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.yaml new file mode 100644 index 0000000..32be033 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-primaire-host.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - cloud.voorbeeld.nl From 6b0bc18b1833fce44fbf8bbebeaef579bc7bb8e2 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 15:28:31 +0200 Subject: [PATCH 02/14] =?UTF-8?q?fix(review):=20#138=20f2=20=E2=80=94=20Be?= =?UTF-8?q?rekende=20primaire=20host=20niet=20herkend=20als=20dubbel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228417 validate_extra_hostnames vergeleek alleen met een expliciete tenant.hostname. Nieuwe helper primary_host() berekent de primaire host zoals de ApplicationSet (tenant.hostname, anders .commonground.nu / ..commonground.nu, inclusief het ontbreken van 'demo' als suffix in het template). Gecontroleerd tegen de gerenderde nextcloud.host van alle 89 tenants: gelijk. Fixture tenant-extra-hostnames-berekende-primaire-host (zonder hostname) faalt nu met 'bevat de primaire host'; tegen de PR-head-validator slaagde hij. Co-Authored-By: Claude Opus 5.5 --- nextcloud-platform/scripts/validate-values.sh | 43 +++++++++++++++++-- ...a-hostnames-berekende-primaire-host.expect | 1 + ...tra-hostnames-berekende-primaire-host.yaml | 11 +++++ 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.yaml diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index d95335a..78a0623 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -405,6 +405,35 @@ validate_hostname_convention() { return 0 } +# Primaire host zoals de ApplicationSet (argo/applicationsets/nextcloud-tenants.yaml) +# hem bepaalt: tenant.hostname, en anders afgeleid uit tenant.name. Houd dit +# gelijk met dat template. Let op: het template kent 'demo' niet als suffix, +# zo'n tenant valt terug op tenant.environment (bv. x-demo.accept.commonground.nu). +# +# Usage: primary_host +primary_host() { + local name="$1" env="$2" hostname="$3" suffix="" org="$1" omgeving + + if [ -n "$hostname" ] && [ "$hostname" != "null" ]; then + echo "$hostname" + return 0 + fi + + if [[ "$name" =~ (accept|test|prod)$ ]]; then + suffix="${BASH_REMATCH[1]}" + fi + if [[ "$name" =~ ^(.*)-(accept|test|prod)$ ]]; then + org="${BASH_REMATCH[1]}" + fi + omgeving="${suffix:-$env}" + + if [ "$omgeving" = "prod" ]; then + echo "${org}.commonground.nu" + else + echo "${org}.${omgeving}.commonground.nu" + fi +} + # Validate tenant.extraHostnames (optioneel) # # Extra hostnamen op dezelfde Nextcloud. De ApplicationSet zet ze in @@ -414,7 +443,7 @@ validate_hostname_convention() { # en een secretnaam die Kubernetes accepteert (DNS-subdomein, max 253). validate_extra_hostnames() { local file="$1" - local kind name primary + local kind name hostname primary primary_bron kind=$(yq eval '.tenant.extraHostnames | type' "$file" 2>/dev/null) if [ "$kind" = "!!null" ] || [ -z "$kind" ]; then @@ -427,7 +456,13 @@ validate_extra_hostnames() { fi name=$(yq eval '.tenant.name' "$file" 2>/dev/null) - primary=$(yq eval '.tenant.hostname // ""' "$file" 2>/dev/null) + hostname=$(yq eval '.tenant.hostname // ""' "$file" 2>/dev/null) + primary=$(primary_host "$name" "$(yq eval '.tenant.environment' "$file" 2>/dev/null)" "$hostname") + if [ -n "$hostname" ]; then + primary_bron="die staat al in tenant.hostname" + else + primary_bron="afgeleid uit tenant.name, zoals de ApplicationSet doet" + fi local has_error=0 host seen=" " secret while IFS= read -r host; do @@ -436,8 +471,8 @@ validate_extra_hostnames() { has_error=1 continue fi - if [ -n "$primary" ] && [ "$host" = "$primary" ]; then - log_error "$file: tenant.extraHostnames bevat de primaire host '$host' (die staat al in tenant.hostname)" + if [ "$host" = "$primary" ]; then + log_error "$file: tenant.extraHostnames bevat de primaire host '$host' ($primary_bron)" has_error=1 fi if [[ "$seen" == *" $host "* ]]; then diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.expect new file mode 100644 index 0000000..9aa6da7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.expect @@ -0,0 +1 @@ +bevat de primaire host 'voorbeeld.commonground.nu' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.yaml new file mode 100644 index 0000000..b864e8a --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-berekende-primaire-host.yaml @@ -0,0 +1,11 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.commonground.nu From 34c94c0bcadc8ab29a550eb2bfc944ab050f6fa5 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 15:43:44 +0200 Subject: [PATCH 03/14] =?UTF-8?q?fix(review):=20#138=20f1=20=E2=80=94=20Ex?= =?UTF-8?q?tra=20host=20mag=20de=20host=20van=20een=20andere=20tenant=20cl?= =?UTF-8?q?aimen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228400 validate-values.sh bouwt nu per run een host-register over alle tenantbestanden in values/tenants/ (primaire host zoals de ApplicationSet hem bepaalt, tenant.extraHostnames, tenant.frontend.host, tenant.frontend.extraHosts) en weigert elke host die twee tenants claimen (validate_host_claims). Een bestand op de commandoregel vervangt het tenantbestand met dezelfde tenant.name, dus ook `validate-values.sh <één bestand>` vergelijkt met de hele vloot en een gewijzigde kopie botst niet met zichzelf. De check werkt in beide richtingen: een nieuwe tenant B faalt ook als een bestaande A zijn host al als extra host heeft (getest met een tijdelijk tenantbestand). Een extra host onder commonground.nu moet volgens de naamconventie van validate_hostname_convention bij de eigen tenant horen (convention_hosts: .commonground.nu / .migrate.commonground.nu voor prod, ..commonground.nu anders). Geen nieuwe opt-in. Class sweep: binnen één tenant mag een extra host ook niet de host van de eigen WOO-frontend zijn (zelfde botsing, andere Ingress). Checklijsten bijgewerkt in de commentaarkop van validate_extra_hostnames, docs/CHECKS-AND-BALANCES.md en changelog.d/tenant-extra-hostnames.md; run-tests.sh noemt dat de botsingsfixtures op tenant-dinkelland-prod leunen (de runner kent alleen enkelvoudige cases; de validator leest values/tenants/ zelf, dus de runner hoefde niet te veranderen). Fixtures: andere-tenant (primaire host van dinkelland-prod), andere-frontend (frontend.host van dinkelland-prod), commonground-vreemd, commonground-eigen (PASS), eigen-frontend. De vier foute cases slagen tegen de PR-head-validator. Co-Authored-By: Claude Opus 5.5 --- changelog.d/tenant-extra-hostnames.md | 5 +- docs/CHECKS-AND-BALANCES.md | 7 +- nextcloud-platform/scripts/validate-values.sh | 114 +++++++++++++++++- ...ant-extra-hostnames-andere-frontend.expect | 1 + ...enant-extra-hostnames-andere-frontend.yaml | 13 ++ ...enant-extra-hostnames-andere-tenant.expect | 1 + .../tenant-extra-hostnames-andere-tenant.yaml | 13 ++ ...-extra-hostnames-commonground-eigen.expect | 1 + ...nt-extra-hostnames-commonground-eigen.yaml | 13 ++ ...extra-hostnames-commonground-vreemd.expect | 1 + ...t-extra-hostnames-commonground-vreemd.yaml | 13 ++ ...nant-extra-hostnames-eigen-frontend.expect | 1 + ...tenant-extra-hostnames-eigen-frontend.yaml | 15 +++ nextcloud-platform/tests/run-tests.sh | 4 + 14 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.yaml diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md index fc4055d..c8f16b4 100644 --- a/changelog.d/tenant-extra-hostnames.md +++ b/changelog.d/tenant-extra-hostnames.md @@ -21,7 +21,10 @@ gerenderd (Argo-goTemplate met sprig en `missingkey=default`, daarna manifests per tenant byte voor byte gelijk. `validate-values.sh` controleert het nieuwe veld (lijst, geldige DNS-namen, -geen dubbele, niet de primaire host, secretnaam ≤ 253), met fixtures in +geen dubbele, niet de primaire host, onder `commonground.nu` alleen de eigen +namen, secretnaam ≤ 253) en weigert over alle tenantbestanden heen een host die +twee tenants claimen (primaire host, `extraHostnames`, `frontend.host`, +`frontend.extraHosts`), met fixtures in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. Bekende beperkingen: `trusted_domains` wordt alleen bij de eerste installatie diff --git a/docs/CHECKS-AND-BALANCES.md b/docs/CHECKS-AND-BALANCES.md index 5bdd456..212c8d4 100644 --- a/docs/CHECKS-AND-BALANCES.md +++ b/docs/CHECKS-AND-BALANCES.md @@ -36,8 +36,11 @@ Before merge, CI runs `scripts/verify.sh`, which wraps: - `nextcloud-platform/scripts/smoke-checks.sh` — Helm chart renders `validate-values.sh` also checks the optional `tenant.extraHostnames` list (a -list, valid DNS names, no duplicates, not the primary host, a derived TLS secret -name Kubernetes accepts). A valid list still gives a warning, because DNS and +list, valid DNS names, no duplicates, not the primary host, under +`commonground.nu` only this tenant's own names, a derived TLS secret name +Kubernetes accepts). It also checks across all tenant files that no host +(primary, `extraHostnames`, `frontend.host`, `frontend.extraHosts`) is claimed +by two tenants, also when it is run on a single file. A valid list still gives a warning, because DNS and HTTP-01 for those hosts are outside what CI can see. Fixtures for every branch are in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 78a0623..22133b9 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -440,7 +440,9 @@ primary_host() { # trustedDomains en rendert er een tweede Ingress voor, met één TLS-secret # "nextcloud---tls" per host. Gecontroleerd: # lijstvorm, geldige DNS-naam, geen dubbele, niet gelijk aan de primaire host, -# en een secretnaam die Kubernetes accepteert (DNS-subdomein, max 253). +# onder commonground.nu alleen hosts van de eigen tenant, en een secretnaam die +# Kubernetes accepteert (DNS-subdomein, max 253). Botsingen met hosts van andere +# tenants vangt validate_host_claims. validate_extra_hostnames() { local file="$1" local kind name hostname primary primary_bron @@ -475,6 +477,13 @@ validate_extra_hostnames() { log_error "$file: tenant.extraHostnames bevat de primaire host '$host' ($primary_bron)" has_error=1 fi + # commonground.nu is van het platform: een extra host daaronder moet + # volgens de naamconventie bij deze tenant horen. + if [ "$host" != "$primary" ] && [[ "$host" == commonground.nu || "$host" == *.commonground.nu ]] \ + && ! convention_hosts "$name" | grep -qxF -- "$host"; then + log_error "$file: tenant.extraHostnames bevat '$host' onder commonground.nu, maar die hoort niet bij tenant '$name' (toegestaan: $(convention_hosts "$name" | paste -sd ' '))" + has_error=1 + fi if [[ "$seen" == *" $host "* ]]; then log_error "$file: tenant.extraHostnames bevat '$host' dubbel" has_error=1 @@ -494,6 +503,105 @@ validate_extra_hostnames() { return $has_error } +# Hosts onder commonground.nu die volgens de conventie bij deze tenant horen. +# Zelfde regel als validate_hostname_convention: prod krijgt .commonground.nu +# en de migratiehost .migrate.commonground.nu, de rest +# ..commonground.nu. +# +# Usage: convention_hosts +convention_hosts() { + local name="$1" org suffix + [[ "$name" =~ ^(.+)-(accept|test|demo|prod)$ ]] || return 0 + org="${BASH_REMATCH[1]}" + suffix="${BASH_REMATCH[2]}" + if [ "$suffix" = "prod" ]; then + echo "${org}.commonground.nu" + echo "${org}.migrate.commonground.nu" + else + echo "${org}.${suffix}.commonground.nu" + fi +} + +# Alle hosts die één tenantbestand claimt, als regels "": +# de primaire host (zoals de ApplicationSet hem bepaalt), tenant.extraHostnames +# en de WOO-frontend (tenant.frontend.host en tenant.frontend.extraHosts, alleen +# als ze expliciet gezet zijn; de afgeleide frontend-host staat in React-base). +# +# Usage: tenant_host_claims +tenant_host_claims() { + local file="$1" name env hostname + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || return 0 + [ -z "$name" ] && return 0 + env=$(yq eval '.tenant.environment // ""' "$file" 2>/dev/null) + hostname=$(yq eval '.tenant.hostname // ""' "$file" 2>/dev/null) + printf '%s\tprimaire host\n' "$(primary_host "$name" "$env" "$hostname")" + local bron host + while read -r bron host; do + [ -n "$host" ] && printf '%s\t%s\n' "$host" "$bron" + done < <(yq eval '.tenant as $t | ( + [$t.extraHostnames // [] | select(tag == "!!seq") | .[] | to_string | "tenant.extraHostnames " + .] + + [$t.frontend.host // null | select(. != null) | to_string | "tenant.frontend.host " + .] + + [$t.frontend.extraHosts // [] | select(tag == "!!seq") | .[] | to_string | "tenant.frontend.extraHosts " + .] + ) | .[]' "$file" 2>/dev/null || true) +} + +# Host-register over ALLE tenants, als regels "". +# Bron: elk bestand in values/tenants/, plus de bestanden op de commandoregel. +# Een bestand op de commandoregel vervangt het tenantbestand met dezelfde +# tenant.name, zodat een gewijzigde kopie niet met zichzelf botst. Zo vergelijkt +# ook `validate-values.sh <één bestand>` met de rest van de vloot. +HOST_CLAIMS="" +build_host_claims() { + local -A by_name=() + local file name + while IFS= read -r -d '' file; do + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || continue + [ -n "$name" ] && by_name["$name"]="$file" + done < <(find "$TENANT_DIR" -name "tenant-*.yaml" -print0 2>/dev/null) + for file in "$@"; do + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || continue + [ -n "$name" ] && by_name["$name"]="$file" + done + + local host bron + for name in "${!by_name[@]}"; do + while IFS=$'\t' read -r host bron; do + [ -z "$host" ] && continue + HOST_CLAIMS+="${host}"$'\t'"${name}"$'\t'"${bron}"$'\n' + done < <(tenant_host_claims "${by_name[$name]}") + done +} + +# Geen host mag door twee tenants geclaimd worden. Een tweede Ingress voor +# dezelfde host (in een andere namespace) wordt door de admission-webhook van +# ingress-nginx geweigerd, of zonder webhook samengevoegd met die van de ander: +# dan gaat verkeer naar de verkeerde Nextcloud. Binnen één tenant mag een extra +# host ook niet de host van de eigen WOO-frontend zijn (een andere Ingress). +validate_host_claims() { + local file="$1" name + name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || return 0 + [ -z "$name" ] && return 0 + + local own has_error=0 host bron other + own=$(tenant_host_claims "$file") + while IFS=$'\t' read -r host bron; do + [ -z "$host" ] && continue + while IFS=$'\t' read -r other; do + [ -z "$other" ] && continue + log_error "$file: host '$host' ($bron) wordt al geclaimd door tenant ${other}" + has_error=1 + done < <(printf '%s' "$HOST_CLAIMS" | awk -F'\t' -v h="$host" -v n="$name" \ + '$1 == h && $2 != n { print "\x27" $2 "\x27 (" $3 ")" }') + if [ "$bron" = "tenant.extraHostnames" ] \ + && printf '%s\n' "$own" | grep -qxF -e "${host}"$'\t'"tenant.frontend.host" -e "${host}"$'\t'"tenant.frontend.extraHosts"; then + log_error "$file: tenant.extraHostnames bevat '$host', de host van de eigen WOO-frontend" + has_error=1 + fi + done <<<"$own" + + return $has_error +} + # Validate wave number validate_wave() { local file="$1" @@ -730,6 +838,7 @@ validate_tenant_file() { validate_hostname "$file" || ((file_errors++)) validate_hostname_convention "$file" || ((file_errors++)) validate_extra_hostnames "$file" || ((file_errors++)) + validate_host_claims "$file" || ((file_errors++)) validate_wave "$file" || ((file_errors++)) validate_bucket "$file" || ((file_errors++)) validate_frontend_image "$file" || ((file_errors++)) @@ -764,6 +873,9 @@ main() { exit 0 fi + # Hosts van alle tenants, voor validate_host_claims + build_host_claims "$@" + echo "==========================================" echo "Validating ${#files[@]} tenant file(s)" echo "==========================================" diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.expect new file mode 100644 index 0000000..34f86a2 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.expect @@ -0,0 +1 @@ +host 'open.dinkelland.nl' (tenant.extraHostnames) wordt al geclaimd door tenant 'dinkelland-prod' (tenant.frontend.host) diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml new file mode 100644 index 0000000..0f4cb18 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - open.dinkelland.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.expect new file mode 100644 index 0000000..dad078f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.expect @@ -0,0 +1 @@ +host 'dinkelland.commonground.nu' (tenant.extraHostnames) wordt al geclaimd door tenant 'dinkelland-prod' (primaire host) diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml new file mode 100644 index 0000000..a211b8c --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - dinkelland.commonground.nu diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.expect new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.expect @@ -0,0 +1 @@ +PASS diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.yaml new file mode 100644 index 0000000..cf7d17b --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-eigen.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.commonground.nu diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.expect new file mode 100644 index 0000000..f906cd9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.expect @@ -0,0 +1 @@ +bevat 'nergens.commonground.nu' onder commonground.nu, maar die hoort niet bij tenant 'voorbeeld-prod' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.yaml new file mode 100644 index 0000000..93ef9f7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-commonground-vreemd.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - nergens.commonground.nu diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.expect new file mode 100644 index 0000000..8a80912 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat 'open.voorbeeld.nl', de host van de eigen WOO-frontend diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.yaml new file mode 100644 index 0000000..b99f661 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-eigen-frontend.yaml @@ -0,0 +1,15 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + frontend: + host: open.voorbeeld.nl + extraHostnames: + - open.voorbeeld.nl diff --git a/nextcloud-platform/tests/run-tests.sh b/nextcloud-platform/tests/run-tests.sh index e2f0177..04912d4 100755 --- a/nextcloud-platform/tests/run-tests.sh +++ b/nextcloud-platform/tests/run-tests.sh @@ -21,6 +21,10 @@ # Een fixture die per ongeluk op een ANDERE check faalt dan bedoeld wordt zo # ook gevangen: de verwachte deelstring komt dan niet voor. # +# De validator vergelijkt hosts met alle tenantbestanden in values/tenants/, +# ook bij één bestand. Fixtures die een botsing met een andere tenant toetsen +# (tenant-extra-hostnames-andere-*) leunen daarom op tenant-dinkelland-prod. +# # Writes: read-only (schrijft alleen in een eigen mktemp-map) # Idempotent: ja # Requires: bash, yq (via validate-values.sh) From 13687d421935afbfabe5b6cd51e63231f3ed7252 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 15:45:59 +0200 Subject: [PATCH 04/14] =?UTF-8?q?fix(review):=20#138=20f5=20=E2=80=94=20Se?= =?UTF-8?q?cretnamen=20botsen=20bij=20a-b.nl=20en=20a.b.nl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228465 validate_extra_hostnames weigert nu een afgeleide TLS-secretnaam die al bij een andere extra host hoort (punten worden streepjes, dus a-b.nl en a.b.nl gaven hetzelfde secret). Een dubbele host meldt alleen 'dubbel' en slaat de overige checks over, zodat hij niet ook als secretbotsing telt. Fixture tenant-extra-hostnames-zelfde-secret; slaagde tegen de PR-head-validator. Class sweep: de checklijst in docs/CHECKS-AND-BALANCES.md, changelog.d/tenant-extra-hostnames.md en de commentaarkop noemen nu een unieke secretnaam. De secretnaam van de hoofd-Ingress (nextcloud--tls) kan niet met een afgeleide naam botsen (die heeft altijd een host-deel). Co-Authored-By: Claude Opus 5.5 --- changelog.d/tenant-extra-hostnames.md | 2 +- docs/CHECKS-AND-BALANCES.md | 4 ++-- nextcloud-platform/scripts/validate-values.sh | 15 ++++++++++++--- .../tenant-extra-hostnames-zelfde-secret.expect | 1 + .../tenant-extra-hostnames-zelfde-secret.yaml | 14 ++++++++++++++ 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.yaml diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md index c8f16b4..accde9b 100644 --- a/changelog.d/tenant-extra-hostnames.md +++ b/changelog.d/tenant-extra-hostnames.md @@ -22,7 +22,7 @@ manifests per tenant byte voor byte gelijk. `validate-values.sh` controleert het nieuwe veld (lijst, geldige DNS-namen, geen dubbele, niet de primaire host, onder `commonground.nu` alleen de eigen -namen, secretnaam ≤ 253) en weigert over alle tenantbestanden heen een host die +namen, unieke secretnaam ≤ 253) en weigert over alle tenantbestanden heen een host die twee tenants claimen (primaire host, `extraHostnames`, `frontend.host`, `frontend.extraHosts`), met fixtures in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. diff --git a/docs/CHECKS-AND-BALANCES.md b/docs/CHECKS-AND-BALANCES.md index 212c8d4..4cbba7c 100644 --- a/docs/CHECKS-AND-BALANCES.md +++ b/docs/CHECKS-AND-BALANCES.md @@ -37,8 +37,8 @@ Before merge, CI runs `scripts/verify.sh`, which wraps: `validate-values.sh` also checks the optional `tenant.extraHostnames` list (a list, valid DNS names, no duplicates, not the primary host, under -`commonground.nu` only this tenant's own names, a derived TLS secret name -Kubernetes accepts). It also checks across all tenant files that no host +`commonground.nu` only this tenant's own names, a unique derived TLS secret +name Kubernetes accepts). It also checks across all tenant files that no host (primary, `extraHostnames`, `frontend.host`, `frontend.extraHosts`) is claimed by two tenants, also when it is run on a single file. A valid list still gives a warning, because DNS and HTTP-01 for those hosts are outside what CI can see. Fixtures for every branch diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 22133b9..48f6553 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -440,8 +440,8 @@ primary_host() { # trustedDomains en rendert er een tweede Ingress voor, met één TLS-secret # "nextcloud---tls" per host. Gecontroleerd: # lijstvorm, geldige DNS-naam, geen dubbele, niet gelijk aan de primaire host, -# onder commonground.nu alleen hosts van de eigen tenant, en een secretnaam die -# Kubernetes accepteert (DNS-subdomein, max 253). Botsingen met hosts van andere +# onder commonground.nu alleen hosts van de eigen tenant, en een unieke +# secretnaam die Kubernetes accepteert (DNS-subdomein, max 253). Botsingen met hosts van andere # tenants vangt validate_host_claims. validate_extra_hostnames() { local file="$1" @@ -466,7 +466,7 @@ validate_extra_hostnames() { primary_bron="afgeleid uit tenant.name, zoals de ApplicationSet doet" fi - local has_error=0 host seen=" " secret + local has_error=0 host seen=" " secret seen_secrets=" " while IFS= read -r host; do if ! [[ "$host" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ ]]; then log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host'" @@ -487,6 +487,8 @@ validate_extra_hostnames() { if [[ "$seen" == *" $host "* ]]; then log_error "$file: tenant.extraHostnames bevat '$host' dubbel" has_error=1 + # De eerste keer zijn de overige checks voor deze host al gedaan + continue fi seen="${seen}${host} " secret="nextcloud-${name}-${host//./-}-tls" @@ -494,6 +496,13 @@ validate_extra_hostnames() { log_error "$file: TLS-secretnaam '$secret' voor '$host' is langer dan 253 tekens" has_error=1 fi + # Punten worden streepjes, dus a-b.nl en a.b.nl krijgen hetzelfde + # secret: één Certificate dat maar één van beide hosts dekt. + if [[ "$seen_secrets" == *" $secret "* ]]; then + log_error "$file: TLS-secretnaam '$secret' voor '$host' is gelijk aan die van een andere extra host" + has_error=1 + fi + seen_secrets="${seen_secrets}${secret} " done < <(yq eval '.tenant.extraHostnames[]' "$file" 2>/dev/null) if [ $has_error -eq 0 ]; then diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.expect new file mode 100644 index 0000000..656bd5e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.expect @@ -0,0 +1 @@ +TLS-secretnaam 'nextcloud-voorbeeld-prod-a-b-voorbeeld-nl-tls' voor 'a.b.voorbeeld.nl' is gelijk aan die van een andere extra host diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.yaml new file mode 100644 index 0000000..f7dd437 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zelfde-secret.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - a-b.voorbeeld.nl + - a.b.voorbeeld.nl From 71e97bcd0382ec446b792b3436f674a212182c03 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 15:51:00 +0200 Subject: [PATCH 05/14] =?UTF-8?q?fix(review):=20#138=20f6=20=E2=80=94=20Ho?= =?UTF-8?q?stnaam-regex=20accepteert=20localhost,=20123=20en=20labels=20>6?= =?UTF-8?q?3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228475 Nieuwe helper is_valid_hostname: RFC 1123-labels zoals voorheen, plus minstens één punt, elk label max 63 en het geheel max 253 tekens. validate_extra_hostnames gebruikt hem; de melding noemt de regels. Class sweep: validate_hostname (tenant.hostname) gebruikte dezelfde regex en had hetzelfde gat; ook die gebruikt nu is_valid_hostname. De 89 tenants blijven groen. Fixtures: tenant-extra-hostnames-localhost, -getal (123), -label-te-lang (label van 64 tekens) en tenant-hostname-zonder-punt; alle vier slaagden tegen de PR-head-validator. Een host > 253 werd voor extraHostnames al via de secretnaamlengte gevangen, dus daar geen aparte fixture. Co-Authored-By: Claude Opus 5.5 --- nextcloud-platform/scripts/validate-values.sh | 25 ++++++++++++++++--- .../cases/tenant-extra-hostnames-getal.expect | 1 + .../cases/tenant-extra-hostnames-getal.yaml | 13 ++++++++++ ...enant-extra-hostnames-label-te-lang.expect | 1 + .../tenant-extra-hostnames-label-te-lang.yaml | 13 ++++++++++ .../tenant-extra-hostnames-localhost.expect | 1 + .../tenant-extra-hostnames-localhost.yaml | 13 ++++++++++ .../cases/tenant-hostname-zonder-punt.expect | 1 + .../cases/tenant-hostname-zonder-punt.yaml | 11 ++++++++ 9 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.expect create mode 100644 nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.yaml diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 48f6553..45cb75e 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -331,6 +331,23 @@ validate_chart_version_format() { return 0 } +# Hostnaam zoals Kubernetes hem in een Ingress-rule accepteert (RFC 1123): +# labels van kleine letters, cijfers en koppeltekens, minstens één punt, elk +# label max 63 tekens en het geheel max 253. Zonder punt of met een te lang +# label weigert de API-server de Ingress en faalt de sync van de Application. +# +# Usage: is_valid_hostname +is_valid_hostname() { + local host="$1" label + [[ "$host" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)+$ ]] || return 1 + [ ${#host} -le 253 ] || return 1 + local IFS=. + for label in $host; do + [ ${#label} -le 63 ] || return 1 + done + return 0 +} + # Validate hostname format validate_hostname() { local file="$1" @@ -343,8 +360,8 @@ validate_hostname() { fi # Basic hostname validation (RFC 1123) - if ! [[ "$hostname" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ ]]; then - log_error "$file: Invalid hostname '$hostname'. Must be a valid DNS name." + if ! is_valid_hostname "$hostname"; then + log_error "$file: Invalid hostname '$hostname'. Must be a valid DNS name (at least one dot, labels max 63, total max 253)." return 1 fi @@ -468,8 +485,8 @@ validate_extra_hostnames() { local has_error=0 host seen=" " secret seen_secrets=" " while IFS= read -r host; do - if ! [[ "$host" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ ]]; then - log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host'" + if ! is_valid_hostname "$host"; then + log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host' (kleine letters, minstens één punt, labels max 63, totaal max 253)" has_error=1 continue fi diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.expect new file mode 100644 index 0000000..aceaeda --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam '123' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.yaml new file mode 100644 index 0000000..13163e7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-getal.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - 123 diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.expect new file mode 100644 index 0000000..abcb929 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.yaml new file mode 100644 index 0000000..0f2cbc0 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-label-te-lang.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.expect new file mode 100644 index 0000000..7ad51c5 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam 'localhost' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.yaml new file mode 100644 index 0000000..45670d4 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-localhost.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - localhost diff --git a/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.expect b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.expect new file mode 100644 index 0000000..fd7d8a8 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.expect @@ -0,0 +1 @@ +Invalid hostname 'localhost' diff --git a/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.yaml b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.yaml new file mode 100644 index 0000000..b4f76d2 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-hostname-zonder-punt.yaml @@ -0,0 +1,11 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: localhost + hostnameOverride: true + apps: + enabled: + - openregister From 50934c5007b4578bb5f0194821394ff2d9eab002 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 15:52:01 +0200 Subject: [PATCH 06/14] =?UTF-8?q?fix(review):=20#138=20f7=20=E2=80=94=20Ex?= =?UTF-8?q?tra=20Ingress=20negeert=20ingress.enabled=20en=20ingress.path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228481 De extra Ingress nextcloud-extra-hosts volgt nu templates/ingress.yaml van chart 8.9.0: hij staat achter `if .Values.ingress.enabled` en neemt ingress.path en ingress.pathType via de tpl over in plaats van vast `/` en `Prefix`. Class sweep (zelfde afwijking van de chart-template): annotations en ingressClassName staan nu ook achter `with`, zoals in de chart, zodat een lege waarde geen `annotations: {}` of lege `ingressClassName:` oplevert. Docs-tabel in docs/ADDING-TENANT.md en changelog.d/tenant-extra-hostnames.md bijgewerkt. Gerenderd met helm (dinkelland-prod + extraHostnames, chart 8.9.0): met de standaardwaarden byte-identiek aan de PR-head; ingress.enabled=false geeft geen enkele Ingress meer (PR-head rendert dan nog nextcloud-extra-hosts); ingress.path=/x en pathType=ImplementationSpecific komen in beide Ingresses. Co-Authored-By: Claude Opus 5.5 --- changelog.d/tenant-extra-hostnames.md | 6 ++--- docs/ADDING-TENANT.md | 2 +- .../applicationsets/nextcloud-tenants.yaml | 25 ++++++++++++------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md index accde9b..303e66a 100644 --- a/changelog.d/tenant-extra-hostnames.md +++ b/changelog.d/tenant-extra-hostnames.md @@ -10,9 +10,9 @@ Een tenant kan nu op meer dan één hostnaam antwoorden. Nieuw optioneel veld (`ingress.hosts` uit de ApplicationSet leest die chart niet); - geeft elke extra host een eigen TLS-blok en dus een eigen Certificate via HTTP-01, zodat één host met nog niet kloppende DNS de andere niet tegenhoudt; -- neemt annotaties, `ingressClassName` en poort via de `tpl` van de chart over - uit dezelfde values als de hoofd-Ingress: geen tweede kopie van de - nginx-instellingen. +- neemt `ingress.enabled`, annotaties, `ingressClassName`, `path`, `pathType` + en poort via de `tpl` van de chart over uit dezelfde values als de + hoofd-Ingress: geen tweede kopie van de nginx-instellingen. **Bestaande tenants renderen byte-identiek.** Het blok wordt alleen geëmit als de lijst gezet is. Gemeten: alle 89 tenantbestanden op main en op deze branch diff --git a/docs/ADDING-TENANT.md b/docs/ADDING-TENANT.md index feb6ead..1ca89ac 100644 --- a/docs/ADDING-TENANT.md +++ b/docs/ADDING-TENANT.md @@ -202,7 +202,7 @@ What the ApplicationSet does with it (`argo/applicationsets/nextcloud-tenants.ya | `trusted_domains` | every extra host is added to `nextcloud.trustedDomains`, after the primary host | | routing | a second Ingress `nextcloud-extra-hosts`, because chart 8.9.0 puts only `nextcloud.host` in its own Ingress rules | | certificates | one TLS entry per host, secret `nextcloud---tls`, so one cert-manager Certificate per host via HTTP-01 (`letsencrypt-prod`). A host whose DNS is not ready does not block the others | -| annotations | taken from `ingress.annotations`, `ingress.className` and `service.port` through the chart's `tpl`, so they always match the main Ingress | +| annotations | taken from `ingress.annotations`, `ingress.className`, `ingress.path`, `ingress.pathType` and `service.port` through the chart's `tpl`, so they always match the main Ingress; with `ingress.enabled: false` neither Ingress is rendered | A tenant without `extraHostnames` renders byte-identical to before; the block is only emitted when the list is set. diff --git a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml index 6dc3b9f..e34d15f 100644 --- a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml +++ b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml @@ -193,13 +193,15 @@ spec: # De chart (8.9.0) zet in zijn eigen Ingress alleen nextcloud.host # als rule, dus extra hosts krijgen hier een tweede Ingress. Eén # TLS-blok per host, dus één Certificate per host: een host waarvan - # DNS nog niet klopt houdt de andere niet tegen. Annotaties, - # ingressClassName en poort komen via de tpl van de chart uit - # dezelfde values als de hoofd-Ingress, dus geen tweede kopie van - # de nginx-instellingen. Alleen geëmit als de lijst gezet is: - # bestaande tenants renderen byte-identiek. + # DNS nog niet klopt houdt de andere niet tegen. ingress.enabled, + # annotaties, ingressClassName, path, pathType en poort komen via + # de tpl van de chart uit dezelfde values als de hoofd-Ingress, + # op dezelfde manier als templates/ingress.yaml van de chart, dus + # geen tweede kopie van de nginx-instellingen. Alleen geëmit als + # de lijst gezet is: bestaande tenants renderen byte-identiek. extraManifests: - | + {{`{{- if .Values.ingress.enabled }}`}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -208,10 +210,14 @@ spec: app.kubernetes.io/name: nextcloud app.kubernetes.io/part-of: nextcloud-platform nextcloud.platform/tenant: "{{ $name }}" + {{`{{- with .Values.ingress.annotations }}`}} annotations: - {{`{{- toYaml .Values.ingress.annotations | nindent 4 }}`}} + {{`{{- toYaml . | nindent 4 }}`}} + {{`{{- end }}`}} spec: - ingressClassName: {{`{{ .Values.ingress.className }}`}} + {{`{{- with .Values.ingress.className }}`}} + ingressClassName: {{`{{ . }}`}} + {{`{{- end }}`}} tls: {{- range $extraHosts }} - secretName: "nextcloud-{{ $name }}-{{ replace "." "-" . }}-tls" @@ -223,14 +229,15 @@ spec: - host: "{{ . }}" http: paths: - - path: / - pathType: Prefix + - path: {{`{{ .Values.ingress.path }}`}} + pathType: {{`{{ .Values.ingress.pathType }}`}} backend: service: name: nextcloud port: number: {{`{{ .Values.service.port }}`}} {{- end }} + {{`{{- end }}`}} {{- end }} # Our GitOps repo for values files From 72bb9e384b906face16506696dbcb37dc4282bd1 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 15:53:37 +0200 Subject: [PATCH 07/14] =?UTF-8?q?docs(review):=20#138=20f8=20=E2=80=94=20H?= =?UTF-8?q?ost=20verwijderen=20blijft=20in=20trusted=5Fdomains=20staan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228492 docs/ADDING-TENANT.md: een host uit extraHostnames halen prunet de Ingress, maar de host blijft in config.php tot `occ config:system:delete trusted_domains `. Eerst `occ config:system:get trusted_domains` draaien om de juiste te vinden, want `set` op een bezette index overschrijft die entry. Class sweep: dezelfde beperking staat in changelog.d/tenant-extra-hostnames.md en noemt nu ook het verwijderen. Co-Authored-By: Claude Opus 5.5 --- changelog.d/tenant-extra-hostnames.md | 4 +++- docs/ADDING-TENANT.md | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md index 303e66a..668f0b0 100644 --- a/changelog.d/tenant-extra-hostnames.md +++ b/changelog.d/tenant-extra-hostnames.md @@ -28,5 +28,7 @@ twee tenants claimen (primaire host, `extraHostnames`, `frontend.host`, `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. Bekende beperkingen: `trusted_domains` wordt alleen bij de eerste installatie -geschreven; `charts/tenant-httproute` neemt nog één host. Zie +geschreven, en een verwijderde host blijft erin staan tot een +`occ config:system:delete trusted_domains `; `charts/tenant-httproute` neemt +nog één host. Zie `docs/ADDING-TENANT.md`. diff --git a/docs/ADDING-TENANT.md b/docs/ADDING-TENANT.md index 1ca89ac..3256b4f 100644 --- a/docs/ADDING-TENANT.md +++ b/docs/ADDING-TENANT.md @@ -220,7 +220,11 @@ Four things to know: reads `NEXTCLOUD_TRUSTED_DOMAINS` when it installs; adding a host to a running tenant later also needs `occ config:system:set trusted_domains --value=` in the pod (same pattern as `cutover-tenant.sh`). Put the full list in the file - before the tenant is first created. + before the tenant is first created. Removing a host from the list works the + same way: Argo prunes the Ingress, but the host stays in `config.php` until + you run `occ config:system:delete trusted_domains `. In both cases run + `occ config:system:get trusted_domains` first to find the right ``: `set` + on an index that is already in use overwrites that entry. - **Gateway API routes do not use this list.** `charts/tenant-httproute` takes one host; a tenant with `gateway.nextcloud: true` gets a route for the primary host only. From bf29d92078e48d0ffc71692aafd82fdbc647a752 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 16:14:53 +0200 Subject: [PATCH 08/14] =?UTF-8?q?fix(review):=20#138=20f7=20=E2=80=94=20pr?= =?UTF-8?q?e-push:=20extraHostnames=20vereist=20ingress.enabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228481 Sinds f7 volgt nextcloud-extra-hosts ingress.enabled, net als de chart. Met ingress.enabled: false (de Gateway-cutover) hadden de extra hosts dan geen enkele route, want charts/tenant-httproute neemt alleen de primaire host. validate_extra_hostnames weigert nu extraHostnames samen met een effectieve ingress.enabled die niet true is. effective_ingress_enabled leest de waarde in dezelfde valueFiles-volgorde als de ApplicationSet (common, env, db, canary-overrides als tenant.canary, tenantbestand; de inline values zetten hem niet). Alle 89 tenants: true, gelijk aan de renders (89 met Ingress). Fixture tenant-extra-hostnames-zonder-ingress; met de check weggehaald slaagt hij (mutatie gecontroleerd). docs/GATEWAY-API.md (extraHostnames-alinea en de cutover-alinea) en docs/ADDING-TENANT.md zeggen het nu. Co-Authored-By: Claude Opus 5.5 --- docs/ADDING-TENANT.md | 5 +++- docs/GATEWAY-API.md | 11 +++++-- nextcloud-platform/scripts/validate-values.sh | 29 +++++++++++++++++++ ...nant-extra-hostnames-zonder-ingress.expect | 1 + ...tenant-extra-hostnames-zonder-ingress.yaml | 15 ++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.yaml diff --git a/docs/ADDING-TENANT.md b/docs/ADDING-TENANT.md index 3256b4f..f939ea6 100644 --- a/docs/ADDING-TENANT.md +++ b/docs/ADDING-TENANT.md @@ -227,7 +227,10 @@ Four things to know: on an index that is already in use overwrites that entry. - **Gateway API routes do not use this list.** `charts/tenant-httproute` takes one host; a tenant with `gateway.nextcloud: true` gets a route for the primary host - only. + only. The extra hosts stay on the nginx Ingress, and that Ingress follows + `ingress.enabled` like the chart's own: `ingress.enabled: false` would leave + them without any route, so `validate-values.sh` refuses `extraHostnames` + together with `ingress.enabled: false`. Validate before pushing: diff --git a/docs/GATEWAY-API.md b/docs/GATEWAY-API.md index 554ba1b..6c1b456 100644 --- a/docs/GATEWAY-API.md +++ b/docs/GATEWAY-API.md @@ -50,7 +50,10 @@ blok dat altijd meegaat zou alle 84 Applications tegelijk laten hersyncen. `tenant.extraHostnames` (zie `docs/ADDING-TENANT.md`) gaat hier niet mee: de route krijgt alleen de primaire host. Een tenant met extra hostnamen blijft voor die hosts op de nginx-Ingress tot deze chart een lijst hostnames aanneemt, met -per host een listener op de Gateway. +per host een listener op de Gateway. Die Ingress (`nextcloud-extra-hosts`) volgt +`ingress.enabled`, net als de hoofd-Ingress van de chart. `validate-values.sh` +weigert daarom `extraHostnames` samen met `ingress.enabled: false`: de extra +hosts zouden dan geen enkele route meer hebben. ## Waarom dit nog niet schaalt @@ -94,8 +97,10 @@ De HTTPRoute komt náást de Ingress. Het verkeer verschuift **niet**: het DNS-record blijft naar ingress-nginx wijzen, want external-dns laat een bestaand record met rust zolang de Ingress bestaat. Gemeten 2026-08-17. -De cutover is dus het weghalen van de Ingress. Doe dat niet lichtvaardig: het -certificaat van deze host wordt via HTTP-01 over díé Ingress vernieuwd. Verdwijnt +De cutover is dus het weghalen van de Ingress. Heeft de tenant +`tenant.extraHostnames`, dan kan dat nog niet: `ingress.enabled: false` haalt ook +de Ingress van de extra hosts weg, en de validator weigert die combinatie. Doe +het ook verder niet lichtvaardig: het certificaat van deze host wordt via HTTP-01 over díé Ingress vernieuwd. Verdwijnt hij voordat cert-manager's `--enable-gateway-api` aanstaat (die vlag staat nu uit), dan breekt de vernieuwing stil en pas bij de eerstvolgende renewal. diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 45cb75e..9f7ca13 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -451,6 +451,27 @@ primary_host() { fi } +# ingress.enabled zoals de render hem ziet: dezelfde valueFiles-volgorde als de +# ApplicationSet (common, env, db, canary-overrides, tenantbestand), de laatste +# die hem zet wint. De inline values van de ApplicationSet zetten hem niet. +# +# Usage: effective_ingress_enabled +effective_ingress_enabled() { + local file="$1" values="${REPO_ROOT}/values" env db canary value f result="false" + env=$(yq eval '.tenant.environment // ""' "$file" 2>/dev/null) + db=$(yq eval '.tenant.dbType // "postgres"' "$file" 2>/dev/null) + canary=$(yq eval '.tenant.canary // ""' "$file" 2>/dev/null) + local layers=("${values}/common.yaml" "${values}/env/${env}.yaml" "${values}/db/${db}.yaml") + [ "$canary" = "true" ] && layers+=("${values}/canary-overrides.yaml") + layers+=("$file") + for f in "${layers[@]}"; do + [ -f "$f" ] || continue + value=$(yq eval '.ingress.enabled' "$f" 2>/dev/null) + [ "$value" != "null" ] && [ -n "$value" ] && result="$value" + done + echo "$result" +} + # Validate tenant.extraHostnames (optioneel) # # Extra hostnamen op dezelfde Nextcloud. De ApplicationSet zet ze in @@ -526,6 +547,14 @@ validate_extra_hostnames() { log_warning "$file: tenant.extraHostnames gezet; DNS en HTTP-01 per host moeten kloppen (zie docs/ADDING-TENANT.md)" fi + # De extra Ingress volgt ingress.enabled, net als die van de chart. Zonder + # Ingress hebben de extra hosts geen enkele route: charts/tenant-httproute + # neemt alleen de primaire host. + if [ "$(effective_ingress_enabled "$file")" != "true" ]; then + log_error "$file: tenant.extraHostnames vereist ingress.enabled: true. De extra hosts lopen via de nginx-Ingress zolang charts/tenant-httproute maar één host neemt (zie docs/GATEWAY-API.md)" + has_error=1 + fi + return $has_error } diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.expect new file mode 100644 index 0000000..09497b3 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.expect @@ -0,0 +1 @@ +tenant.extraHostnames vereist ingress.enabled: true diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.yaml new file mode 100644 index 0000000..c83cde7 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-zonder-ingress.yaml @@ -0,0 +1,15 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - www.voorbeeld.nl +ingress: + enabled: false From ab064c71051d9502bb4dc7a093a663ed3465c766 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 16:21:48 +0200 Subject: [PATCH 09/14] =?UTF-8?q?fix(review):=20#138=20f1=20=E2=80=94=20pr?= =?UTF-8?q?e-push:=20dubbele=20tenant.name=20en=20register=20=C3=A9=C3=A9n?= =?UTF-8?q?=20keer=20bouwen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228400 Dubbele tenant.name: build_host_claims hield één bestand per naam aan, dus een tweede tenantbestand met dezelfde naam viel stil uit de botsingscheck. dump_host_claims (het register van values/tenants/) geeft nu een error voor elk tweede bestand daar met een bestaande naam. Alleen een bestand BUITEN values/tenants/ (fixture, gewijzigde kopie) vervangt de tenant met dezelfde naam; een bestand binnen values/tenants/ wordt nooit als vervanger gebruikt, dus de omweg bestaat niet meer. Twee bestanden buiten de map met dezelfde naam zijn ook een error. Getest met een tijdelijke kopie in values/tenants/ (error, daarna verwijderd) en met fixture tenant-dubbele-naam, die een eigen vloot heeft in tenant-dubbele-naam.fleet/ (run-tests.sh zet daarvoor VALIDATE_TENANT_DIR). Met de dup-check weggehaald wordt die fixture rood. Looptijd: run-tests.sh bouwde per case het register over 89 bestanden opnieuw (~2,8 s per case). De validator accepteert nu VALIDATE_HOST_CLAIMS_FILE (geschreven met `validate-values.sh --dump-host-claims`); run-tests.sh bouwt het één keer. Ontbreekt het bestand, dan bouwt de validator het register zelf. Eén fixture: 2,8 s -> 0,3 s; hele suite (34 cases): ~14 s. Co-Authored-By: Claude Opus 5.5 --- nextcloud-platform/scripts/validate-values.sh | 94 ++++++++++++++++--- .../tests/cases/tenant-dubbele-naam.expect | 1 + .../tenant-zelfde-prod-a.yaml | 9 ++ .../tenant-zelfde-prod-b.yaml | 9 ++ .../tests/cases/tenant-dubbele-naam.yaml | 14 +++ nextcloud-platform/tests/run-tests.sh | 22 ++++- 6 files changed, 133 insertions(+), 16 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-dubbele-naam.expect create mode 100644 nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-a.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-b.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-dubbele-naam.yaml diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 9f7ca13..040eeca 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -11,12 +11,14 @@ # Usage: # ./scripts/validate-values.sh [tenant-file.yaml ...] # ./scripts/validate-values.sh # validates all tenants +# ./scripts/validate-values.sh --dump-host-claims # host-register (zie build_host_claims) set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -TENANT_DIR="${REPO_ROOT}/values/tenants" +# VALIDATE_TENANT_DIR: alleen voor tests/run-tests.sh (fixtures met een eigen vloot) +TENANT_DIR="${VALIDATE_TENANT_DIR:-${REPO_ROOT}/values/tenants}" # Colors for output RED='\033[0;31m' @@ -600,30 +602,85 @@ tenant_host_claims() { ) | .[]' "$file" 2>/dev/null || true) } -# Host-register over ALLE tenants, als regels "". -# Bron: elk bestand in values/tenants/, plus de bestanden op de commandoregel. -# Een bestand op de commandoregel vervangt het tenantbestand met dezelfde -# tenant.name, zodat een gewijzigde kopie niet met zichzelf botst. Zo vergelijkt -# ook `validate-values.sh <één bestand>` met de rest van de vloot. -HOST_CLAIMS="" -build_host_claims() { +# Absoluut pad met symlinks opgelost, zonder GNU realpath +abs_path() { + local dir + dir=$(cd "$(dirname "$1")" 2>/dev/null && pwd -P) || { echo "$1"; return 0; } + echo "${dir}/$(basename "$1")" +} + +# Host-register van values/tenants/, als regels "". +# Twee bestanden met dezelfde tenant.name zijn een fout: het register (en +# daarmee de botsingscheck) zou er stil één van overslaan. +dump_host_claims() { local -A by_name=() - local file name + local file name has_error=0 host bron while IFS= read -r -d '' file; do name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || continue - [ -n "$name" ] && by_name["$name"]="$file" - done < <(find "$TENANT_DIR" -name "tenant-*.yaml" -print0 2>/dev/null) + [ -z "$name" ] && continue + if [ -n "${by_name[$name]:-}" ]; then + log_error "$file: tenant.name '$name' staat ook in ${by_name[$name]}" + has_error=$((has_error + 1)) + continue + fi + by_name["$name"]="$file" + done < <(find "$TENANT_DIR" -name "tenant-*.yaml" -print0 2>/dev/null | sort -z) + + for name in "${!by_name[@]}"; do + while IFS=$'\t' read -r host bron; do + [ -z "$host" ] && continue + printf '%s\t%s\t%s\n' "$host" "$name" "$bron" + done < <(tenant_host_claims "${by_name[$name]}") + done + return $has_error +} + +# Host-register over ALLE tenants: dat van values/tenants/, plus de bestanden op +# de commandoregel. Een bestand BUITEN values/tenants/ (een fixture, een +# gewijzigde kopie) vervangt het tenantbestand met dezelfde tenant.name, zodat +# het niet met zichzelf botst. Zo vergelijkt ook `validate-values.sh <één +# bestand>` met de rest van de vloot. Een bestand BINNEN values/tenants/ staat +# al in het register; een tweede daar met een bestaande naam is een fout. +# +# VALIDATE_HOST_CLAIMS_FILE: een register dat `validate-values.sh +# --dump-host-claims` eerder schreef (run-tests.sh bouwt het één keer). Ontbreekt +# het bestand, dan wordt het register gewoon opnieuw opgebouwd. +HOST_CLAIMS="" +build_host_claims() { + local cache="${VALIDATE_HOST_CLAIMS_FILE:-}" + if [ -n "$cache" ] && [ -f "$cache" ]; then + HOST_CLAIMS=$(cat "$cache") + else + # Draait in een subshell: tel de fouten hier bij (de meldingen zelf + # staan al op stderr) + local dups=0 + HOST_CLAIMS=$(dump_host_claims) || dups=$? + ERRORS=$((ERRORS + dups)) + fi + [ -n "$HOST_CLAIMS" ] && HOST_CLAIMS+=$'\n' + + local tenant_dir file path name host bron + tenant_dir=$(cd "$TENANT_DIR" 2>/dev/null && pwd -P) || tenant_dir="$TENANT_DIR" + local -A overlay=() for file in "$@"; do + path=$(abs_path "$file") + [[ "$path" == "${tenant_dir}/"* ]] && continue name=$(yq eval '.tenant.name // ""' "$file" 2>/dev/null) || continue - [ -n "$name" ] && by_name["$name"]="$file" + [ -z "$name" ] && continue + if [ -n "${overlay[$name]:-}" ] && [ "${overlay[$name]}" != "$path" ]; then + log_error "$file: tenant.name '$name' staat ook in ${overlay[$name]}" + continue + fi + overlay["$name"]="$path" done - local host bron - for name in "${!by_name[@]}"; do + for name in "${!overlay[@]}"; do + HOST_CLAIMS=$(printf '%s' "$HOST_CLAIMS" | awk -F'\t' -v n="$name" '$2 != n') + [ -n "$HOST_CLAIMS" ] && HOST_CLAIMS+=$'\n' while IFS=$'\t' read -r host bron; do [ -z "$host" ] && continue HOST_CLAIMS+="${host}"$'\t'"${name}"$'\t'"${bron}"$'\n' - done < <(tenant_host_claims "${by_name[$name]}") + done < <(tenant_host_claims "${overlay[$name]}") done } @@ -911,6 +968,13 @@ validate_tenant_file() { # Main main() { check_dependencies + + # --dump-host-claims: schrijf het host-register van values/tenants/ naar + # stdout, voor VALIDATE_HOST_CLAIMS_FILE (zie build_host_claims) + if [ "${1:-}" = "--dump-host-claims" ]; then + dump_host_claims + exit $? + fi local files=() diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.expect b/nextcloud-platform/tests/cases/tenant-dubbele-naam.expect new file mode 100644 index 0000000..d3b4a95 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.expect @@ -0,0 +1 @@ +tenant.name 'zelfde-prod' staat ook in diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-a.yaml b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-a.yaml new file mode 100644 index 0000000..b5de65e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-a.yaml @@ -0,0 +1,9 @@ +--- +tenant: + name: zelfde-prod + environment: prod + wave: "1" + dbType: postgres + apps: + enabled: + - openregister diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-b.yaml b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-b.yaml new file mode 100644 index 0000000..b5de65e --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.fleet/tenant-zelfde-prod-b.yaml @@ -0,0 +1,9 @@ +--- +tenant: + name: zelfde-prod + environment: prod + wave: "1" + dbType: postgres + apps: + enabled: + - openregister diff --git a/nextcloud-platform/tests/cases/tenant-dubbele-naam.yaml b/nextcloud-platform/tests/cases/tenant-dubbele-naam.yaml new file mode 100644 index 0000000..f0fa171 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-dubbele-naam.yaml @@ -0,0 +1,14 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/run-tests.sh b/nextcloud-platform/tests/run-tests.sh index 04912d4..219fead 100755 --- a/nextcloud-platform/tests/run-tests.sh +++ b/nextcloud-platform/tests/run-tests.sh @@ -24,6 +24,12 @@ # De validator vergelijkt hosts met alle tenantbestanden in values/tenants/, # ook bij één bestand. Fixtures die een botsing met een andere tenant toetsen # (tenant-extra-hostnames-andere-*) leunen daarom op tenant-dinkelland-prod. +# Dat register bouwt deze runner één keer (VALIDATE_HOST_CLAIMS_FILE), niet per +# case. +# +# .fleet/ optioneel: een eigen map met tenantbestanden die voor deze +# case values/tenants/ vervangt (VALIDATE_TENANT_DIR), voor +# checks over de hele vloot, zoals een dubbele tenant.name. # # Writes: read-only (schrijft alleen in een eigen mktemp-map) # Idempotent: ja @@ -66,7 +72,11 @@ run_case() { # validate-values.sh geeft non-zero bij fouten; dat is hier data, geen crash. set +e - output="$(bash "$VALIDATOR" "$yaml" 2>&1)" + if [[ -d "${CASES_DIR}/${name}.fleet" ]]; then + output="$(VALIDATE_TENANT_DIR="${CASES_DIR}/${name}.fleet" bash "$VALIDATOR" "$yaml" 2>&1)" + else + output="$(VALIDATE_HOST_CLAIMS_FILE="$CLAIMS_FILE" bash "$VALIDATOR" "$yaml" 2>&1)" + fi rc=$? set -e # ANSI-kleuren eruit, anders matchen de deelstrings niet @@ -119,6 +129,16 @@ main() { exit 1 fi + # Host-register van values/tenants/ één keer opbouwen in plaats van per + # case. Globaal: de EXIT-trap draait buiten deze functie. + TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$TMP_DIR"' EXIT + CLAIMS_FILE="${TMP_DIR}/host-claims.tsv" + if ! bash "$VALIDATOR" --dump-host-claims >"$CLAIMS_FILE"; then + echo "error: host-register van values/tenants/ niet op te bouwen" >&2 + exit 1 + fi + local yaml for yaml in "${CASES_DIR}"/*.yaml; do [[ -e "$yaml" ]] || continue From 6da1eaa47695b7328a7af65f82e4132ff447b1b8 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 16:22:54 +0200 Subject: [PATCH 10/14] =?UTF-8?q?fix(review):=20#138=20f6=20=E2=80=94=20pr?= =?UTF-8?q?e-push:=20IP-adres=20als=20hostnaam=20weigeren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228475 is_valid_hostname accepteerde 81.24.6.82; Kubernetes neemt geen IP-adres als Ingress-host. Een naam waarvan het laatste label alleen cijfers is, wordt nu geweigerd. Geldt via de helper voor zowel tenant.extraHostnames als tenant.hostname; de meldingen noemen het. Fixture tenant-extra-hostnames-ip-adres; tegen de validator van de vorige commit slaagt hij (mutatie gecontroleerd). De 89 tenants blijven groen. Co-Authored-By: Claude Opus 5.5 --- nextcloud-platform/scripts/validate-values.sh | 10 ++++++---- .../cases/tenant-extra-hostnames-ip-adres.expect | 1 + .../cases/tenant-extra-hostnames-ip-adres.yaml | 13 +++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.yaml diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 040eeca..e9bb936 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -335,14 +335,16 @@ validate_chart_version_format() { # Hostnaam zoals Kubernetes hem in een Ingress-rule accepteert (RFC 1123): # labels van kleine letters, cijfers en koppeltekens, minstens één punt, elk -# label max 63 tekens en het geheel max 253. Zonder punt of met een te lang -# label weigert de API-server de Ingress en faalt de sync van de Application. +# label max 63 tekens en het geheel max 253, en het laatste label niet alleen +# cijfers (dan is het een IP-adres, dat Kubernetes niet als Ingress-host neemt). +# Anders weigert de API-server de Ingress en faalt de sync van de Application. # # Usage: is_valid_hostname is_valid_hostname() { local host="$1" label [[ "$host" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)+$ ]] || return 1 [ ${#host} -le 253 ] || return 1 + [[ "${host##*.}" =~ ^[0-9]+$ ]] && return 1 local IFS=. for label in $host; do [ ${#label} -le 63 ] || return 1 @@ -363,7 +365,7 @@ validate_hostname() { # Basic hostname validation (RFC 1123) if ! is_valid_hostname "$hostname"; then - log_error "$file: Invalid hostname '$hostname'. Must be a valid DNS name (at least one dot, labels max 63, total max 253)." + log_error "$file: Invalid hostname '$hostname'. Must be a valid DNS name (at least one dot, labels max 63, total max 253, not an IP address)." return 1 fi @@ -509,7 +511,7 @@ validate_extra_hostnames() { local has_error=0 host seen=" " secret seen_secrets=" " while IFS= read -r host; do if ! is_valid_hostname "$host"; then - log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host' (kleine letters, minstens één punt, labels max 63, totaal max 253)" + log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host' (kleine letters, minstens één punt, labels max 63, totaal max 253, geen IP-adres)" has_error=1 continue fi diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.expect new file mode 100644 index 0000000..02f0ce5 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.expect @@ -0,0 +1 @@ +tenant.extraHostnames bevat ongeldige hostnaam '81.24.6.82' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.yaml new file mode 100644 index 0000000..e731742 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-ip-adres.yaml @@ -0,0 +1,13 @@ +--- +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - 81.24.6.82 From fd6c9c2191f39146c9f2d344ed83911cebf35bfe Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 16:22:58 +0200 Subject: [PATCH 11/14] =?UTF-8?q?test(review):=20#138=20f1=20=E2=80=94=20p?= =?UTF-8?q?re-push:=20afhankelijkheid=20van=20dinkelland=20in=20fixtures?= =?UTF-8?q?=20noemen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228400 De fixtures tenant-extra-hostnames-andere-tenant en -andere-frontend toetsen een botsing met tenant-dinkelland-prod. Een regel bovenin zegt nu dat ze breken als de hosts van dinkelland veranderen. Co-Authored-By: Claude Opus 5.5 --- .../tests/cases/tenant-extra-hostnames-andere-frontend.yaml | 1 + .../tests/cases/tenant-extra-hostnames-andere-tenant.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml index 0f4cb18..335da83 100644 --- a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-frontend.yaml @@ -1,4 +1,5 @@ --- +# Leunt op values/tenants/tenant-dinkelland-prod.yaml: breekt als de hosts van dinkelland veranderen. tenant: name: voorbeeld-prod environment: prod diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml index a211b8c..a41911d 100644 --- a/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-andere-tenant.yaml @@ -1,4 +1,5 @@ --- +# Leunt op values/tenants/tenant-dinkelland-prod.yaml: breekt als de hosts van dinkelland veranderen. tenant: name: voorbeeld-prod environment: prod From 86e8c7f7190e55396c8a5a2b3ac8e2dffcecd361 Mon Sep 17 00:00:00 2001 From: Robert Zondervan Date: Wed, 14 Oct 2026 16:36:08 +0200 Subject: [PATCH 12/14] =?UTF-8?q?docs(review):=20#138=20f1=20=E2=80=94=20p?= =?UTF-8?q?re-push:=20check=20lists=20bijgewerkt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/ConductionNL/Nextcloud-base/pull/138#discussion_r4271228400 De checklijsten noemen nu ook de checks uit de pre-push-ronde: extraHostnames niet samen met ingress.enabled: false, geen IP-adres als host (ook voor tenant.hostname), en geen dubbele tenant.name over tenantbestanden heen. Bijgewerkt in docs/CHECKS-AND-BALANCES.md, changelog.d/tenant-extra-hostnames.md en de commentaarkop van validate_extra_hostnames (git grep: geen andere lijsten). Co-Authored-By: Claude Opus 5.5 --- changelog.d/tenant-extra-hostnames.md | 12 +++++++----- docs/CHECKS-AND-BALANCES.md | 12 +++++++----- nextcloud-platform/scripts/validate-values.sh | 9 +++++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md index 668f0b0..a5de288 100644 --- a/changelog.d/tenant-extra-hostnames.md +++ b/changelog.d/tenant-extra-hostnames.md @@ -20,11 +20,13 @@ gerenderd (Argo-goTemplate met sprig en `missingkey=default`, daarna `helm template` met chart 8.9.0 en dezelfde valueFiles-volgorde), values en manifests per tenant byte voor byte gelijk. -`validate-values.sh` controleert het nieuwe veld (lijst, geldige DNS-namen, -geen dubbele, niet de primaire host, onder `commonground.nu` alleen de eigen -namen, unieke secretnaam ≤ 253) en weigert over alle tenantbestanden heen een host die -twee tenants claimen (primaire host, `extraHostnames`, `frontend.host`, -`frontend.extraHosts`), met fixtures in +`validate-values.sh` controleert het nieuwe veld (lijst, geldige DNS-namen en +geen IP-adres, geen dubbele, niet de primaire host, onder `commonground.nu` +alleen de eigen namen, unieke secretnaam ≤ 253, niet samen met +`ingress.enabled: false`). Ook `tenant.hostname` mag geen IP-adres meer zijn. +Over alle tenantbestanden heen weigert hij een host die twee tenants claimen +(primaire host, `extraHostnames`, `frontend.host`, `frontend.extraHosts`) en +een `tenant.name` die in twee bestanden staat, met fixtures in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. Bekende beperkingen: `trusted_domains` wordt alleen bij de eerste installatie diff --git a/docs/CHECKS-AND-BALANCES.md b/docs/CHECKS-AND-BALANCES.md index 4cbba7c..b2ced81 100644 --- a/docs/CHECKS-AND-BALANCES.md +++ b/docs/CHECKS-AND-BALANCES.md @@ -36,11 +36,13 @@ Before merge, CI runs `scripts/verify.sh`, which wraps: - `nextcloud-platform/scripts/smoke-checks.sh` — Helm chart renders `validate-values.sh` also checks the optional `tenant.extraHostnames` list (a -list, valid DNS names, no duplicates, not the primary host, under -`commonground.nu` only this tenant's own names, a unique derived TLS secret -name Kubernetes accepts). It also checks across all tenant files that no host -(primary, `extraHostnames`, `frontend.host`, `frontend.extraHosts`) is claimed -by two tenants, also when it is run on a single file. A valid list still gives a warning, because DNS and +list, valid DNS names and no IP address, no duplicates, not the primary host, +under `commonground.nu` only this tenant's own names, a unique derived TLS +secret name Kubernetes accepts, and not together with `ingress.enabled: false`). +`tenant.hostname` is refused as an IP address too. It also checks across all +tenant files that no host (primary, `extraHostnames`, `frontend.host`, +`frontend.extraHosts`) is claimed by two tenants, also when it is run on a +single file, and that no two tenant files share a `tenant.name`. A valid list still gives a warning, because DNS and HTTP-01 for those hosts are outside what CI can see. Fixtures for every branch are in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index e9bb936..4ccab33 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -481,10 +481,11 @@ effective_ingress_enabled() { # Extra hostnamen op dezelfde Nextcloud. De ApplicationSet zet ze in # trustedDomains en rendert er een tweede Ingress voor, met één TLS-secret # "nextcloud---tls" per host. Gecontroleerd: -# lijstvorm, geldige DNS-naam, geen dubbele, niet gelijk aan de primaire host, -# onder commonground.nu alleen hosts van de eigen tenant, en een unieke -# secretnaam die Kubernetes accepteert (DNS-subdomein, max 253). Botsingen met hosts van andere -# tenants vangt validate_host_claims. +# lijstvorm, geldige DNS-naam (geen IP-adres), geen dubbele, niet gelijk aan de +# primaire host, onder commonground.nu alleen hosts van de eigen tenant, een +# unieke secretnaam die Kubernetes accepteert (DNS-subdomein, max 253), en +# ingress.enabled: true. Botsingen met hosts van andere tenants vangt +# validate_host_claims; een dubbele tenant.name vangt dump_host_claims. validate_extra_hostnames() { local file="$1" local kind name hostname primary primary_bron From cb163ee7196421f26b763d189f1f63d53321662b Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 14 Oct 2026 16:59:09 +0200 Subject: [PATCH 13/14] fix(extra-hostnames): #138 f3, apex-host alleen met opt-in en zonder includeSubDomains Een extra host met twee labels (gemeente.nl) wordt geweigerd tenzij de tenant tenant.extraHostnamesAllowApex: true zet. HSTS met includeSubDomains op een apex dwingt HTTPS af op elk subdomein van de klant, en dat is in een browser niet terug te draaien. Apex-hosts krijgen een eigen Ingress nextcloud-extra-hosts-apex met nginx.ingress.kubernetes.io/hsts-include-subdomains: "false" over de annotaties heen (merge in een nieuwe dict, de gedeelde values blijven ongemoeid). De hoofd-Ingress en nextcloud-extra-hosts houden hun waarde. Fixtures: apex zonder opt-in faalt, met opt-in slaagt, opt-in false en opt-in als tekst falen. Nieuwe render-check tests/render-extra-hosts.sh (in verify.sh) rendert de fixtures via de ApplicationSet en chart 8.9.0 en controleert de Ingresses. De fixture "geldig" heeft geen kale apex meer. Alle 89 tenants renderen byte-identiek aan a17181e (values en manifests). --- .../applicationsets/nextcloud-tenants.yaml | 31 +++- nextcloud-platform/scripts/validate-values.sh | 24 ++- ...nant-extra-hostnames-apex-met-optin.expect | 1 + ...tenant-extra-hostnames-apex-met-optin.yaml | 16 ++ ...nt-extra-hostnames-apex-optin-false.expect | 1 + ...nant-extra-hostnames-apex-optin-false.yaml | 15 ++ ...nt-extra-hostnames-apex-optin-tekst.expect | 2 + ...nant-extra-hostnames-apex-optin-tekst.yaml | 15 ++ ...t-extra-hostnames-apex-zonder-optin.expect | 1 + ...ant-extra-hostnames-apex-zonder-optin.yaml | 15 ++ .../cases/tenant-extra-hostnames-geldig.yaml | 2 +- .../tests/render-extra-hosts.sh | 162 ++++++++++++++++++ scripts/verify.sh | 11 +- 13 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.yaml create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.expect create mode 100644 nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.yaml create mode 100755 nextcloud-platform/tests/render-extra-hosts.sh diff --git a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml index e34d15f..f9ff4e1 100644 --- a/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml +++ b/nextcloud-platform/argo/applicationsets/nextcloud-tenants.yaml @@ -98,6 +98,15 @@ spec: {{- $appapiEnabled := default false .tenant.features.appapi -}} {{- $phpMemoryLimit := default "2048M" .tenant.phpMemoryLimit -}} {{- $extraHosts := default (list) .tenant.extraHostnames -}} + {{- $subHosts := list -}} + {{- $apexHosts := list -}} + {{- range $extraHosts -}} + {{- if eq (len (splitList "." .)) 2 -}} + {{- $apexHosts = append $apexHosts . -}} + {{- else -}} + {{- $subHosts = append $subHosts . -}} + {{- end -}} + {{- end -}} fullnameOverride: "nextcloud" commonLabels: nextcloud.platform/tenant: "{{ .tenant.name }}" @@ -199,18 +208,32 @@ spec: # op dezelfde manier als templates/ingress.yaml van de chart, dus # geen tweede kopie van de nginx-instellingen. Alleen geëmit als # de lijst gezet is: bestaande tenants renderen byte-identiek. + # + # Apex-hosts (twee labels, zoals gemeente.nl) krijgen een eigen + # Ingress nextcloud-extra-hosts-apex met hsts-include-subdomains + # "false" over de annotaties heen: includeSubDomains op een apex + # dwingt HTTPS af op elk subdomein van de klant. De validator + # eist daarvoor tenant.extraHostnamesAllowApex: true. Let op: de + # annotatie is een intentie, ingress-nginx leest hsts-* alleen uit + # zijn ConfigMap (zie docs/ADDING-TENANT.md). extraManifests: + {{- range $group := list (dict "naam" "nextcloud-extra-hosts" "hosts" $subHosts "apex" false) (dict "naam" "nextcloud-extra-hosts-apex" "hosts" $apexHosts "apex" true) }} + {{- if $group.hosts }} - | {{`{{- if .Values.ingress.enabled }}`}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: nextcloud-extra-hosts + name: {{ $group.naam }} labels: app.kubernetes.io/name: nextcloud app.kubernetes.io/part-of: nextcloud-platform nextcloud.platform/tenant: "{{ $name }}" + {{- if $group.apex }} + {{`{{- with merge (dict "nginx.ingress.kubernetes.io/hsts-include-subdomains" "false") (.Values.ingress.annotations | default dict) }}`}} + {{- else }} {{`{{- with .Values.ingress.annotations }}`}} + {{- end }} annotations: {{`{{- toYaml . | nindent 4 }}`}} {{`{{- end }}`}} @@ -219,13 +242,13 @@ spec: ingressClassName: {{`{{ . }}`}} {{`{{- end }}`}} tls: - {{- range $extraHosts }} + {{- range $group.hosts }} - secretName: "nextcloud-{{ $name }}-{{ replace "." "-" . }}-tls" hosts: - "{{ . }}" {{- end }} rules: - {{- range $extraHosts }} + {{- range $group.hosts }} - host: "{{ . }}" http: paths: @@ -239,6 +262,8 @@ spec: {{- end }} {{`{{- end }}`}} {{- end }} + {{- end }} + {{- end }} # Our GitOps repo for values files # diff --git a/nextcloud-platform/scripts/validate-values.sh b/nextcloud-platform/scripts/validate-values.sh index 4ccab33..e537685 100644 --- a/nextcloud-platform/scripts/validate-values.sh +++ b/nextcloud-platform/scripts/validate-values.sh @@ -483,8 +483,9 @@ effective_ingress_enabled() { # "nextcloud---tls" per host. Gecontroleerd: # lijstvorm, geldige DNS-naam (geen IP-adres), geen dubbele, niet gelijk aan de # primaire host, onder commonground.nu alleen hosts van de eigen tenant, een -# unieke secretnaam die Kubernetes accepteert (DNS-subdomein, max 253), en -# ingress.enabled: true. Botsingen met hosts van andere tenants vangt +# unieke secretnaam die Kubernetes accepteert (DNS-subdomein, max 253), +# ingress.enabled: true, en een apex-host (twee labels, zoals gemeente.nl) +# alleen met tenant.extraHostnamesAllowApex: true. Botsingen met hosts van andere tenants vangt # validate_host_claims; een dubbele tenant.name vangt dump_host_claims. validate_extra_hostnames() { local file="$1" @@ -509,13 +510,30 @@ validate_extra_hostnames() { primary_bron="afgeleid uit tenant.name, zoals de ApplicationSet doet" fi - local has_error=0 host seen=" " secret seen_secrets=" " + # Opt-in voor apex-hosts. HSTS met includeSubDomains op een apex dwingt + # HTTPS af op elk subdomein van de klant, ook interne en http-only, en dat + # is niet terug te draaien. Alleen met een expliciete true, en alleen als de + # zone-eigenaar dat accepteert (docs/ADDING-TENANT.md). + local allow_apex allow_apex_kind + allow_apex_kind=$(yq eval '.tenant.extraHostnamesAllowApex | type' "$file" 2>/dev/null) + allow_apex=$(yq eval '.tenant.extraHostnamesAllowApex' "$file" 2>/dev/null) + local has_error=0 + if [ "$allow_apex_kind" != "!!null" ] && [ "$allow_apex_kind" != "!!bool" ]; then + log_error "$file: tenant.extraHostnamesAllowApex moet true of false zijn (got $allow_apex_kind)" + has_error=1 + fi + + local host seen=" " secret seen_secrets=" " while IFS= read -r host; do if ! is_valid_hostname "$host"; then log_error "$file: tenant.extraHostnames bevat ongeldige hostnaam '$host' (kleine letters, minstens één punt, labels max 63, totaal max 253, geen IP-adres)" has_error=1 continue fi + if [[ "$host" != *.*.* ]] && [ "$allow_apex" != "true" ]; then + log_error "$file: tenant.extraHostnames bevat apex-host '$host'. Die krijgt HSTS met includeSubDomains, dus HTTPS-dwang op elk subdomein van de zone. Zet tenant.extraHostnamesAllowApex: true alleen als de zone-eigenaar dat accepteert (zie docs/ADDING-TENANT.md)" + has_error=1 + fi if [ "$host" = "$primary" ]; then log_error "$file: tenant.extraHostnames bevat de primaire host '$host' ($primary_bron)" has_error=1 diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.expect new file mode 100644 index 0000000..7ef22e9 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.expect @@ -0,0 +1 @@ +PASS diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.yaml new file mode 100644 index 0000000..ebd1a3d --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-met-optin.yaml @@ -0,0 +1,16 @@ +--- +# Apex-host met de opt-in: geldig. De render-check (render-extra-hosts.sh) gebruikt dit bestand ook. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnamesAllowApex: true + extraHostnames: + - voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.expect new file mode 100644 index 0000000..5bc170f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.expect @@ -0,0 +1 @@ +bevat apex-host 'voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.yaml new file mode 100644 index 0000000..1a83045 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-false.yaml @@ -0,0 +1,15 @@ +--- +# Opt-in expliciet uit: geweigerd zoals zonder opt-in. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnamesAllowApex: false + extraHostnames: + - voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.expect new file mode 100644 index 0000000..6328ade --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.expect @@ -0,0 +1,2 @@ +tenant.extraHostnamesAllowApex moet true of false zijn +bevat apex-host 'voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.yaml new file mode 100644 index 0000000..ae22be6 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-optin-tekst.yaml @@ -0,0 +1,15 @@ +--- +# De opt-in is een boolean, geen tekst. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnamesAllowApex: "ja" + extraHostnames: + - voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.expect b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.expect new file mode 100644 index 0000000..5bc170f --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.expect @@ -0,0 +1 @@ +bevat apex-host 'voorbeeld.nl' diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.yaml new file mode 100644 index 0000000..793cf24 --- /dev/null +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-apex-zonder-optin.yaml @@ -0,0 +1,15 @@ +--- +# Apex-host (twee labels) zonder tenant.extraHostnamesAllowApex: geweigerd. +tenant: + name: voorbeeld-prod + environment: prod + wave: "1" + dbType: postgres + hostname: cloud.voorbeeld.nl + hostnameOverride: true + apps: + enabled: + - openregister + extraHostnames: + - voorbeeld.nl + - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml index f0fa171..f0ca77e 100644 --- a/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml +++ b/nextcloud-platform/tests/cases/tenant-extra-hostnames-geldig.yaml @@ -10,5 +10,5 @@ tenant: enabled: - openregister extraHostnames: - - voorbeeld.nl + - portaal.voorbeeld.nl - www.voorbeeld.nl diff --git a/nextcloud-platform/tests/render-extra-hosts.sh b/nextcloud-platform/tests/render-extra-hosts.sh new file mode 100755 index 0000000..06e6051 --- /dev/null +++ b/nextcloud-platform/tests/render-extra-hosts.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: EUPL-1.2 +# role: tool +# +# nextcloud-platform/tests/render-extra-hosts.sh — render-check voor +# tenant.extraHostnames. +# +# De fixture-suite (run-tests.sh) toetst de validator; deze check toetst wat de +# ApplicationSet er daarna van maakt. Hij rendert twee fixtures zoals Argo het +# doet: eerst het inline values-blok van de ApplicationSet met het tenantbestand +# als parameters, daarna `helm template` met chart 8.9.0 en dezelfde +# valueFiles-volgorde. Daarna moet gelden: +# +# tenant-extra-hostnames-apex-met-optin (voorbeeld.nl + www.voorbeeld.nl) +# - Ingress nextcloud-extra-hosts-apex bestaat, met alleen voorbeeld.nl en +# hsts-include-subdomains "false"; +# - Ingress nextcloud-extra-hosts heeft alleen www.voorbeeld.nl en dezelfde +# hsts-include-subdomains als de hoofd-Ingress; +# - de hoofd-Ingress houdt de waarde uit values/common.yaml (de apex-merge +# mag de gedeelde values niet muteren); +# - beide extra Ingresses houden de cert-manager-issuer van de hoofd-Ingress. +# tenant-extra-hostnames-geldig (geen apex) +# - geen Ingress nextcloud-extra-hosts-apex. +# +# Argo rendert het values-blok met Go text/template en sprig. Helm doet dat +# ook, dus deze check gebruikt helm als template-engine: het blok gaat als +# template in een wegwerp-chart, met het tenantbestand als values. Twee +# verschillen met Argo, en hoe die gedekt zijn: +# - Argo draait met missingkey=default, helm met missingkey=zero. Daardoor +# breekt `.tenant.features.appapi` in helm als `features` ontbreekt, en in +# Argo niet. De check zet daarom elke tussenliggende map die het blok leest +# (afgeleid uit de `.tenant.a.b`-paden in het blok zelf) op `{}` als hij +# ontbreekt; een ontbrekende sleutel in een lege map geeft in beide engines +# dezelfde lege waarde. +# - Helm heeft een paar functies meer (include, tpl, required); het blok +# gebruikt die niet. +# Gemeten 2026-10-14: voor alle 89 tenantbestanden en de extraHostnames-fixtures +# gaf deze engine hetzelfde values-blok als een Go-emulatie van Argo, byte voor +# byte op de afsluitende lege regel na (text/template + sprig, missingkey=default). +# +# Writes: read-only (schrijft alleen in een eigen mktemp-map) +# Idempotent: ja +# Requires: bash, helm, yq; netwerk naar https://nextcloud.github.io/helm/, +# tenzij NEXTCLOUD_CHART naar een uitgepakte chart wijst +# +# Usage: +# nextcloud-platform/tests/render-extra-hosts.sh +# NEXTCLOUD_CHART=/pad/naar/nextcloud nextcloud-platform/tests/render-extra-hosts.sh + +set -euo pipefail +shopt -s inherit_errexit + +HERE="$(cd "$(dirname "$0")" && pwd)" +PLATFORM="$(cd "${HERE}/.." && pwd)" +VALUES="${PLATFORM}/values" +APPSET="${PLATFORM}/argo/applicationsets/nextcloud-tenants.yaml" +CASES="${HERE}/cases" +CHART_VERSION="8.9.0" +HSTS_SUB="nginx.ingress.kubernetes.io/hsts-include-subdomains" +ISSUER="cert-manager.io/cluster-issuer" + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +fails=0 +fail() { echo "FAIL: $*" >&2; fails=$((fails + 1)); } + +chart="${NEXTCLOUD_CHART:-}" +if [[ -z "$chart" ]]; then + helm pull nextcloud --repo https://nextcloud.github.io/helm/ \ + --version "$CHART_VERSION" --untar --untardir "$TMP_DIR/chart" >/dev/null + chart="$TMP_DIR/chart/nextcloud" +fi + +# Wegwerp-chart met het values-blok van de ApplicationSet als enige template. +engine="$TMP_DIR/engine" +mkdir -p "$engine/templates" +printf 'apiVersion: v2\nname: appset-values\nversion: 0.0.0\n' >"$engine/Chart.yaml" +{ + echo '{{- with .Values }}' + yq -r '.spec.template.spec.sources[0].helm.values' "$APPSET" + echo '{{- end }}' +} >"$engine/templates/values.yaml" + +# Tussenliggende maps die het blok leest, zoals `.tenant.apps.versions` uit +# `.tenant.apps.versions.opencatalogi`. Elk wordt `{}` als hij ontbreekt. +fill_expr="$(grep -oE '\.tenant(\.[A-Za-z0-9_]+){2,}' "$engine/templates/values.yaml" \ + | sed -E 's/\.[A-Za-z0-9_]+$//' \ + | awk -F. '{p=".tenant"; for (i=3; i<=NF; i++) {p=p"."$i; print p}}' \ + | sort -u | awk '{printf "%s%s |= (. // {})", (NR>1 ? " | " : ""), $0}')" + +# Usage: appset_values +appset_values() { + yq "${fill_expr:-.}" "$1" >"$2.params.yaml" + helm template x "$engine" -f "$2.params.yaml" --show-only templates/values.yaml \ + | sed '1,2d' >"$2" +} + +# Usage: render → pad naar de gerenderde manifests +render() { + local name="$1" tenant="${CASES}/$1.yaml" out="$TMP_DIR/$1" + local env db + appset_values "$tenant" "$out.values.yaml" + env="$(yq -r '.tenant.environment' "$tenant")" + db="$(yq -r '.tenant.dbType // "postgres"' "$tenant")" + helm template nextcloud "$chart" \ + -f "${VALUES}/common.yaml" -f "${VALUES}/env/${env}.yaml" -f "${VALUES}/db/${db}.yaml" \ + -f "$tenant" -f "$out.values.yaml" \ + --namespace "$(yq -r '.tenant.name' "$tenant")" >"$out.manifests.yaml" + echo "$out.manifests.yaml" +} + +# Usage: ingress +ingress() { + # yq geeft per niet-passend document een lege regel; die weg. + yq -r "select(.kind == \"Ingress\" and .metadata.name == \"$2\") | $3" "$1" | sed '/^$/d' +} + +expected_main="$(yq -r ".ingress.annotations.\"${HSTS_SUB}\"" "${VALUES}/common.yaml")" +expected_issuer="$(yq -r ".ingress.annotations.\"${ISSUER}\"" "${VALUES}/common.yaml")" + +# Alleen voor het meten van de engine tegen Argo: print het values-blok van +# één tenantbestand en stop. +if [[ "${1:-}" == "--values" ]]; then + appset_values "$2" "$TMP_DIR/one" + cat "$TMP_DIR/one" + exit 0 +fi + +m="$(render tenant-extra-hostnames-apex-met-optin)" + +got="$(ingress "$m" nextcloud-extra-hosts-apex '[.spec.rules[].host] | join(",")')" +[[ "$got" == "voorbeeld.nl" ]] || fail "apex-Ingress hosts: verwacht 'voorbeeld.nl', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts-apex "[.spec.tls[].hosts[]] | join(\",\")")" +[[ "$got" == "voorbeeld.nl" ]] || fail "apex-Ingress tls-hosts: verwacht 'voorbeeld.nl', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts-apex ".metadata.annotations.\"${HSTS_SUB}\"")" +[[ "$got" == "false" ]] || fail "apex-Ingress ${HSTS_SUB}: verwacht 'false', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts-apex ".metadata.annotations.\"${ISSUER}\"")" +[[ "$got" == "$expected_issuer" ]] || fail "apex-Ingress ${ISSUER}: verwacht '$expected_issuer', kreeg '$got'" + +got="$(ingress "$m" nextcloud-extra-hosts '[.spec.rules[].host] | join(",")')" +[[ "$got" == "www.voorbeeld.nl" ]] || fail "extra Ingress hosts: verwacht 'www.voorbeeld.nl', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts ".metadata.annotations.\"${HSTS_SUB}\"")" +[[ "$got" == "$expected_main" ]] || fail "extra Ingress ${HSTS_SUB}: verwacht '$expected_main', kreeg '$got'" +got="$(ingress "$m" nextcloud-extra-hosts ".metadata.annotations.\"${ISSUER}\"")" +[[ "$got" == "$expected_issuer" ]] || fail "extra Ingress ${ISSUER}: verwacht '$expected_issuer', kreeg '$got'" + +got="$(ingress "$m" nextcloud ".metadata.annotations.\"${HSTS_SUB}\"")" +[[ "$got" == "$expected_main" ]] || fail "hoofd-Ingress ${HSTS_SUB}: verwacht '$expected_main', kreeg '$got'" + +m="$(render tenant-extra-hostnames-geldig)" +got="$(ingress "$m" nextcloud-extra-hosts-apex '.metadata.name')" +[[ -z "$got" ]] || fail "geen apex-host, toch een Ingress nextcloud-extra-hosts-apex" +got="$(ingress "$m" nextcloud-extra-hosts '[.spec.rules[].host] | join(",")')" +[[ "$got" == "portaal.voorbeeld.nl,www.voorbeeld.nl" ]] \ + || fail "extra Ingress hosts (geldig): verwacht 'portaal.voorbeeld.nl,www.voorbeeld.nl', kreeg '$got'" + +if [[ $fails -gt 0 ]]; then + echo "render-extra-hosts: $fails fout(en)" >&2 + exit 1 +fi +echo "render-extra-hosts OK" diff --git a/scripts/verify.sh b/scripts/verify.sh index e8450d4..7899244 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -12,7 +12,9 @@ # # Writes: read-only # Idempotent: yes -# Requires: bash, yq, yamllint (via validate-values), helm +# Requires: bash, yq, yamllint (via validate-values), helm; netwerk naar +# nextcloud.github.io/helm voor render-extra-hosts.sh, of +# NEXTCLOUD_CHART naar een uitgepakte chart 8.9.0 # # Usage: # ./scripts/verify.sh @@ -137,4 +139,11 @@ echo "doc-assertion OK (${script_count} scripts gedekt in de index)" nextcloud-platform/tests/run-tests.sh >/dev/null echo "validator-tests OK" +# Render-check voor tenant.extraHostnames: wat de ApplicationSet van een geldige +# lijst maakt. Een apex-host krijgt een eigen Ingress met +# hsts-include-subdomains "false"; de hoofd-Ingress houdt zijn waarde. De +# validator-tests hierboven zien de render niet. +nextcloud-platform/tests/render-extra-hosts.sh >/dev/null +echo "render-extra-hosts OK" + echo "verify: OK" From 4e449cbb569136982ce0487c3ca2fa2d4160a2e6 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 14 Oct 2026 16:59:09 +0200 Subject: [PATCH 14/14] docs(extra-hostnames): #138 f3 en f4, HSTS op apex-hosts en alleen Let's Encrypt ADDING-TENANT gebruikt www.example.nl als voorbeeld en noemt het gevolg van includeSubDomains op een apex, de opt-in en dat de zone-eigenaar dat moet accepteren. Ook staat er dat ingress-nginx geen HSTS-annotatie per Ingress leest (HSTS komt uit de controller-ConfigMap), dus wat live wel zou helpen. Extra hosts krijgen alleen Let's Encrypt: hun Ingresses erven cert-manager.io/cluster-issuer. Een eigen of betaald cert per extra host kan nog niet; tenant.frontend.tls is het bestaande patroon. --- changelog.d/tenant-extra-hostnames.md | 14 +++++++ docs/ADDING-TENANT.md | 60 +++++++++++++++++++++++---- docs/CHECKS-AND-BALANCES.md | 10 ++++- docs/index.md | 10 ++++- 4 files changed, 83 insertions(+), 11 deletions(-) diff --git a/changelog.d/tenant-extra-hostnames.md b/changelog.d/tenant-extra-hostnames.md index a5de288..4c91013 100644 --- a/changelog.d/tenant-extra-hostnames.md +++ b/changelog.d/tenant-extra-hostnames.md @@ -34,3 +34,17 @@ geschreven, en een verwijderde host blijft erin staan tot een `occ config:system:delete trusted_domains `; `charts/tenant-httproute` neemt nog één host. Zie `docs/ADDING-TENANT.md`. + +**Apex-hosts (review f3).** Een extra host met twee labels (`gemeente.nl`) +wordt geweigerd zonder `tenant.extraHostnamesAllowApex: true`: HSTS met +includeSubDomains op een apex dwingt HTTPS af op elk subdomein van de klant. +Apex-hosts krijgen een eigen Ingress `nextcloud-extra-hosts-apex` met +`hsts-include-subdomains: "false"`; de hoofd-Ingress en de andere extra hosts +houden hun waarde. ingress-nginx leest die annotatie niet (HSTS komt uit de +controller-ConfigMap), dus live blijft includeSubDomains staan tot die +ConfigMap of de route verandert; zie `docs/ADDING-TENANT.md`. Nieuwe +render-check `nextcloud-platform/tests/render-extra-hosts.sh` in `verify.sh`. + +**Certificaten (review f4).** Extra hosts krijgen alleen Let's Encrypt: hun +Ingresses erven `cert-manager.io/cluster-issuer`. Een eigen of betaald cert per +extra host kan nog niet; `tenant.frontend.tls` is het bestaande patroon. diff --git a/docs/ADDING-TENANT.md b/docs/ADDING-TENANT.md index f939ea6..9dfed80 100644 --- a/docs/ADDING-TENANT.md +++ b/docs/ADDING-TENANT.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-10-13 +last_reviewed: 2026-10-14 owner: info@conduction.nl --- @@ -183,31 +183,74 @@ validation as-is — the rules above are not machine-checked. #### Extra hostnames on one tenant One Nextcloud can answer on more than one hostname, for example a workplace on -`cloud.` and a public site served by an app on `` and -`www.`. Set the primary host as usual and list the others: +`cloud.` and a public site served by an app on `www.`. Set the +primary host as usual and list the others: ```yaml tenant: hostname: cloud.example.nl hostnameOverride: true extraHostnames: - - example.nl - www.example.nl ``` +A bare apex such as `example.nl` is refused unless you also set +`extraHostnamesAllowApex: true`; read the HSTS point below first. + What the ApplicationSet does with it (`argo/applicationsets/nextcloud-tenants.yaml`): | Effect | Detail | |---|---| | `trusted_domains` | every extra host is added to `nextcloud.trustedDomains`, after the primary host | -| routing | a second Ingress `nextcloud-extra-hosts`, because chart 8.9.0 puts only `nextcloud.host` in its own Ingress rules | +| routing | a second Ingress `nextcloud-extra-hosts`, because chart 8.9.0 puts only `nextcloud.host` in its own Ingress rules. Apex hosts (two labels) go into a third Ingress `nextcloud-extra-hosts-apex` | | certificates | one TLS entry per host, secret `nextcloud---tls`, so one cert-manager Certificate per host via HTTP-01 (`letsencrypt-prod`). A host whose DNS is not ready does not block the others | -| annotations | taken from `ingress.annotations`, `ingress.className`, `ingress.path`, `ingress.pathType` and `service.port` through the chart's `tpl`, so they always match the main Ingress; with `ingress.enabled: false` neither Ingress is rendered | +| annotations | taken from `ingress.annotations`, `ingress.className`, `ingress.path`, `ingress.pathType` and `service.port` through the chart's `tpl`, so they always match the main Ingress; with `ingress.enabled: false` no Ingress is rendered. The apex Ingress has one difference: `nginx.ingress.kubernetes.io/hsts-include-subdomains: "false"` | A tenant without `extraHostnames` renders byte-identical to before; the block is only emitted when the list is set. -Four things to know: +Seven things to know: + +- **An apex host gets HSTS with includeSubDomains, so it needs an opt-in.** + Every host on our ingress-nginx answers with + `Strict-Transport-Security: max-age=31536000; includeSubDomains` (measured on + `dinkelland.commonground.nu`). On an apex like `example.nl` that means a + browser that visits it once forces HTTPS on every `*.example.nl` for a year, + including internal or http-only subdomains of the customer. Neither we nor the + customer can undo that in a browser that already has it. `validate-values.sh` + therefore refuses an extra host with two labels unless the tenant sets + `tenant.extraHostnamesAllowApex: true`. Set it only when the owner of the zone + has accepted that every subdomain must work over HTTPS. +- **The apex Ingress asks for no includeSubDomains, but ingress-nginx does not + read that today.** `nextcloud-extra-hosts-apex` carries + `hsts-include-subdomains: "false"`; the main Ingress and the other extra hosts + keep their value. ingress-nginx has no per-Ingress HSTS annotations: `hsts`, + `hsts-max-age` and `hsts-include-subdomains` are settings of the controller + ConfigMap. That is why the live max-age is 31536000 (the ConfigMap default) + and not the 15552000 in `values/common.yaml` (see `docs/GATEWAY-API.md`). The + ConfigMap `nginx-ingress-nginx-controller` is in no repo (Helm release `nginx`, + see `cluster-infra/scripts/tls-sigalgs-apply.sh`). So with the opt-in, the + apex host does get includeSubDomains until one of these changes: the + controller ConfigMap sets `hsts-include-subdomains: "false"` (for every host + on the cluster), the apex Ingress gets a `configuration-snippet` that sets the + header itself (only if the controller allows snippet annotations, not + verified), or the apex host moves to a Gateway API route whose `route.hsts` + leaves includeSubDomains out (`charts/tenant-httproute` does that per tenant, + but takes one host). The nginx sidecar in the pod also sends its own header, + `max-age=15768000; includeSubDomains; preload` (`values/common.yaml`, + `nginx.config.default`). ingress-nginx replaces it today, but a route that + does not set HSTS itself lets it through. +- **Extra hosts only get Let's Encrypt.** Both extra Ingresses inherit + `cert-manager.io/cluster-issuer: letsencrypt-prod` from `ingress.annotations`, + so cert-manager creates and owns the certificate of every extra host. A paid + or own certificate per extra host is not possible yet: a certificate you put + in `nextcloud---tls` yourself is overwritten by + cert-manager, and a CAA record that allows only the customer's own CA leaves + the Certificate on `Ready=False` while nginx serves its fake certificate. The + existing pattern for a customer certificate is `tenant.frontend.tls` + (`secretName` plus `issuer: none`, an Ingress without the cert-manager + annotation; see `tenant-tubbergen-prod.yaml`). `extraHostnames` has no such + option; a host that needs its own certificate cannot be an extra host for now. - **DNS is yours when the zone is not ours.** external-dns only manages `commonground.nu`, `openwoo.app` and `opencatalogi.nl` @@ -215,7 +258,8 @@ Four things to know: creates the records by hand, pointing at the ingress-nginx load balancer, and keeps them **DNS only** (not proxied): Cloudflare caps request bodies at 100 MB against our 16G, and HTTP-01 has to reach the Ingress. -- **A CAA record on the zone must allow `letsencrypt.org`**, or issuance fails. +- **A CAA record on the zone must allow `letsencrypt.org`**, or issuance fails + (see the Let's Encrypt point above). - **`trusted_domains` is written at first install only.** The Nextcloud image reads `NEXTCLOUD_TRUSTED_DOMAINS` when it installs; adding a host to a running tenant later also needs `occ config:system:set trusted_domains --value=` diff --git a/docs/CHECKS-AND-BALANCES.md b/docs/CHECKS-AND-BALANCES.md index b2ced81..6ec9ace 100644 --- a/docs/CHECKS-AND-BALANCES.md +++ b/docs/CHECKS-AND-BALANCES.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-10-13 +last_reviewed: 2026-10-14 owner: info@conduction.nl --- @@ -38,13 +38,19 @@ Before merge, CI runs `scripts/verify.sh`, which wraps: `validate-values.sh` also checks the optional `tenant.extraHostnames` list (a list, valid DNS names and no IP address, no duplicates, not the primary host, under `commonground.nu` only this tenant's own names, a unique derived TLS -secret name Kubernetes accepts, and not together with `ingress.enabled: false`). +secret name Kubernetes accepts, not together with `ingress.enabled: false`, and +no apex host such as `gemeente.nl` without `tenant.extraHostnamesAllowApex: true`). `tenant.hostname` is refused as an IP address too. It also checks across all tenant files that no host (primary, `extraHostnames`, `frontend.host`, `frontend.extraHosts`) is claimed by two tenants, also when it is run on a single file, and that no two tenant files share a `tenant.name`. A valid list still gives a warning, because DNS and HTTP-01 for those hosts are outside what CI can see. Fixtures for every branch are in `nextcloud-platform/tests/cases/tenant-extra-hostnames-*`. +`nextcloud-platform/tests/render-extra-hosts.sh` (also in `verify.sh`) renders +two of those fixtures through the ApplicationSet and chart 8.9.0 and checks the +result: an apex host lands in its own Ingress with +`hsts-include-subdomains: "false"`, the main Ingress keeps its value, and both +extra Ingresses keep the cert-manager issuer. This catches invalid values and Helm/rendering failures early. Both are dry-run: CI needs no cluster access and no secrets. The same gate runs locally via diff --git a/docs/index.md b/docs/index.md index ebb61c9..c607200 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,5 @@ --- -last_reviewed: 2026-08-10 +last_reviewed: 2026-10-14 owner: info@conduction.nl --- @@ -82,6 +82,14 @@ Verwijderen doe je niet met een script uit deze repo, maar met een `.yaml` met precies één afwijking en een `.expect` met `PASS` of de vereiste deelstrings). `scripts/verify.sh` draait de suite mee. +`nextcloud-platform/tests/render-extra-hosts.sh` rendert twee +`extraHostnames`-fixtures zoals Argo het doet (values-blok van de +ApplicationSet, daarna `helm template` met chart 8.9.0) en controleert de +Ingresses: een apex-host krijgt een eigen Ingress met +`hsts-include-subdomains: "false"`. Ook die draait mee in `scripts/verify.sh`; +hij haalt de chart op, of gebruikt `NEXTCLOUD_CHART` als die naar een +uitgepakte chart wijst. + Dat de validator over de echte vloot groen is, zegt alleen dat de huidige bestanden schoon zijn — niet dat een check nog aanslaat op een fout die niemand meer maakt. Een nieuwe check hoort dus met minstens twee cases te komen.