diff --git a/README.md b/README.md index fe446d0..b5def5b 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ Diensten ist nicht die Domain, sondern **wohin der Upstream zeigt**: ![Architektur-Übersicht](doc/Architektur.svg) -> Diagramm-Quelle: [`doc/Architektur.svg`](doc/Architektur.svg) (daraus lässt -> sich bei Bedarf eine PDF/PNG erzeugen). +> Diagramm-Quelle: [`doc/Architektur.svg`](doc/Architektur.svg) — auch als +> [`doc/Architektur.png`](doc/Architektur.png) (für Viewer ohne SVG-Support). **Bausteine (Docker-Container, siehe `docker-compose.yaml`):** @@ -77,8 +77,8 @@ Ruft man die nackte Domain `server.schooltech.ch` auf, erscheint die ![Portal-Ansicht](doc/Portal.svg) *Oben die Navigationsleiste mit Logo und Dienst-Buttons, darunter der gewählte -Dienst im iFrame. (Quelle: [`doc/Portal.svg`](doc/Portal.svg), auch als -[`doc/Portal.pdf`](doc/Portal.pdf).)* +Dienst im iFrame. (Quelle: [`doc/Portal.svg`](doc/Portal.svg) — auch als +[`doc/Portal.png`](doc/Portal.png) und [`doc/Portal.pdf`](doc/Portal.pdf).)* 1. Seite öffnen → ist man nicht eingeloggt, zeigt der Button **„Login“**. 2. Login (User/Passwort) → der Auth-Service setzt ein Session-Cookie für die @@ -205,7 +205,9 @@ Läuft **automatisch beim Containerstart** des nginx-Containers (liegt als `/docker-entrypoint.d/40-connect-proxies.sh`). Pro `forwarding.conf`-Zeile: 1. **Globale Map + Resolver** schreiben (`_globals.generated.conf`) – nötig für - WebSockets und für die DNS-Auflösung der Upstreams zur Laufzeit. + WebSockets und für die DNS-Auflösung der Upstreams zur Laufzeit. Außerdem ein + **443-Default-Server** (`00-default-server.generated.conf`), der unbekanntes + SNI per `ssl_reject_handshake` abweist (siehe Hinweis unten). 2. **Alte generierte Configs** löschen (`*-https.generated.conf`, `*-http-redirect.generated.conf`). 3. Pro Dienst eine vHost-Datei erzeugen – mit Fallunterscheidung: @@ -215,7 +217,8 @@ Läuft **automatisch beim Containerstart** des nginx-Containers (liegt als optional `proxy_ssl_verify`, optional `auth_request`-Schutz. - **DNS nicht auflösbar** → statt Proxy ein **503-Platzhalter** („Service nicht erreichbar“), damit nginx trotzdem startet. - - **Kein Zertifikat vorhanden** → vHost wird **übersprungen** (keine 443-Conf). + - **Kein Zertifikat vorhanden** → vHost wird **übersprungen** (keine 443-Conf); + solche Anfragen fängt der 443-Default-Server ab (siehe Hinweis unten). - Bei `http_behavior = redirect` zusätzlich ein **80→443-Redirect** (mit Ausnahme für die ACME-Challenge). 4. Abschließend **`nginx -t`** zur Syntaxprüfung. @@ -223,6 +226,14 @@ Läuft **automatisch beim Containerstart** des nginx-Containers (liegt als So ist das System robust: fehlt ein Zertifikat oder ein Backend, fällt nur der betroffene Dienst aus – nginx selbst läuft weiter. +> **Anti-Leak / Default-Server:** Ohne expliziten Default bedient nginx eine +> Anfrage mit unbekanntem `server_name` mit dem *ersten* 443-Block (alphabetisch – +> das war `ai.server.schooltech.ch`). Damit eine Domain ohne passenden vHost +> (z. B. fehlendes Zertifikat) **nicht still** bei einem fremden Dienst landet, +> erzeugt das Skript einen Catch-all `listen 443 ssl default_server; +> ssl_reject_handshake on;` – unbekanntes SNI bekommt einen sauberen TLS-Abbruch. +> `server.schooltech.ch` & Co. treffen ihren eigenen vHost weiterhin per SNI-Match. + > **Aktiver Pfad vs. Referenz:** Die vHosts werden zur Laufzeit von > `connect-proxies.sh` aus `forwarding.conf` erzeugt. Die statischen Dateien in > `nginxPages/` sind **nicht** in `docker-compose.yaml` gemountet und dienen nur diff --git a/connect-proxies.sh b/connect-proxies.sh index 009f084..e85b5c2 100755 --- a/connect-proxies.sh +++ b/connect-proxies.sh @@ -40,6 +40,23 @@ map \$http_upgrade \$connection_upgrade { $RESOLVER_LINE NGINX +# 1b) Default-Server für 443: weist unbekanntes/leeres SNI sauber ab, +# statt es an den alphabetisch ersten vHost zu "vererben" (Anti-Leak). +# Greift auch dann, wenn einer Domain das Zertifikat fehlt. +DEFAULT_443_FILE="$CONF_DIR/00-default-server.generated.conf" +cat > "$DEFAULT_443_FILE" <<'NGINX' +# Automatisch generiert – nicht editieren +# Fängt alle HTTPS-Verbindungen ab, deren SNI auf keinen echten vHost passt, +# und lehnt den TLS-Handshake ab. So landet eine unbekannte/zertifikatslose +# Domain NIE still beim ersten vHost. Braucht per ssl_reject_handshake +# bewusst KEIN eigenes Zertifikat. +server { + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + ssl_reject_handshake on; +} +NGINX + # 2) Alte generierte Confs entfernen rm -f "$CONF_DIR/"*"$HTTPS_SUFFIX" 2>/dev/null || true rm -f "$CONF_DIR/"*"$HTTP_REDIRECT_SUFFIX" 2>/dev/null || true diff --git a/doc/Architektur.png b/doc/Architektur.png new file mode 100644 index 0000000..581df18 Binary files /dev/null and b/doc/Architektur.png differ diff --git a/doc/Portal.png b/doc/Portal.png new file mode 100644 index 0000000..aac024b Binary files /dev/null and b/doc/Portal.png differ