Skip to content

feat(tenants): tenant.extraHostnames voor meerdere hostnamen op één tenant - #138

Open
rubenvdlinde wants to merge 16 commits into
mainfrom
feat/tenant-extra-hostnames
Open

rubenvdlinde wants to merge 16 commits into
mainfrom
feat/tenant-extra-hostnames

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Collaborator

Wat

Een tenant kan op meer dan één hostnaam antwoorden. Nieuw optioneel veld tenant.extraHostnames in de ApplicationSet nextcloud-tenants:

  • extra hosts komen achter de primaire host in trustedDomains, en daarmee in NEXTCLOUD_TRUSTED_DOMAINS;
  • een tweede Ingress nextcloud-extra-hosts via extraManifests, omdat chart 8.9.0 in zijn eigen Ingress alleen nextcloud.host als rule zet;
  • per extra host een eigen TLS-blok en dus een eigen HTTP-01-Certificate, zodat een host met nog niet kloppende DNS de andere niet tegenhoudt;
  • annotaties, class en poort via de tpl van de chart uit dezelfde values als de hoofd-Ingress.

Aanleiding: de demogemeente Zuiddrecht moet op cloud.zuiddrecht.nl, zuiddrecht.nl, www.zuiddrecht.nl en mijn.zuiddrecht.nl draaien. Het tenantbestand zit bewust niet in deze PR, zie volgorde.

Achterwaarts compatibel

Het blok wordt alleen geëmit als de lijst gezet is. Gemeten: alle 89 bestaande tenantbestanden gerenderd op main en op deze branch, de inline values van de ApplicationSet (Go text/template met sprig en missingkey=default, zoals Argo) en daarna helm template met chart nextcloud 8.9.0. Values en manifests per tenant byte voor byte gelijk, waaronder conduction-demo (postgres, hostnameOverride) en gouda-prod (mariadb).

Gedraaid

Commando Exit
nextcloud-platform/scripts/validate-values.sh 0
nextcloud-platform/tests/run-tests.sh (5 nieuwe fixtures voor extraHostnames) 0
scripts/verify.sh 0
pre-commit run --all-files --hook-stage pre-push 0
kubeconform -strict op een render met extraHostnames 0 (gedraaid op een eerdere branch met dezelfde ApplicationSet-wijziging, niet opnieuw op deze)

Niet gedraaid: conftest, kube-score. Niets tegen een cluster.

Volgorde

Platformwijziging, label change/platform, mergen ma tot do na 17:00. nextcloud-platform-bootstrap synct niet automatisch: pas na die sync mag een tenant met extraHostnames verschijnen, want trusted_domains wordt alleen bij de eerste installatie geschreven. Het tenantbestand zuiddrecht-prod volgt daarom als aparte tenant-PR, na deze en na een tweede platform-PR die elke app op een GitHub-release laat pinnen (nu kan dat alleen voor opencatalogi, openconnector en openregister).

DNS en certificaten

Een extra host buiten commonground.nu valt buiten external-dns en de DNS-01-issuer. De zone-eigenaar zet A-records naar 81.24.6.82 (ingress-nginx), DNS only. HTTP-01 per host werkt dan zonder token in het cluster.

Bekende beperkingen

  • trusted_domains wordt alleen bij de eerste installatie geschreven. Een host toevoegen aan een bestaande tenant vraagt daarna ook occ config:system:set trusted_domains.
  • charts/tenant-httproute (Gateway API) neemt nog één host. Deze PR raakt alleen het nginx-pad.

🤖 Generated with Claude Code

…enant

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/.
@rubenvdlinde rubenvdlinde added the change/platform Platform- of gemengde wijziging: raakt shared values, Argo-wiring, scripts of policy label Oct 13, 2026
@rubenvdlinde
rubenvdlinde marked this pull request as ready for review October 13, 2026 20:29
fi

name=$(yq eval '.tenant.name' "$file" 2>/dev/null)
primary=$(yq eval '.tenant.hostname // ""' "$file" 2>/dev/null)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocker — Extra host mag de host van een andere tenant claimen

validate_extra_hostnames kijkt alleen binnen het eigen bestand. Er is geen check tegen de primaire host, extraHostnames of frontend.host van de andere tenants. En de commonground.nu-conventie (validate_hostname_convention, opt-out alleen via hostnameOverride: true) geldt hier niet. Getest: voorbeeld-prod met extraHostnames: [dinkelland.commonground.nu, open.dinkelland.nl] geeft PASSED with warnings.

Impact: tenant A krijgt een Ingress-rule en een Let's Encrypt-cert (HTTP-01 lukt, want alle hosts wijzen naar dezelfde load balancer) voor de host van tenant B, of voor de WOO-frontend van B. Met de admission-webhook van ingress-nginx faalt de sync van wie als tweede komt. Is B nog niet uitgerold, dan is A eerst en valt B om. Zonder webhook voegt nginx de twee samen en gaat verkeer van B naar de Nextcloud van A. Eén tikfout in een tenantbestand is genoeg.

Fix: maak de check globaal. Verzamel per run alle hosts (primair, ook de berekende <org>.commonground.nu; extraHostnames; frontend.host) van alle tenantbestanden en faal op elke host die in twee tenants voorkomt. Eis daarnaast voor een extra host onder commonground.nu, openwoo.app of opencatalogi.nl dat hij bij de eigen tenant hoort, of eis een opt-in zoals bij hostnameOverride.

Klaar als:

  • Een fixture met de primaire host van een bestaande andere tenant in extraHostnames faalt.
  • Een fixture met de frontend.host van een andere tenant faalt.
  • ./scripts/validate-values.sh op alle 89 tenants blijft groen.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Voorgestelde fix in #142, commit 34c94c0: een host die bij twee tenants hoort wordt geweigerd (register over alle tenantbestanden, ook bij één bestand), een extra host onder commonground.nu moet een conventienaam van de eigen tenant zijn, en een dubbele tenant.name is nu ook een fout (ab064c7). De thread blijft open tot de fix-PR gemerged is.

has_error=1
continue
fi
if [ -n "$primary" ] && [ "$host" = "$primary" ]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Aandachtspunt — Berekende primaire host niet herkend als dubbel

primary is .tenant.hostname // "". De meeste tenants zetten geen hostname; de ApplicationSet berekent dan <org>.commonground.nu of <org>.<omgeving>.commonground.nu. Voor die tenants wordt de check overgeslagen. Getest: voorbeeld-prod zonder hostname en met extraHostnames: [voorbeeld.commonground.nu] geeft PASSED.

Impact: dezelfde host staat in twee Ingresses in één namespace, met twee Certificates. De admission-webhook weigert dan de tweede Ingress, en de sync van de hele Application faalt. De fixture primaire-host dekt alleen het geval met een expliciete hostname.

Fix: bereken de primaire host op dezelfde manier als de ApplicationSet, als tenant.hostname leeg is. Voeg een fixture zonder hostname toe.

Klaar als:

  • Fixture zonder tenant.hostname met extraHostnames: [voorbeeld.commonground.nu] faalt met 'bevat de primaire host'.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Voorgestelde fix in #142, commit 6b0bc18: de primaire host wordt berekend zoals de ApplicationSet dat doet als tenant.hostname leeg is (gelijk voor alle 89 tenants). De thread blijft open tot de fix-PR gemerged is.

Comment thread docs/ADDING-TENANT.md Outdated
hostname: cloud.example.nl
hostnameOverride: true
extraHostnames:
- example.nl

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Blocker — Apex-host krijgt HSTS includeSubDomains

Het voorbeeld en de use case in de PR zetten de apex (example.nl, zuiddrecht.nl) als extra host. De extra Ingress neemt de annotaties van de hoofd-Ingress over, waaronder nginx.ingress.kubernetes.io/hsts-include-subdomains: "true" (values/common.yaml:659). Live stuurt een tenant-host nu strict-transport-security: max-age=31536000; includeSubDomains (gemeten op dinkelland.commonground.nu).

Impact: wie één keer https://gemeente.nl via ons bezoekt, krijgt een jaar lang HTTPS-dwang op elk *.gemeente.nl. Interne of http-only subdomeinen van de klant gaan dan stuk. Dat kunnen wij niet terugdraaien en de klant ook niet.

Fix: laat de validator een apex-host (twee labels) weigeren, of sta hem alleen toe met een expliciete opt-in en een regel in de docs dat de zone-eigenaar dit accepteert. Zet in elk geval op de extra Ingress hsts-include-subdomains: "false" voor apex-hosts, en controleer met curl -sI of de header dan echt zonder includeSubDomains komt (de live max-age wijkt af van de annotatie, dus de header kan ook uit een andere bron komen). Verander het voorbeeld in ADDING-TENANT naar www.example.nl en noem het HSTS-gevolg.

Klaar als:

  • Een fixture met een apex-host faalt, of slaagt alleen met de opt-in.
  • Op een apex-host komt geen includeSubDomains in de HSTS-header (gemeten met curl -sI).
  • ADDING-TENANT noemt het includeSubDomains-gevolg en het voorbeeld gebruikt geen kale apex meer.

app.kubernetes.io/part-of: nextcloud-platform
nextcloud.platform/tenant: "{{ $name }}"
annotations:
{{`{{- toYaml .Values.ingress.annotations | nindent 4 }}`}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Aandachtspunt — Geen eigen of betaald cert mogelijk per extra host

De extra Ingress neemt alle ingress.annotations over, dus ook cert-manager.io/cluster-issuer: letsencrypt-prod. Er is geen issuer: none en geen eigen secretName, zoals frontend.tls die sinds #131 en #139 wel heeft voor klantdomeinen met een betaald cert (dinkelland, tubbergen, noaberkracht, roosendaal, noorderzijlvest).

Impact: klantdomeinen zijn precies de hosts waarvoor dit veld bedoeld is. Heeft de klant een CAA-record dat alleen zijn eigen CA toestaat, dan blijft het Certificate op Ready=False en serveert nginx het fake-cert. Zet je zelf een betaald cert in het secret, dan overschrijft cert-manager het. Dat is het scenario dat #131 juist dichtzette.

Fix: maak een lijstitem een object (host, optioneel tls.secretName, tls.issuer: none), of documenteer expliciet dat extra hosts alleen Let's Encrypt kunnen en dat een betaald cert niet kan. Bij de eerste optie: een host met issuer: none in een aparte Ingress zonder de cert-manager-annotatie.

Klaar als:

  • Een extra host met een betaald cert kan zonder dat cert-manager een Certificate maakt, of de docs zeggen dat dit niet kan.

has_error=1
fi
seen="${seen}${host} "
secret="nextcloud-${name}-${host//./-}-tls"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Klein — Secretnamen botsen bij a-b.nl en a.b.nl

${host//./-} maakt van a-b.nl en a.b.nl allebei nextcloud-<tenant>-a-b-nl-tls. De validator laat dat door (getest). Twee TLS-blokken met dezelfde secretName en andere hosts geven één Certificate dat maar één van de twee dekt. Fix: controleer ook dat de afgeleide secretnamen uniek zijn.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Voorgestelde fix in #142, commit 13687d4: afgeleide TLS-secretnamen moeten uniek zijn. De thread blijft open tot de fix-PR gemerged is.


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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Klein — Hostnaam-regex accepteert localhost, 123 en labels >63

De regex eist geen punt en geen maximale lengte per label (63) of totaal (253). Getest: localhost, 123, true en een label van 71 tekens slagen allemaal. Een label >63 weigert de API-server in de Ingress, en dan faalt de sync van de hele Application. Fix: eis minstens één punt en voeg een lengtecheck per label en voor het geheel toe.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Voorgestelde fix in #142, commit 71e97bc: een punt, labels ≤ 63 en totaal ≤ 253 zijn verplicht, ook voor tenant.hostname; een IP-adres wordt geweigerd (6da1eaa). De thread blijft open tot de fix-PR gemerged is.

- host: "{{ . }}"
http:
paths:
- path: /

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Klein — Extra Ingress negeert ingress.enabled en ingress.path

De chart rendert de hoofd-Ingress alleen als ingress.enabled aan staat, en gebruikt ingress.path en ingress.pathType. De extra Ingress rendert altijd en zet / en Prefix vast. Dat botst met 'uit dezelfde values als de hoofd-Ingress' in de comment en de docs. Fix: neem path en pathType via tpl over en zet de manifest achter {{{{- if .Values.ingress.enabled }}}}.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Voorgestelde fix in #142, commit 50934c5: de extra Ingress volgt ingress.enabled, path, pathType, annotaties en class zoals de chart; extraHostnames met ingress.enabled: false wordt geweigerd en GATEWAY-API.md zegt waarom (bf29d92). De thread blijft open tot de fix-PR gemerged is.

Comment thread docs/ADDING-TENANT.md
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Klein — Host verwijderen blijft in trusted_domains staan

De beperking noemt alleen toevoegen. Haal je een host uit de lijst, dan prunet Argo de Ingress, maar de host blijft in config.php staan. Bij config:system:set trusted_domains <n> overschrijf je met een verkeerde <n> ook een bestaande entry. Fix: één zin over verwijderen (config:system:delete trusted_domains <n>) en het advies om eerst config:system:get trusted_domains te draaien.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔧 Voorgestelde fix in #142, commit 72bb9e3: ADDING-TENANT.md beschrijft verwijderen: eerst config:system:get trusted_domains, dan config:system:delete trusted_domains <n>. De thread blijft open tot de fix-PR gemerged is.

@rjzondervan rjzondervan left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Wijzigingen gevraagd (Strict)

Het render-pad is netjes: alle 89 bestaande tenants renderen byte voor byte gelijk, en de extra hosts komen in trusted_domains en in een eigen Ingress met een cert per host. Twee dingen blokkeren: een extra host wordt niet vergeleken met de hosts van andere tenants, en een kale domeinnaam (zoals zuiddrecht.nl) krijgt HSTS met includeSubDomains voor het hele domein van de klant.

🔧 Fix PR: #142 — 6 van 8 bevindingen, één commit per bevinding (plus pre-push-commits)

Merge hem in de branch van deze PR en vraag daarna de review opnieuw aan. De andere bevindingen blijven op hun threads.

🔴 Blockers

🟡 Aandachtspunten

🟢 Klein

✅ Gecontroleerd en in orde

  • Achterwaarts compatibel: de inline values van alle 89 tenants opnieuw gerenderd (Go template + sprig, missingkey=default), main tegen deze branch: 0 verschillen; ook bij extraHostnames: [].
  • Template: inspringing, quoting en de dubbele {{-escaping door de tpl van de chart renderen geldige YAML; de regex houdt quotes, spaties en wildcards tegen.
  • NEXTCLOUD_TRUSTED_DOMAINS bevat de extra hosts en wordt alleen bij installatie toegepast, dus de beperking in de docs klopt. Geen overwritehost, dus geen redirect naar de primaire host.
  • letsencrypt-prod is de HTTP-01-issuer; de naam nextcloud-extra-hosts botst niet binnen de release.
  • De 5 nieuwe fixtures slagen of falen om de reden die ze noemen.

Mode: Strict · Head: efbbe861 · CI: groen (validate & render, classify, CodeQL, gate)
Niet gecontroleerd: de admission-webhook van ingress-nginx en de zonelijst van external-dns (staan in cluster-infra); niets tegen een cluster.

rjzondervan and others added 11 commits October 14, 2026 15:28
#138 (comment)

validate_extra_hostnames vergeleek alleen met een expliciete tenant.hostname.
Nieuwe helper primary_host() berekent de primaire host zoals de ApplicationSet
(tenant.hostname, anders <org>.commonground.nu / <org>.<omgeving>.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 <noreply@anthropic.com>
…laimen

#138 (comment)

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:
<org>.commonground.nu / <org>.migrate.commonground.nu voor prod,
<org>.<suffix>.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 <noreply@anthropic.com>
#138 (comment)

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-<tenant>-tls) kan
niet met een afgeleide naam botsen (die heeft altijd een host-deel).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bels >63

#138 (comment)

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 <noreply@anthropic.com>
…ss.path

#138 (comment)

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 <noreply@anthropic.com>
#138 (comment)

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 <n>`. Eerst `occ config:system:get trusted_domains` draaien om
de juiste <n> 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 <noreply@anthropic.com>
#138 (comment)

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 <noreply@anthropic.com>
…keer bouwen

#138 (comment)

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 <noreply@anthropic.com>
#138 (comment)

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 <noreply@anthropic.com>
…ixtures noemen

#138 (comment)

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 <noreply@anthropic.com>
#138 (comment)

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 <noreply@anthropic.com>
rubenvdlinde and others added 3 commits October 14, 2026 16:44
…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).
…t'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.
@rubenvdlinde

Copy link
Copy Markdown
Collaborator Author

Antwoord op de twee open bevindingen uit de review van @rjzondervan.

Bevindingen naar commits

Bevinding Commit Wat er verandert
f3 🔴 Apex-host krijgt HSTS includeSubDomains cb163ee Een extra host met twee labels faalt in validate-values.sh, tenzij de tenant tenant.extraHostnamesAllowApex: true zet (alleen een boolean). Apex-hosts krijgen een eigen Ingress nextcloud-extra-hosts-apex met nginx.ingress.kubernetes.io/hsts-include-subdomains: "false". De hoofd-Ingress en nextcloud-extra-hosts houden hun waarde. Fixtures: apex-zonder-optin faalt, apex-met-optin slaagt, apex-optin-false en apex-optin-tekst falen. Nieuwe render-check nextcloud-platform/tests/render-extra-hosts.sh (draait mee in verify.sh) controleert de annotatie "false" op de apex-Ingress, "true" op de andere twee en de issuer op beide extra Ingresses.
f3 docs 4e449cb ADDING-TENANT gebruikt www.example.nl als voorbeeld, noemt het gevolg van includeSubDomains op een apex en zegt dat de opt-in alleen mag als de zone-eigenaar dat accepteert.
f4 🟡 Geen eigen of betaald cert per extra host 4e449cb Docs: extra hosts krijgen alleen Let's Encrypt (ze erven cert-manager.io/cluster-issuer). Een eigen of betaald cert per extra host kan nog niet. tenant.frontend.tls (secretName + issuer: none) is het bestaande patroon. De objectvorm is niet gebouwd.

Belangrijk bij f3: de annotatie verandert de header live waarschijnlijk niet

In ConductionNL/cluster-infra (alleen gelezen) staat geen HSTS-instelling. De controller-ConfigMap nginx-ingress-nginx-controller staat in geen enkele repo: het is Helm-release nginx en geen Argo-app (zie scripts/tls-sigalgs-apply.sh). ingress-nginx kent geen HSTS-annotaties per Ingress. hsts, hsts-max-age en hsts-include-subdomains zijn ConfigMap-sleutels. Dat verklaart jouw meting: max-age=31536000; includeSubDomains is de default van de controller, en de 15552000 uit values/common.yaml wordt nooit gelezen. cluster-infra/docs/gateway-api.md heeft dat op 2026-08-17 al gemeten voor hsts-max-age. Met de opt-in krijgt een apex-host live dus nog steeds includeSubDomains, en dat zeggen de docs ook. Wat wel zou werken:

  • hsts-include-subdomains: "false" in de controller-ConfigMap. Dat geldt dan voor elke host op het cluster.
  • Een configuration-snippet op de apex-Ingress die de header zelf zet. Dat werkt alleen als de controller snippet-annotaties toestaat, en dat heb ik niet kunnen nagaan.
  • De apex-host via een Gateway API-route met een eigen route.hsts. charts/tenant-httproute neemt nu maar één host.

Let op: de nginx-sidecar in de pod stuurt zelf ook max-age=15768000; includeSubDomains; preload (values/common.yaml, nginx.config.default). ingress-nginx vervangt die nu, maar een route die zelf geen HSTS zet, laat hem door.

Gedraaid op 4e449cb

  • nextcloud-platform/tests/run-tests.sh: exit 0, 39 geslaagd, 0 gefaald.
  • nextcloud-platform/scripts/validate-values.sh (89 tenants): exit 0, 0 fouten, 5 waarschuwingen (zoals voorheen).
  • scripts/verify.sh (yamllint en kubeconform lokaal op PATH): exit 0, inclusief render-extra-hosts OK.
  • pre-commit run --all-files --hook-stage pre-push: exit 0, alle 6 hooks geslaagd.
  • Render-vergelijking voor alle 89 tenants tegen a17181e (Argo-goTemplate-emulatie, daarna helm template met chart 8.9.0): 178 bestanden, 0 verschillen.
  • De render-check gebruikt helm als template-engine. Getoetst tegen de Go-emulatie van Argo over 89 tenants plus 2 fixtures: 0 verschillen, op een afsluitende lege regel na.
  • Mutaties: apex-annotatie op "true" maakt de render-check rood. De apex-split uitzetten maakt hem ook rood. De apex-check uit de validator halen maakt 3 van de 4 apex-fixtures rood.

Niet geverifieerd

  • curl -sI op een live apex-host: ik heb geen toegang tot het cluster en er draait nog geen apex-host. Op basis van het bovenstaande verwacht ik dat de header live includeSubDomains houdt, totdat de ConfigMap of de route verandert.
  • Of de controller snippet-annotaties toestaat.

🤖 Generated with Claude Code

…tnames

# Conflicts:
#	docs/ADDING-TENANT.md
#	docs/index.md
#	scripts/verify.sh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change/platform Platform- of gemengde wijziging: raakt shared values, Argo-wiring, scripts of policy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants