Solutions Board (detailliert)

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

Workflow: Nach jeder Konfig-Aenderung mindestens ./scripts/lab.sh proxy-reload, bei Compose-Aenderungen ./scripts/lab.sh redeploy.

Easy - Musterloesungen

Easy 1) Routing verstehen

Dateien: nur lesen: proxy/nginx.conf

Commands noetig: ja

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

Dateien: docker-compose.yml, proxy/nginx.conf, backends/c/index.html

Commands noetig: ja

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

Hinweis: Stelle sicher, dass backends/c/index.html existiert (Starterdatei liegt bereits im Repo).

Easy 3) Alias-Route /demo/a

Dateien: proxy/nginx.conf

Commands noetig: ja

location = /demo/a {
  proxy_pass http://backend_a/;
}

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

Medium - Musterloesungen

Medium 4) Security Headers

Dateien: proxy/nginx.conf

Commands noetig: ja

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

Platzierung: in den server {}-Block, damit alle Locations sie erben. Sobald eine Location ein eigenes add_header hat, verwirft Nginx dort die Server-Header und sie muessen wiederholt werden.

Optional CSP (statisch, inline styles erlaubt):

add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'" always;

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

Medium 5) Interne Route absichern

Dateien: proxy/nginx.conf

Commands noetig: ja

location /internal/status {
  allow 127.0.0.1;
  deny all;
  default_type text/plain;
  return 200 "internal ok\n";
}

Check (Host): curl -i http://localhost:8080/internal/status (typisch 403)

Check (Container-intern):

./scripts/compose.sh exec -T reverse-proxy sh -lc "wget -qO- http://127.0.0.1/internal/status"
Medium 6) Logging verbessern

Dateien: proxy/nginx.conf

Commands noetig: ja

Platzierung: log_format muss in den http {}-Block (sonst startet Nginx nicht). access_log darf in http, server oder location.

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
Medium 7) Load Balancing

Dateien: docker-compose.yml, proxy/nginx.conf, backends/a2/index.html

Commands noetig: ja

Compose (backend-a2):

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

Nginx (upstream erweitern):

upstream backend_a {
  server backend-a:80;
  server backend-a2:80;
}

Check:

for i in $(seq 1 8); do
  curl -s http://localhost:8080/service/a | grep -o "INSTANCE=[A-Za-z0-9]*"
done

Marker: Backend A/A2 enthalten unsichtbar <!-- INSTANCE=A --> bzw. INSTANCE=A2. [A-Za-z0-9]* matched das ganze Token (kein Prefix-Problem A vs. A2).

Medium 8) Response Header Minimization

Dateien: proxy/nginx.conf

Commands noetig: ja

Abgrenzung zu #4: #4 fuegt Schutz-Header hinzu, #8 entfernt unnoetige Upstream-Metadaten.

Gut zu wissen: proxy_hide_header ist kein add_header → es loest die Vererbungsfalle aus #4 nicht aus. Die Server-Header bleiben in dieser Location erhalten.

location /service/a {
  proxy_pass http://backend_a/;
  proxy_hide_header ETag;
  proxy_hide_header Last-Modified;
}

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

Medium 9) Debugging Challenge

Dateien: proxy/nginx.broken.conf, proxy/nginx.conf

Commands noetig: ja

Ablauf: Zuerst die eigene Config sichern (diese Challenge ueberschreibt sie!), dann die kaputte aktivieren, Fehler beheben und am Ende die eigene Config wiederherstellen.

cp proxy/nginx.conf proxy/nginx.conf.bak      # 1. eigene Config sichern
cp proxy/nginx.broken.conf proxy/nginx.conf   # 2. kaputte aktivieren
./scripts/lab.sh proxy-reload
./scripts/compose.sh logs reverse-proxy        # 3. Fehler finden + fixen

Am Ende eigene Config zurueck:

cp proxy/nginx.conf.bak proxy/nginx.conf
./scripts/lab.sh proxy-reload

Konkrete Fehler und Fixes:

  1. Upstream-Mismatch: backend_a_typo ist definiert, aber backend_a wird referenziert -> Namen angleichen.
  2. Falscher Port: backend-a:8080 -> auf backend-a:80 korrigieren.
  3. Pfadfehler: in /service/b fehlt der Trailing Slash bei proxy_pass -> proxy_pass http://backend_b/;.

Check: beide Routen funktionieren wieder.

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

Hard (TLS) - Musterloesungen

Hard 10) HTTPS mit Easy-RSA

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

Commands noetig: ja

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 --subject-alt-name="DNS:localhost,IP:127.0.0.1" sign-req server localhost

Compose:

reverse-proxy:
  ports:
    - "${HTTP_PORT:-8080}:80"
    - "${HTTPS_PORT:-8443}:443"
  volumes:
    - ./certs/live:/etc/nginx/certs:ro,z

Wichtig: Nicht die komplette PKI in den Container mounten. Nur Runtime-Zertifikat + Key bereitstellen.

Deploy: Port- und Volume-Aenderungen sind Compose-Aenderungen → ./scripts/lab.sh redeploy (nicht ./scripts/lab.sh proxy-reload), sonst greift der neue Port nicht.

Nginx TLS-Pfade: ssl_certificate /etc/nginx/certs/localhost.crt; und ssl_certificate_key /etc/nginx/certs/localhost.key;

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

Hard 11) HTTP -> HTTPS Redirect

Voraussetzung: Challenge 10 abgeschlossen (gleiches Config-File weiterverwenden).

Dateien: proxy/nginx.conf

Commands noetig: ja

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 12) TLS Haertung + HSTS

Voraussetzung: Challenge 10 und 11 abgeschlossen.

Dateien: proxy/nginx.conf

Commands noetig: ja

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=3600; 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 13) HTTP vs HTTPS sauber analysieren

Dateien: keine Pflicht-Datei; optional Wireshark Settings und Keylog-Datei

Commands noetig: ja

Schritt 1 - HTTP Capture:

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

Schritt 2 - HTTPS Capture:

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

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.