From 3047413a412d5a2191684d812b9a51a126be2cca Mon Sep 17 00:00:00 2001 From: hkoeck Date: Sat, 7 Mar 2026 17:21:22 +0100 Subject: [PATCH] Initialize reverse proxy and TLS workshop lab setup --- .env.example | 1 + .gitignore | 4 + Makefile | 26 +++ README.md | 118 ++++++++++++ backends/a/index.html | 74 ++++++++ backends/b/index.html | 74 ++++++++ certs/.gitkeep | 0 challenges/README.md | 229 +++++++++++++++++++++++ challenges/easyrsa-hints.md | 125 +++++++++++++ challenges/wireshark-hints.md | 116 ++++++++++++ docker-compose.yml | 26 +++ proxy/html/challenges.html | 333 +++++++++++++++++++++++++++++++++ proxy/html/hints.html | 198 ++++++++++++++++++++ proxy/html/index.html | 157 ++++++++++++++++ proxy/html/solutions.html | 341 ++++++++++++++++++++++++++++++++++ proxy/nginx.conf | 47 +++++ proxy/nginx.tls.example.conf | 66 +++++++ scripts/bootstrap-wsl.sh | 36 ++++ scripts/bootstrap.sh | 5 + scripts/compose.sh | 11 ++ scripts/reset-lab.sh | 32 ++++ scripts/workshop.ps1 | 43 +++++ 22 files changed, 2062 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 backends/a/index.html create mode 100644 backends/b/index.html create mode 100644 certs/.gitkeep create mode 100644 challenges/README.md create mode 100644 challenges/easyrsa-hints.md create mode 100644 challenges/wireshark-hints.md create mode 100644 docker-compose.yml create mode 100644 proxy/html/challenges.html create mode 100644 proxy/html/hints.html create mode 100644 proxy/html/index.html create mode 100644 proxy/html/solutions.html create mode 100644 proxy/nginx.conf create mode 100644 proxy/nginx.tls.example.conf create mode 100755 scripts/bootstrap-wsl.sh create mode 100755 scripts/bootstrap.sh create mode 100755 scripts/compose.sh create mode 100755 scripts/reset-lab.sh create mode 100644 scripts/workshop.ps1 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d39c2da --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +HTTP_PORT=8080 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8def2ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +certs/* +!certs/.gitkeep +__pycache__/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1fbe5bc --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +.PHONY: bootstrap up down logs redeploy proxy-reload reset reset-hard + +bootstrap: + ./scripts/bootstrap.sh + +up: + ./scripts/compose.sh up -d --build + +redeploy: + ./scripts/compose.sh up -d --build --remove-orphans + ./scripts/compose.sh restart reverse-proxy + +proxy-reload: + ./scripts/compose.sh restart reverse-proxy + +down: + ./scripts/compose.sh down + +logs: + ./scripts/compose.sh logs -f + +reset: + ./scripts/reset-lab.sh + +reset-hard: + ./scripts/reset-lab.sh --hard diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d8fc4a --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +# HTL Workshop - Reverse Proxy & TLS Lab + +Dieses Repo ist ein **leichtes HTTP-Basissetup** fuer einen Workshop mit Fokus auf: + +- Reverse Proxy Grundlagen (Nginx) +- Routing auf mehrere Backends +- TLS/HTTPS manuell umsetzen (z. B. Easy-RSA) +- Security-Hardening im Proxy + +Die Aufgaben sind absichtlich auf **manuelle Konfiguration** ausgelegt, nicht auf App-Entwicklung. + +## Architektur + +- `reverse-proxy` (Nginx, Port `8080`) +- `backend-a` (einfaches statisches Backend) +- `backend-b` (einfaches statisches Backend) + +Basisrouten nach dem Start: + +- `http://localhost:8080/` -> Startseite +- `http://localhost:8080/service/a` -> Backend A via Proxy +- `http://localhost:8080/service/b` -> Backend B via Proxy + +## WebUI Struktur + +- `http://localhost:8080/` -> Ueberblick + Navigation +- `http://localhost:8080/challenges.html` -> Detaillierte Challenges +- `http://localhost:8080/hints.html` -> Hint Cheatsheet +- `http://localhost:8080/solutions.html` -> Musterloesungen + +## Voraussetzungen (WSL oder Linux) + +1. Docker + Compose (`docker compose` oder `docker-compose`) +2. Bei Windows: Docker Desktop + WSL Integration aktiv +3. Optional fuer TLS-Challenges: `easy-rsa`, `openssl`, `wireshark` + +## Schnellstart + +```bash +./scripts/bootstrap.sh +``` + +### Start auf Windows + +Empfohlen: ueber WSL starten (nicht nativ in PowerShell ohne WSL). + +Option A (WSL Terminal): + +```bash +./scripts/bootstrap.sh +``` + +Option B (PowerShell Wrapper): + +```powershell +./scripts/workshop.ps1 -Action bootstrap +``` + +Der PowerShell-Wrapper braucht kein `make` und ruft die Linux-Skripte direkt in WSL auf. + +Falls PowerShell das Script blockiert: + +```powershell +Set-ExecutionPolicy -Scope Process Bypass +``` + +Weitere Aktionen aus PowerShell: + +```powershell +./scripts/workshop.ps1 -Action redeploy +./scripts/workshop.ps1 -Action proxy-reload +./scripts/workshop.ps1 -Action reset +``` + +Wenn mehrere Distros installiert sind: + +```powershell +./scripts/workshop.ps1 -Action bootstrap -Distro Ubuntu-24.04 +``` + +Der Script: + +- prueft Docker + Compose +- erstellt `.env` aus `.env.example` (falls nicht vorhanden) +- startet den Stack + +## Neu deployen / resetten + +```bash +make redeploy +make proxy-reload +make reset +``` + +- `redeploy`: build + restart aller Services +- `proxy-reload`: nur Reverse Proxy restart +- `reset`: Container/Netzwerk/Volumes aufraeumen + +Hinweis: `make redeploy` startet zusaetzlich den Reverse Proxy neu, damit Nginx-Config-Aenderungen sicher aktiv sind. + +## Kurz testen + +```bash +curl http://localhost:8080/service/a +curl http://localhost:8080/service/b +``` + +## Fokus im Workshop + +Die Basis ist bewusst nur HTTP. HTTPS ist Teil der Aufgaben: + +- CA + Server-Zertifikat selbst erstellen +- Nginx auf 443 erweitern +- Root-CA importieren +- HSTS und TLS-Haertung umsetzen +- TLS mitschneiden und analysieren + +Vollstaendige TLS-Beispielkonfiguration mit HSTS: `proxy/nginx.tls.example.conf`. diff --git a/backends/a/index.html b/backends/a/index.html new file mode 100644 index 0000000..978fd5c --- /dev/null +++ b/backends/a/index.html @@ -0,0 +1,74 @@ + + + + + + Backend A + + + +
+ Backend A +

Reverse Proxy Target A

+

This page is served by backend A and reached through Nginx reverse proxy.

+

Route example: /service/a

+
+ + diff --git a/backends/b/index.html b/backends/b/index.html new file mode 100644 index 0000000..5e208f4 --- /dev/null +++ b/backends/b/index.html @@ -0,0 +1,74 @@ + + + + + + Backend B + + + +
+ Backend B +

Reverse Proxy Target B

+

This page is served by backend B and reached through Nginx reverse proxy.

+

Route example: /service/b

+
+ + diff --git a/certs/.gitkeep b/certs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/challenges/README.md b/challenges/README.md new file mode 100644 index 0000000..e44004d --- /dev/null +++ b/challenges/README.md @@ -0,0 +1,229 @@ +# Workshop Challenges (Reverse Proxy + TLS Focus) + +Diese Aufgaben sind auf **manuelle Proxy-Konfiguration** ausgelegt. + +Abgabe pro Aufgabe: + +- kurzer Demo-Run (1-3 Minuten) +- Done-Check Command +- 2-3 Saetze: Was wurde geaendert und warum? + +## Arbeitsmodus + +1. Datei anpassen (`proxy/nginx.conf`, `docker-compose.yml`). +2. Deployen mit `make redeploy` oder `make proxy-reload`. +3. Testen mit `curl`/`openssl`. +4. Wenn etwas kaputt ist: `./scripts/compose.sh logs reverse-proxy`. + +--- + +## Easy + +### 1) Routing verstehen + +**Ziel** +- Reverse Proxy leitet Requests auf unterschiedliche Backends. + +**Muss** +- `service/a` und `service/b` aufrufen. +- Unterschiede in den Antworten erklaeren. + +**Done-Check** +```bash +curl http://localhost:8080/service/a +curl http://localhost:8080/service/b +``` + +### 2) Drittes Backend manuell hinzufuegen + +**Ziel** +- Proxy auf ein neues Backend erweitern. + +**Dateien** +- `docker-compose.yml` +- `proxy/nginx.conf` + +**Muss** +- Service `backend-c` anlegen. +- Upstream + Route `/service/c` konfigurieren. + +**Done-Check** +```bash +curl http://localhost:8080/service/c +``` + +### 3) Eigene Route mit Rewrite + +**Ziel** +- URL-Struktur im Proxy gestalten. + +**Datei** +- `proxy/nginx.conf` + +**Muss** +- Route `/demo/a` soll intern auf Backend A zeigen. +- Implementierung ueber `rewrite` oder eigene `location`. + +**Done-Check** +```bash +curl http://localhost:8080/demo/a +``` + +--- + +## Medium + +### 4) Security Headers setzen + +**Ziel** +- Grundlegende Header-Haertung im Proxy. + +**Datei** +- `proxy/nginx.conf` + +**Muss** +- Setze mindestens: + - `X-Content-Type-Options: nosniff` + - `X-Frame-Options: DENY` + - `Referrer-Policy: no-referrer` + +**Done-Check** +```bash +curl -I http://localhost:8080/ +``` + +### 5) Interne Route absichern + +**Ziel** +- Route nur lokal erreichbar machen. + +**Datei** +- `proxy/nginx.conf` + +**Muss** +- Neue Route `/internal/status` bauen. +- Nur `127.0.0.1` erlauben, alle anderen verbieten. + +**Done-Check** +```bash +curl -i http://localhost:8080/internal/status +``` + +### 6) Logging verbessern + +**Ziel** +- Proxy-Debugging ueber bessere Logs. + +**Datei** +- `proxy/nginx.conf` + +**Muss** +- Eigenes `log_format` mit Upstream-Infos erstellen. +- Access Log auf dieses Format umstellen. + +**Done-Check** +```bash +curl http://localhost:8080/service/a +./scripts/compose.sh logs reverse-proxy +``` + +--- + +## Hard (TLS) + +### 7) HTTPS von 0 mit Easy-RSA + +**Ziel** +- Eigene CA + Server-Zertifikat fuer `localhost`. + +**Dateien** +- `docker-compose.yml` +- `proxy/nginx.conf` + +**Muss** +- Cert fuer `localhost` erzeugen. +- Proxy auf `443` aktivieren (z. B. `8443:443`). +- Root-CA importieren. + +**Done-Check** +```bash +curl https://localhost:8443/service/a +``` + +### 8) HTTP -> HTTPS Redirect + +**Ziel** +- Klarer Redirect-Flow. + +**Datei** +- `proxy/nginx.conf` + +**Muss** +- HTTP Requests auf HTTPS umleiten. +- `/healthz` darf optional auf HTTP bleiben. + +**Done-Check** +```bash +curl -I http://localhost:8080/service/a +``` + +### 9) TLS Haertung + Chain Check + +**Ziel** +- Bessere TLS-Defaults + saubere Validierung. + +**Datei** +- `proxy/nginx.conf` + +**Muss** +- TLS auf 1.2/1.3 beschraenken. +- HSTS aktivieren (`Strict-Transport-Security`). +- Zertifikatskette pruefen. + +Hinweis: Im HTTP-Basissetup ist HSTS absichtlich **noch nicht** aktiv. Das ist Teil der Aufgabe. + +**Done-Check** +```bash +curl -I https://localhost:8443/service/a +openssl s_client -connect localhost:8443 -servername localhost +``` + +--- + +## Bonus Expert + +### 10) Wireshark: HTTP vs HTTPS + +**Ziel** +- Sichtbar machen, was verschluesselt ist und was nicht. + +**Muss** +- HTTP Traffic auf `8080` mitschneiden. +- HTTPS Traffic auf `8443` mitschneiden. +- TLS Handshake markieren (`ClientHello`, `ServerHello`, `Certificate`). +- Technisch erklaeren, warum HTTP lesbar ist und HTTPS nicht. + +**Vorgehen (empfohlen)** +1. Capture auf `lo` (oder passendes Interface) starten, Filter `tcp.port == 8080`. +2. `curl http://localhost:8080/service/a` senden und HTTP-Stream zeigen. +3. Capture auf `tcp.port == 8443` oder `tls` umstellen. +4. `curl https://localhost:8443/service/a` senden. +5. Handshake-Pakete markieren und kurz erklaeren. + +**Optional** +- TLS Decrypt mit `SSLKEYLOGFILE`. + +**Done-Check** +- 3-4 Screenshots + 3-5 Bulletpoints Auswertung. + +**Typische Fehler** +- falsches Interface gewaehlt +- HTTPS noch nicht korrekt aktiv +- Keylog gesetzt, aber Browser nicht aus derselben Shell gestartet + +--- + +## Zusatz-Hints + +- `challenges/easyrsa-hints.md` +- `challenges/wireshark-hints.md` diff --git a/challenges/easyrsa-hints.md b/challenges/easyrsa-hints.md new file mode 100644 index 0000000..1e1088e --- /dev/null +++ b/challenges/easyrsa-hints.md @@ -0,0 +1,125 @@ +# Easy-RSA Hint Card (optional) + +Diese Hinweise koennen Teams nutzen, wenn sie bei der HTTPS-Challenge feststecken. + +## 1) Easy-RSA installieren (WSL/Linux) + +Fedora: + +```bash +sudo dnf install -y easy-rsa openssl +``` + +Ubuntu/WSL: + +```bash +sudo apt update +sudo apt install -y easy-rsa openssl +``` + +## 2) PKI vorbereiten + +```bash +mkdir -p certs/easyrsa +cp -r /usr/share/easy-rsa/* certs/easyrsa/ +cd certs/easyrsa +./easyrsa init-pki +``` + +## 3) CA erstellen + +```bash +./easyrsa build-ca nopass +``` + +## 4) Server-Zertifikat fuer localhost + +```bash +./easyrsa gen-req localhost nopass +./easyrsa sign-req server localhost +``` + +## 5) Dateien fuer Nginx bereitstellen + +Typische Dateien: + +- `pki/issued/localhost.crt` +- `pki/private/localhost.key` +- `pki/ca.crt` + +Danach in `proxy/nginx.conf` TLS aktivieren und in `docker-compose.yml` Port `443` mappen. + +### Compose-Mindestbeispiel + +```yaml +services: + reverse-proxy: + ports: + - "8080:80" + - "8443:443" + volumes: + - ./proxy/nginx.conf:/etc/nginx/nginx.conf:ro,z + - ./proxy/html:/usr/share/nginx/html:ro,z + - ./certs/easyrsa/pki:/etc/nginx/pki:ro,z +``` + +### Nginx-Mindestbeispiel + +```nginx +server { + listen 443 ssl; + server_name localhost; + + ssl_certificate /etc/nginx/pki/issued/localhost.crt; + ssl_certificate_key /etc/nginx/pki/private/localhost.key; + ssl_protocols TLSv1.2 TLSv1.3; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + location /service/a { + proxy_pass http://backend_a/; + } +} +``` + +## 6) Root-CA importieren (Trust Store) + +Fedora: + +```bash +sudo cp pki/ca.crt /etc/pki/ca-trust/source/anchors/htl-workshop-root-ca.crt +sudo update-ca-trust +``` + +Ubuntu/Debian: + +```bash +sudo cp pki/ca.crt /usr/local/share/ca-certificates/htl-workshop-root-ca.crt +sudo update-ca-certificates +``` + +Windows (PowerShell als Admin, optional): + +```powershell +certutil -addstore -f Root C:\path\to\ca.crt +``` + +## 7) Test ohne -k + +Nach Import sollte HTTPS ohne Insecure-Flag funktionieren: + +```bash +curl https://localhost:8443/service/a +``` + +Falls CA nicht global importiert ist: + +```bash +curl --cacert pki/ca.crt https://localhost:8443/service/a +``` + +## 8) Typische Fehlerbilder + +- `curl: (60) SSL certificate problem` -> Root-CA nicht importiert oder falsche CA. +- Browser war offen waehrend CA-Import -> Browser neu starten. +- `permission denied` beim Nginx-Config-Mount (Fedora/SELinux) -> Volumes mit `:z` mounten. +- Zertifikat ohne `localhost` -> SAN/CN passt nicht zum Hostnamen. diff --git a/challenges/wireshark-hints.md b/challenges/wireshark-hints.md new file mode 100644 index 0000000..d9b01ee --- /dev/null +++ b/challenges/wireshark-hints.md @@ -0,0 +1,116 @@ +# Wireshark Hint Card (optional) + +Diese Hinweise helfen bei der Bonus-Challenge mit Paketmitschnitt. + +## 1) Installation + +Fedora: + +```bash +sudo dnf install -y wireshark wireshark-cli +``` + +Ubuntu/WSL: + +```bash +sudo apt update +sudo apt install -y wireshark tshark +``` + +## 2) Interface waehlen + +- Linux lokal: meist `lo` (Loopback) fuer `localhost` +- Docker-Welt: ggf. `docker0` bzw. Bridge-Interface + +## 3) HTTP zuerst (Klartext) + +1. Mitschnitt starten +2. Request senden: + +```bash +curl http://localhost:8080/service/a +``` + +3. In Wireshark nach `http` oder `tcp.port == 8080` filtern + +## 4) Root-CA importieren und HTTPS ohne -k testen + +Voraussetzung: HTTPS-Challenge umgesetzt (Port `8443` aktiv). + +Fedora: + +```bash +sudo cp certs/easyrsa/pki/ca.crt /etc/pki/ca-trust/source/anchors/htl-workshop-root-ca.crt +sudo update-ca-trust +``` + +Ubuntu/Debian: + +```bash +sudo cp certs/easyrsa/pki/ca.crt /usr/local/share/ca-certificates/htl-workshop-root-ca.crt +sudo update-ca-certificates +``` + +Test: + +```bash +curl https://localhost:8443/service/a +``` + +## 5) HTTPS danach mitschneiden (verschluesselt) + +Filter: + +```text +tcp.port == 8443 +``` + +oder + +```text +tls +``` + +Handshake schnell finden: + +```text +tls.handshake +``` + +Nur Zertifikats-Nachrichten: + +```text +tls.handshake.type == 11 +``` + +## 6) Optional: TLS in Wireshark entschluesseln + +1. Vor Browser-Start setzen: + +```bash +export SSLKEYLOGFILE="$HOME/sslkeys.log" +``` + +2. Browser aus derselben Shell starten und HTTPS-Request erzeugen. +3. In Wireshark unter TLS-Preferences `sslkeys.log` als Key Log File setzen. +4. Mitschnitt erneut laden. + +CLI-Alternative mit tshark (optional): + +```bash +tshark -i lo -f "tcp port 8443" +``` + +## 7) Was ihr zeigen sollt + +- HTTP-Mitschnitt: URL/Headers lesbar +- HTTPS-Mitschnitt: TLS Handshake sichtbar, Nutzdaten nicht im Klartext +- Nach CA-Import funktioniert `curl https://localhost:8443/...` ohne `-k` +- Optional: Mit Key Log koennen HTTP-Details im TLS-Stream sichtbar werden + +## 8) Erwartete Abgabe (kurz) + +- Screenshot 1: HTTP-Request mit lesbaren Daten +- Screenshot 2: HTTPS-Request mit TLS-Handshake +- Screenshot 3 (optional): entschluesselter TLS-Stream via Key Log +- 3 Bulletpoints: Unterschied HTTP vs HTTPS in euren eigenen Worten diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bde45ca --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +name: htl-workshop + +services: + reverse-proxy: + image: nginx:1.27-alpine + container_name: workshop-proxy + depends_on: + - backend-a + - backend-b + ports: + - "${HTTP_PORT:-8080}:80" + volumes: + - ./proxy/nginx.conf:/etc/nginx/nginx.conf:ro,z + - ./proxy/html:/usr/share/nginx/html:ro,z + + backend-a: + image: nginx:1.27-alpine + container_name: workshop-backend-a + volumes: + - ./backends/a:/usr/share/nginx/html:ro,z + + backend-b: + image: nginx:1.27-alpine + container_name: workshop-backend-b + volumes: + - ./backends/b:/usr/share/nginx/html:ro,z diff --git a/proxy/html/challenges.html b/proxy/html/challenges.html new file mode 100644 index 0000000..a07dc53 --- /dev/null +++ b/proxy/html/challenges.html @@ -0,0 +1,333 @@ + + + + + + HTL Workshop Challenges + + + +
+
+

Challenges - Reverse Proxy & TLS

+

Fokus: manuelle Proxy-Konfiguration, saubere Security-Entscheidungen, TLS von Grund auf.

+ +
+ +
+

Abgabe-Format (fuer jede Challenge)

+
    +
  • 1) Demo: 1-3 Minuten, live zeigen.
  • +
  • 2) Check: Done-Check Command ausfuehren.
  • +
  • 3) Erklaerung: 2-3 Saetze: was, warum, Sicherheitswirkung.
  • +
+
+ +
+

Easy

+ +
+ Easy 1) Routing verstehen +

Ziel: Verstehen, wie der Reverse Proxy Requests anhand des Pfads verteilt.

+

Muss:

+
    +
  • /service/a und /service/b aufrufen.
  • +
  • Unterschied der Antworten erklaeren.
  • +
  • In 1 Satz erklaeren, welche location auf welchen upstream zeigt.
  • +
+

Done-Check:

+
curl http://localhost:8080/service/a
+curl http://localhost:8080/service/b
+
+ +
+ Easy 2) backend-c manuell hinzufuegen +

Ziel: Proxy-Setup sinnvoll erweitern, ohne bestehenden Traffic zu brechen.

+

Dateien: docker-compose.yml, proxy/nginx.conf

+

Muss:

+
    +
  • Service backend-c in Compose anlegen.
  • +
  • In Nginx upstream backend_c und location /service/c konfigurieren.
  • +
  • Routing fuer A/B darf danach nicht kaputt sein.
  • +
+

Done-Check:

+
curl http://localhost:8080/service/c
+curl http://localhost:8080/service/a
+curl http://localhost:8080/service/b
+
+ +
+ Easy 3) Eigene Route mit Rewrite +

Ziel: URL-Design vom Backend entkoppeln.

+

Muss:

+
    +
  • Neue public Route (z. B. /demo/a) einbauen.
  • +
  • Intern auf bestehenden Backend-Pfad umbiegen (Rewrite oder eigene Route).
  • +
  • In der Demo erklaeren, warum solche Pfad-Abstraktion nuetzlich ist.
  • +
+

Done-Check:

+
curl http://localhost:8080/demo/a
+
+
+ +
+

Medium

+ +
+ Medium 4) Security Headers setzen +

Ziel: Browser-Schutzmechanismen bewusst aktivieren.

+

Muss:

+
    +
  • X-Content-Type-Options: nosniff
  • +
  • X-Frame-Options: DENY
  • +
  • Referrer-Policy: no-referrer
  • +
+

Done-Check:

+
curl -I http://localhost:8080/
+

Abgabe: kurz erklaeren, welchen Angriff jeder Header erschwert.

+
+ +
+ Medium 5) Interne Route absichern +

Ziel: Zugangskontrolle direkt im Proxy umsetzen.

+

Muss:

+
    +
  • Route /internal/status einbauen.
  • +
  • Nur 127.0.0.1 erlauben, alle anderen verbieten.
  • +
  • Sinn der Route erklaeren (z. B. intern fuer Ops/Monitoring).
  • +
+

Done-Check:

+
curl -i http://localhost:8080/internal/status
+
+ +
+ Medium 6) Logging verbessern +

Ziel: Fehler schneller eingrenzen koennen.

+

Muss:

+
    +
  • Eigenes log_format mit Upstream-Daten erstellen.
  • +
  • Mindestens enthalten: status, upstream_addr, Timing.
  • +
  • Access Log auf das neue Format stellen.
  • +
+

Done-Check:

+
curl http://localhost:8080/service/a
+./scripts/compose.sh logs reverse-proxy
+
+
+ +
+

Hard (TLS)

+ +
+ Hard 7) HTTPS von 0 (Easy-RSA) +

Ziel: Eigene PKI und TLS-Endpoint fuer den Proxy aufbauen.

+

Muss:

+
    +
  • Server-Zertifikat fuer localhost erstellen.
  • +
  • Proxy auf 443 erweitern (z. B. 8443:443).
  • +
  • Root-CA importieren und ohne -k testen.
  • +
+

Done-Check:

+
curl https://localhost:8443/service/a
+
+ +
+ Hard 8) HTTP -> HTTPS Redirect +

Ziel: Client sauber auf TLS umleiten.

+

Muss:

+
    +
  • Alle HTTP Requests nach HTTPS redirecten.
  • +
  • /healthz darf optional auf HTTP bleiben.
  • +
  • Redirect-Statuscode + Location Header erklaeren.
  • +
+

Done-Check:

+
curl -I http://localhost:8080/service/a
+
+ +
+ Hard 9) TLS Haertung + Chain Check + HSTS +

Ziel: TLS nicht nur aktivieren, sondern sinnvoll haerten.

+

Muss:

+
    +
  • TLS auf 1.2/1.3 beschraenken.
  • +
  • HSTS Header setzen: Strict-Transport-Security.
  • +
  • Zertifikatskette pruefen und in der Demo erklaeren.
  • +
+

Hinweis: Im HTTP-Basissetup ist HSTS absichtlich noch nicht aktiv.

+

Done-Check:

+
curl -I https://localhost:8443/service/a
+openssl s_client -connect localhost:8443 -servername localhost
+
+
+ +
+

Bonus Expert

+ +
+ Expert 10) Wireshark: HTTP vs HTTPS sauber ausarbeiten +

Ziel: Nachweisbar zeigen, was bei HTTP lesbar und bei HTTPS geschuetzt ist.

+

Muss:

+
    +
  1. HTTP Capture auf Port 8080, Request erzeugen, Klartext markieren.
  2. +
  3. HTTPS Capture auf Port 8443, TLS Handshake markieren.
  4. +
  5. Pakete mit ClientHello, ServerHello, Certificate zeigen.
  6. +
  7. Kurze Analyse: Was sieht ein Angreifer im HTTP-Fall vs im HTTPS-Fall?
  8. +
+

Optional: TLS Decrypt via SSLKEYLOGFILE und Unterschied vorher/nachher erklaeren.

+

Done-Check / Abgabe:

+
    +
  • Mindestens 3 Screenshots mit Markierungen.
  • +
  • 3-5 Bulletpoints als technische Schlussfolgerung.
  • +
  • Ein Satz zu Grenzen der Methode (z. B. nur mit Keylog decryption).
  • +
+
+
+
+ + diff --git a/proxy/html/hints.html b/proxy/html/hints.html new file mode 100644 index 0000000..a075258 --- /dev/null +++ b/proxy/html/hints.html @@ -0,0 +1,198 @@ + + + + + + HTL Workshop Hint Cheatsheet + + + +
+
+

Hint Cheatsheet

+

+ Kompakte Hilfe fuer Setup, TLS und Wireshark. Fuer komplette Aufgaben siehe das + Challenge Board und fuer Korrekturhilfe das + Solutions Board. +

+ +
+ +
+

Deploy & Reset

+
+
+

Start

+
./scripts/bootstrap.sh
+./scripts/compose.sh ps
+

Windows PowerShell: ./scripts/workshop.ps1 -Action bootstrap

+
+
+

Neu deployen

+
make redeploy
+make proxy-reload
+

PowerShell: ./scripts/workshop.ps1 -Action redeploy

+
+
+

Reset

+
make reset
+make bootstrap
+

PowerShell: ./scripts/workshop.ps1 -Action reset

+
+
+
+ +
+

Easy-RSA & CA Import

+
+
+

PKI Quickstart

+
mkdir -p certs/easyrsa
+cp -r /usr/share/easy-rsa/* certs/easyrsa/
+cd certs/easyrsa
+./easyrsa init-pki
+./easyrsa build-ca nopass
+./easyrsa gen-req localhost nopass
+./easyrsa sign-req server localhost
+
+
+

Fedora CA Import

+
sudo cp certs/easyrsa/pki/ca.crt \
+  /etc/pki/ca-trust/source/anchors/htl-workshop-root-ca.crt
+sudo update-ca-trust
+
+
+

HTTPS Check

+
curl https://localhost:8443/service/a
+curl -I https://localhost:8443/service/a
+openssl s_client -connect localhost:8443 -servername localhost
+

Bei TLS-Haertung auf Strict-Transport-Security im Header achten.

+

Komplettes TLS-Beispiel: proxy/nginx.tls.example.conf

+
+
+
+ +
+

Wireshark Quick Hints

+
+
+

Filter

+
    +
  • http
  • +
  • tcp.port == 8443
  • +
  • tls.handshake
  • +
  • tls.handshake.type == 11
  • +
+
+
+

TLS Decrypt (optional)

+
export SSLKEYLOGFILE="$HOME/sslkeys.log"
+

Browser aus derselben Shell starten und Datei in Wireshark als TLS Key Log setzen.

+
+
+
+
+ + diff --git a/proxy/html/index.html b/proxy/html/index.html new file mode 100644 index 0000000..9d63438 --- /dev/null +++ b/proxy/html/index.html @@ -0,0 +1,157 @@ + + + + + + HTL Reverse Proxy Lab + + + +
+
+

HTL Reverse Proxy & TLS Lab

+

+ Basis läuft mit HTTP. Ziel im Workshop: Reverse Proxy verstehen und HTTPS/TLS manuell + aufbauen. +

+ +
+ +
+

Setup Quickcheck

+
    +
  • curl http://localhost:8080/service/a
  • +
  • curl http://localhost:8080/service/b
  • +
  • ./scripts/compose.sh ps
  • +
+
+ +
+

Saubere Struktur

+
+
+

Challenges

+
    +
  • Aufgaben mit Ziel und Anforderungen
  • +
  • Done-Checks pro Aufgabe
  • +
  • Fokus: Reverse Proxy + TLS
  • +
+
+
+

Hints

+
    +
  • Kompakte Hilfe fuer Blocker
  • +
  • Easy-RSA / CA Import
  • +
  • Wireshark Filter
  • +
+
+
+

Solutions

+
    +
  • Konkrete Musterloesungen
  • +
  • Snippets fuer Nginx/Compose
  • +
  • Korrekturhilfe
  • +
+
+
+
+
+ + diff --git a/proxy/html/solutions.html b/proxy/html/solutions.html new file mode 100644 index 0000000..0f0f174 --- /dev/null +++ b/proxy/html/solutions.html @@ -0,0 +1,341 @@ + + + + + + HTL Workshop Solutions + + + +
+
+

Solutions Board (detailliert)

+

Hier stehen absichtlich konkrete Musterloesungen mit Snippets, Checks und typischen Stolperfallen.

+ +
+ +
+

Easy - Musterloesungen

+ +
+ Easy 1) Routing verstehen +
curl http://localhost:8080/service/a
+curl http://localhost:8080/service/b
+

Erwartete Erklaerung: Nginx matched den Pfad in location und leitet auf den passenden upstream weiter.

+
+ +
+ Easy 2) backend-c hinzufuegen +

Compose (Beispiel):

+
backend-c:
+  image: nginx:1.27-alpine
+  container_name: workshop-backend-c
+  volumes:
+    - ./backends/c:/usr/share/nginx/html:ro,z
+

Nginx (Beispiel):

+
upstream backend_c {
+  server backend-c:80;
+}
+
+location /service/c {
+  proxy_pass http://backend_c/;
+  proxy_set_header Host $host;
+  proxy_set_header X-Forwarded-Proto http;
+  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+}
+

Check: curl http://localhost:8080/service/c

+
+ +
+ Easy 3) Rewrite Route +
location = /demo/a {
+  rewrite ^ /service/a break;
+  proxy_pass http://backend_a/;
+}
+

Check: curl http://localhost:8080/demo/a

+
+
+ +
+

Medium - Musterloesungen

+ +
+ Medium 4) Security Headers +
add_header X-Content-Type-Options "nosniff" always;
+add_header X-Frame-Options "DENY" always;
+add_header Referrer-Policy "no-referrer" always;
+

Check: curl -I http://localhost:8080/

+
+ +
+ Medium 5) Interne Route absichern +
location /internal/status {
+  allow 127.0.0.1;
+  deny all;
+  default_type text/plain;
+  return 200 "internal ok\n";
+}
+

Check: curl -i http://localhost:8080/internal/status

+
+ +
+ Medium 6) Logging verbessern +
log_format workshop '$remote_addr - $request '
+                   'status=$status upstream=$upstream_addr '
+                   'rt=$request_time urt=$upstream_response_time';
+
+access_log /var/log/nginx/access.log workshop;
+

Check:

+
curl http://localhost:8080/service/a
+./scripts/compose.sh logs reverse-proxy
+
+
+ +
+

Hard (TLS) - Musterloesungen

+ +
+ Hard 7) HTTPS mit Easy-RSA +
mkdir -p certs/easyrsa
+cp -r /usr/share/easy-rsa/* certs/easyrsa/
+cd certs/easyrsa
+./easyrsa init-pki
+./easyrsa build-ca nopass
+./easyrsa gen-req localhost nopass
+./easyrsa sign-req server localhost
+

Compose:

+
reverse-proxy:
+  ports:
+    - "8080:80"
+    - "8443:443"
+  volumes:
+    - ./certs/easyrsa/pki:/etc/nginx/pki:ro,z
+

Check: curl https://localhost:8443/service/a (ohne -k nach CA-Import)

+
+ +
+ Hard 8) HTTP -> HTTPS Redirect +
server {
+  listen 80;
+  server_name _;
+
+  location = /healthz {
+    default_type text/plain;
+    return 200 "ok\n";
+  }
+
+  location / {
+    return 301 https://$host:8443$request_uri;
+  }
+}
+

Check: curl -I http://localhost:8080/service/a

+
+ +
+ Hard 9) TLS Haertung + HSTS +
ssl_protocols TLSv1.2 TLSv1.3;
+ssl_prefer_server_ciphers on;
+add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+

Check:

+
curl -I https://localhost:8443/service/a
+openssl s_client -connect localhost:8443 -servername localhost
+

Vollstaendige Referenz: proxy/nginx.tls.example.conf

+
+
+ +
+

Bonus Expert - Wireshark (ausformulierte Referenzloesung)

+ +
+ Expert 10) HTTP vs HTTPS sauber analysieren +

Schritt 1 - HTTP Capture:

+
    +
  1. Interface waehlen: lokal meist lo.
  2. +
  3. Capture starten, Filter tcp.port == 8080.
  4. +
  5. Request senden: curl http://localhost:8080/service/a.
  6. +
  7. In "Follow HTTP Stream" zeigen, dass Pfad und Header lesbar sind.
  8. +
+ +

Schritt 2 - HTTPS Capture:

+
    +
  1. HTTPS muss vorher laufen (Challenge 7).
  2. +
  3. Filter auf tcp.port == 8443 oder tls setzen.
  4. +
  5. Request senden: curl https://localhost:8443/service/a.
  6. +
  7. Pakete markieren: ClientHello, ServerHello, Certificate.
  8. +
+ +

Schritt 3 - Erwartete Aussagen:

+
    +
  • HTTP: URL, Header, Payload sind sichtbar.
  • +
  • HTTPS: Handshake sichtbar, Nutzdaten ohne Key nicht lesbar.
  • +
  • Zertifikat im Handshake zeigt, wie der Server seine Identitaet beweist.
  • +
+ +

Optional - TLS Decrypt (nur fuer Demo):

+
export SSLKEYLOGFILE="$HOME/sslkeys.log"
+
    +
  • Browser aus derselben Shell starten.
  • +
  • In Wireshark TLS Preferences -> Key Log File setzen.
  • +
  • Capture neu laden und Unterschiede vor/nach Keylog zeigen.
  • +
+ +

Abgabe (Muster):

+
    +
  • Screenshot A: HTTP Stream mit lesbaren Headern.
  • +
  • Screenshot B: TLS Handshake mit markierten Nachrichten.
  • +
  • Screenshot C (optional): entschluesselter Stream mit Keylog.
  • +
  • 3-5 Bulletpoints mit eigener technischer Interpretation.
  • +
+ +

Typische Fehler: falsches Interface, kein HTTPS aktiv, Browser nicht aus Keylog-Shell gestartet.

+
+
+
+ + diff --git a/proxy/nginx.conf b/proxy/nginx.conf new file mode 100644 index 0000000..e64d448 --- /dev/null +++ b/proxy/nginx.conf @@ -0,0 +1,47 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + server_tokens off; + + upstream backend_a { + server backend-a:80; + } + + upstream backend_b { + server backend-b:80; + } + + server { + listen 80; + server_name _; + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + + location /service/a { + proxy_pass http://backend_a/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto http; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /service/b { + proxy_pass http://backend_b/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto http; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /healthz { + default_type text/plain; + return 200 "ok\n"; + } + } +} diff --git a/proxy/nginx.tls.example.conf b/proxy/nginx.tls.example.conf new file mode 100644 index 0000000..400775d --- /dev/null +++ b/proxy/nginx.tls.example.conf @@ -0,0 +1,66 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + sendfile on; + server_tokens off; + + upstream backend_a { + server backend-a:80; + } + + upstream backend_b { + server backend-b:80; + } + + server { + listen 80; + server_name _; + + location = /healthz { + default_type text/plain; + return 200 "ok\n"; + } + + location / { + return 301 https://$host:8443$request_uri; + } + } + + server { + listen 443 ssl; + server_name localhost; + + ssl_certificate /etc/nginx/pki/issued/localhost.crt; + ssl_certificate_key /etc/nginx/pki/private/localhost.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header Referrer-Policy "no-referrer" always; + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + } + + location /service/a { + proxy_pass http://backend_a/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /service/b { + proxy_pass http://backend_b/; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + } +} diff --git a/scripts/bootstrap-wsl.sh b/scripts/bootstrap-wsl.sh new file mode 100755 index 0000000..0e10af0 --- /dev/null +++ b/scripts/bootstrap-wsl.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_ROOT" + +if ! command -v docker >/dev/null 2>&1; then + echo "[error] docker nicht gefunden. Bitte Docker installieren (Linux) oder Docker Desktop + WSL Integration aktivieren." + exit 1 +fi + +if ! docker info >/dev/null 2>&1; then + echo "[error] Docker Daemon nicht erreichbar. Bitte Docker starten." + exit 1 +fi + +if ! "$SCRIPT_DIR/compose.sh" version >/dev/null 2>&1; then + echo "[error] Weder docker compose noch docker-compose verfuegbar." + exit 1 +fi + +if [[ ! -f .env ]]; then + cp .env.example .env + echo "[ok] .env aus .env.example erstellt" +else + echo "[skip] .env existiert bereits" +fi + +"$SCRIPT_DIR/compose.sh" up -d --build + +echo +echo "[ok] Workshop-Stack laeuft" +echo " Landing Page: http://localhost:8080" +echo " Test (backend-a): curl http://localhost:8080/service/a" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..767793f --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +"$SCRIPT_DIR/bootstrap-wsl.sh" diff --git a/scripts/compose.sh b/scripts/compose.sh new file mode 100755 index 0000000..a0d8482 --- /dev/null +++ b/scripts/compose.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +if docker compose version >/dev/null 2>&1; then + docker compose "$@" +elif command -v docker-compose >/dev/null 2>&1; then + docker-compose "$@" +else + echo "[error] Weder 'docker compose' noch 'docker-compose' verfuegbar." + exit 1 +fi diff --git a/scripts/reset-lab.sh b/scripts/reset-lab.sh new file mode 100755 index 0000000..4a0799e --- /dev/null +++ b/scripts/reset-lab.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd -- "$SCRIPT_DIR/.." && pwd)" + +cd "$PROJECT_ROOT" + +HARD_RESET="${1:-}" + +echo "[info] Stoppe und entferne Container, Netzwerke, Volumes" +"$SCRIPT_DIR/compose.sh" down -v --remove-orphans + +echo "[info] Entferne ungenutzte lokale Images" +docker image prune -f >/dev/null 2>&1 || true + +if [[ "$HARD_RESET" == "--hard" ]]; then + if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + if git rev-parse --verify HEAD >/dev/null 2>&1; then + echo "[info] Git Hard Reset: stelle Dateien auf HEAD zurueck" + git restore --source=HEAD --staged --worktree . + else + echo "[warn] Git-Repo ohne Commit-Historie, Datei-Restore wird uebersprungen" + fi + git clean -fd + else + echo "[warn] Kein Git-Repo erkannt, --hard wird uebersprungen" + fi +fi + +echo "[ok] Lab wurde zurueckgesetzt" +echo "[hint] Neu starten mit: ./scripts/bootstrap.sh" diff --git a/scripts/workshop.ps1 b/scripts/workshop.ps1 new file mode 100644 index 0000000..8ebcd00 --- /dev/null +++ b/scripts/workshop.ps1 @@ -0,0 +1,43 @@ +param( + [ValidateSet("bootstrap", "up", "redeploy", "proxy-reload", "down", "logs", "reset")] + [string]$Action = "bootstrap", + [string]$Distro = "" +) + +if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) { + Write-Error "WSL wurde nicht gefunden. Bitte WSL installieren und konfigurieren." + exit 1 +} + +$repoWinPath = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$repoWslPath = (& wsl.exe wslpath -a "$repoWinPath").Trim() + +if (-not $repoWslPath) { + Write-Error "Konnte den WSL Pfad fuer das Repo nicht ermitteln." + exit 1 +} + +switch ($Action) { + "bootstrap" { $linuxCommand = "./scripts/bootstrap.sh" } + "up" { $linuxCommand = "./scripts/compose.sh up -d --build" } + "redeploy" { $linuxCommand = "./scripts/compose.sh up -d --build --remove-orphans && ./scripts/compose.sh restart reverse-proxy" } + "proxy-reload" { $linuxCommand = "./scripts/compose.sh restart reverse-proxy" } + "down" { $linuxCommand = "./scripts/compose.sh down" } + "logs" { $linuxCommand = "./scripts/compose.sh logs -f" } + "reset" { $linuxCommand = "./scripts/reset-lab.sh" } +} + +$wslArgs = @() +if ($Distro) { + $wslArgs += "-d" + $wslArgs += $Distro +} + +$wslArgs += "-e" +$wslArgs += "bash" +$wslArgs += "-lc" +$wslArgs += "cd '$repoWslPath' && $linuxCommand" + +Write-Host "[info] Running in WSL: $linuxCommand" +& wsl.exe @wslArgs +exit $LASTEXITCODE