- Move Simon's architecture documentation into architecture/ (setup, variables, topology, dns, deploy, security, operations plus index and glossary). All cross-repo references point at https://git.digitalboard.ch/Digitalboard/{reference-ansible,dns-zones} via absolute URLs so the docs remain navigable from any context. - Rewrite README.md as a documentation hub: introduction, platform Mermaid overview, comparison of the three repos (docs / digitalboard.core / reference-ansible) and a full table of contents covering architecture, contributing, infrastructure, keycloak, ms-entra and troubleshooting. Addresses the open items from the WKS PoC review (2026-05-26): docs README begrüssungstext + Übersichtsgrafik + Verlinkung der beiden anderen Repos, sowie das Verschieben der Architektur-Doku.
5.7 KiB
DNS topology and ACME zone layout
← Back to Architecture index
Authoritative DNS for everything described in this document runs on
ns1.digitalboard.ch (public 193.43.183.169, DMZ 172.16.9.169)
using Knot DNS. The zone files and Knot config live in the
dns-zones repo; this section explains how the
public service FQDNs, the internal "split-horizon" FQDNs, and the ACME
challenge sub-trees fit together.
Authoritative zones on ns1
| Zone | Purpose | DNSSEC | Dynamic updates |
|---|---|---|---|
digitalboard.ch |
Production zone for the platform itself (auth, cloud, office, bao, …). |
on | none (static zone file) |
_acme.digitalboard.ch |
Parent zone for ACME challenge labels. | on | yes, per-tenant TSIG ACLs (demo-gymb, demo-phbe, demo-mbaz) |
digitalboard._acme.digitalboard.ch |
Delegated child zone for digitalboard.ch ACME updates only. |
off | yes, TSIG acme_update_key_digitalboard |
souveredu.ch |
Demo-tenant zone (gymb, phbe, mbaz sub-labels). |
on | none (static zone file) |
demo-schulen.ch |
Reserve / unused so far. | on | none |
Two different ACME models live here. This is the most common source of confusion when copying a tenant:
digitalboard.chuses a NS-delegated child zone (digitalboard._acme.digitalboard.ch.has its ownNSrecord in_acme.digitalboard.ch). The TSIG key writes into that delegated zone.- The demo tenants (
demo-gymb,demo-phbe,demo-mbaz) share the parent zone_acme.digitalboard.chand are isolated only by Knot ACLupdate-owner-nameon the per-tenant sub-tree (demo-gymb._acme.digitalboard.ch.and below). There is no NS delegation for them.Both work for the ACME flow; the demo model is cheaper to manage but means tenant isolation depends on Knot ACLs, not zone boundaries.
Naming pattern for demo-gymb (template for new tenants)
Public, browser-facing:
cloud.gymb.souveredu.ch CNAME → rvp.gymb.souveredu.ch (193.43.183.131)
auth.gymb.souveredu.ch CNAME → rvp.gymb.souveredu.ch
office.gymb.souveredu.ch CNAME → rvp.gymb.souveredu.ch
s3.gymb.souveredu.ch CNAME → rvp.gymb.souveredu.ch
...
Internal, server-to-server (split horizon):
cloud.int.gymb.souveredu.ch A → 172.16.19.101 (application host)
auth.int.gymb.souveredu.ch A → 172.16.19.101
office.int.gymb.souveredu.ch A → 172.16.19.101
s3.int.gymb.souveredu.ch A → 172.16.19.102 (storage host)
...
Tenant entry IPs:
rvp.gymb.souveredu.ch A → 193.43.183.131 (DMZ Traefik public)
reverseproxy.int.gymb A → 172.16.9.111 (DMZ Traefik internal)
ACME challenge labels (writeable via TSIG acme_update_key_demo_gymb):
_acme-challenge.cloud.gymb CNAME → cloud.demo-gymb._acme.digitalboard.ch
_acme-challenge.cloud.int.gymb CNAME → cloud.int.demo-gymb._acme.digitalboard.ch
...
The .int. family is what makes Nextcloud → Garage, Nextcloud →
Authentik (OIDC), Nextcloud → Collabora (WOPI) etc. bypass the DMZ
Traefik: the backend host's local Traefik presents the right cert
directly, so traffic stays on the backend subnet. Without this,
server-to-server calls would either ride out through the DMZ and back
in, or hit a hostname mismatch on the cert.
TSIG / ACL model
flowchart LR
classDef tenant fill:#dcfce7,stroke:#166534,color:#000
classDef zone fill:#dbeafe,stroke:#1e40af,color:#000
classDef acl fill:#fef3c7,stroke:#92400e,color:#000
subgraph KNOT["ns1.digitalboard.ch (Knot DNS)"]
Z1["_acme.digitalboard.ch<br/>(parent zone)"]:::zone
Z2["digitalboard._acme.digitalboard.ch<br/>(NS-delegated child)"]:::zone
A1["ACL acme_updates_digitalboard<br/>scope: digitalboard._acme.digitalboard.ch."]:::acl
A2["ACL acme_updates_demo_gymb<br/>scope: demo-gymb._acme.digitalboard.ch."]:::acl
A3["ACL acme_updates_demo_phbe<br/>scope: demo-phbe._acme.digitalboard.ch."]:::acl
A4["ACL acme_updates_demo_mbaz<br/>scope: demo-mbaz._acme.digitalboard.ch."]:::acl
end
DB["digitalboard.ch Traefik<br/>TSIG: acme_update_key_digitalboard"]:::tenant
GY["demo-gymb Traefik<br/>TSIG: acme_update_key_demo_gymb"]:::tenant
PH["demo-phbe Traefik<br/>TSIG: acme_update_key_demo_phbe"]:::tenant
MB["demo-mbaz Traefik<br/>TSIG: acme_update_key_demo_mbaz"]:::tenant
DB -- nsupdate TXT --> A1
GY -- nsupdate TXT --> A2
PH -- nsupdate TXT --> A3
MB -- nsupdate TXT --> A4
A1 -- writes into --> Z2
A2 -- writes into --> Z1
A3 -- writes into --> Z1
A4 -- writes into --> Z1
Each ACL is restricted to update-type: TXT and
update-owner-match: sub-or-equal under the tenant prefix, so a
leaked tenant key cannot write outside its own ACME sub-tree and cannot
modify non-TXT records (no A/CNAME/NS hijack).
Traefik variables that bind to this layout
From inventories/demo-gymburgdorf/group_vars/traefik_servers/traefik.yml:
| Traefik variable | Value for demo-gymb |
Bound to |
|---|---|---|
traefik_acme_dns_provider |
rfc2136 |
Knot dynamic-update endpoint |
traefik_acme_dns_zone |
demo-gymb._acme.digitalboard.ch |
Per-tenant write scope on ns1 |
traefik_acme_tsig_key_name |
acme_update_key_demo_gymb |
Matches key: entry in knot.conf |
traefik_acme_tsig_secret |
Bao lookup | See security.md |
A tenant whose ACME zone does not match the Knot ACL
update-owner-name will get REFUSED on nsupdate and ACME issuance
will silently retry until the renewal window expires.