Solutions Board (detailliert)

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

Workflow: Nach jeder Konfig-Aenderung mindestens make proxy-reload, bei Compose-Aenderungen make 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) Rewrite Route

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;

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

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 6a) 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 "Target A2\|Target A"
done
Medium 6b) Response Header Minimization

Dateien: proxy/nginx.conf

Commands noetig: ja

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

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 6c) Debugging Challenge

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

Commands noetig: ja

Kopiere testweise proxy/nginx.broken.conf auf proxy/nginx.conf, behebe die Fehler und stelle danach die funktionierende Konfiguration wieder her.

cp proxy/nginx.broken.conf proxy/nginx.conf
make proxy-reload
./scripts/compose.sh logs reverse-proxy

Check: beide Routen funktionieren wieder.

Hard (TLS) - Musterloesungen

Hard 7) 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 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

Voraussetzung: Challenge 7 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 9) TLS Haertung + HSTS

Voraussetzung: Challenge 7 und 8 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=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

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 7).
  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.