Sicherheit & Deployment
Das IuK-Backend verarbeitet hochsensible Einsatzdaten — Personendaten Betroffener, Patientendaten und Einsatzlagebilder. Der Betrieb hinter einem gehärteten nginx-Reverse-Proxy mit HTTPS und vorgelagerter Basic-Authentifizierung ist daher dringend empfohlen und ergänzt die applikationsinterne Anmeldung um eine zusätzliche Sicherheitsebene.
Sicherheitsarchitektur
Zwei unabhängige Schutzschichten zwischen Internet und Datenbank
Das Sicherheitskonzept basiert auf zwei unabhängigen Schutzschichten: Kompromittiert ein Angreifer eine Ebene, steht er vor der nächsten.
Abwehr auf Netzwerkebene: Verschlüsselung, Authentifizierung, Schutz-Header und WAF-Grundregeln (Rate Limiting, Methoden-Filter, Pattern Blocking) — bevor eine HTTP-Anfrage die Applikation überhaupt erreicht.
Feingranulare Zugriffssteuerung: Benutzersessions, Rollen, Mandanten-Trennung und Schutz vor Brute-Force-Angriffen auf Endpunkt-Ebene.
Schutzmaßnahmen im Überblick
Alle aktiven Maßnahmen und ihre Wirkung
nginx-Ebene
| Maßnahme | Wirkung |
|---|---|
| HTTPS / TLS 1.2–1.3 | Alle Verbindungen verschlüsselt — Mitlesen und Manipulation im Netz unterbunden |
| HSTS (2 Jahre) | Browser erzwingen HTTPS dauerhaft — kein Downgrade auf HTTP möglich |
| Basic Auth (Shared Secret) | Angreifer ohne Einsatzkräfte-Passwort sehen weder Login noch andere Seiten |
| X-Frame-Options: DENY | Verhindert Einbindung in fremde Frames (Clickjacking-Schutz) |
| X-Content-Type-Options | Verhindert MIME-Type-Sniffing durch den Browser |
| Referrer-Policy | Schränkt Weitergabe von URL-Informationen an externe Seiten ein |
| server_tokens off | Versteckt nginx-Version vor Angreifern (Erschwerung von Versions-Exploits) |
| DH-Parameter (2048 Bit) | Schützt den Key-Exchange vor dem Logjam-Angriff |
| OCSP-Stapling | Schnelle Zertifikat-Validierung ohne externe Anfrage beim Verbindungsaufbau |
| ssl_session_tickets off | Sichert Forward Secrecy — aufgezeichnete Verbindungen bleiben nach Key-Kompromiss lesbar |
| Permissions-Policy | Deaktiviert Geolocation, Mikrofon und Kamera für alle eingebetteten Inhalte |
| Rate Limiting (3 Zonen) | Login 5 Req/min · authentifizierte Endpunkte 60 Req/min · öffentliche Endpunkte 120 Req/min — Überschreitung → HTTP 429 |
| HTTP-Methoden-Filter | Nur GET/POST/PUT/DELETE/OPTIONS erlaubt — alle anderen Methoden (TRACE, CONNECT …) → HTTP 405 |
| Suspicious-Pattern-Blocking | Blockiert Path Traversal (..), XSS-Ansätze in URLs (<>"'), Semikolon sowie bekannte Scanner-User-Agents (sqlmap, nikto …) → HTTP 403 |
| WAF-Logging | Alle blockierten Anfragen (400/403/405/429) werden zusätzlich nach /var/log/nginx/waf_blocked.log geschrieben |
Applikations-Ebene (Go)
| Maßnahme | Wirkung |
|---|---|
| Session-Auth + Rollen | Nur authentifizierte Nutzer mit passender Rolle dürfen auf Ressourcen zugreifen |
| Tenant-Isolation | Mandanten können nicht auf Daten anderer Mandanten zugreifen |
| Rate-Limiting | Brute-Force-Schutz bei Login, Passwort-Reset und Selbstregistrierung |
| CSRF-Schutz | Formular-Tokens verhindern Cross-Site-Request-Forgery |
WAF-Grundregeln
nginx-Bordmittel ohne ModSecurity — Basisschutz gegen automatisierte Angriffe
Rate Limiting
| Zone | Endpunkte | Limit | Burst |
|---|---|---|---|
| login | /login, /password_reset |
5 Req/min | 3 |
| global | Alle authentifizierten Endpunkte (/, /ws) |
60 Req/min | 20 |
| public | /static/, /shelter/…/self_registration |
120 Req/min | 10–50 |
Überschreitungen liefern HTTP 429 Too Many Requests. /wsapi ist bewusst ausgenommen — der TETRA-Client ist vertrauenswürdig und hält eine dauerhafte Verbindung.
HTTP-Methoden-Filter
Nur GET POST PUT DELETE OPTIONS werden weitergeleitet.
Alle anderen Methoden (z. B. TRACE, CONNECT) erhalten HTTP 405.
Suspicious-Pattern-Blocking
..— Path Traversal< > " '— XSS-Ansätze in URL; %3B— Semikolon (Injection-Versuche)
- Leerer User-Agent
sqlmap,niktomasscan,zgrabpython-requests
WAF-Logging
Alle blockierten Anfragen (HTTP 400, 403, 405, 429) werden zusätzlich in ein separates Log geschrieben:
# /var/log/nginx/waf_blocked.log 203.0.113.42 - [09/Jun/2026:14:22:10 +0200] "GET /../../etc/passwd HTTP/2.0" 403 "sqlmap/1.7" 203.0.113.99 - [09/Jun/2026:14:22:11 +0200] "POST /login HTTP/2.0" 429 "Mozilla/5.0 ..."
# Live-Monitoring sudo tail -f /var/log/nginx/waf_blocked.log
nginx einrichten
Schritt-für-Schritt-Anleitung für Debian/Ubuntu
Entweder über Let's Encrypt (öffentlich erreichbarer Server) oder ein selbstsigniertes Zertifikat der eigenen PKI (internes Netz).
# Let's Encrypt (Certbot) sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d iuk-backend.example.com
Einmaliger Schritt, dauert ca. 1–2 Minuten. Schützt den Schlüsselaustausch vor dem Logjam-Angriff.
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
Die fertige Konfiguration liegt unter server/deployment/nginx.conf
im Projektverzeichnis. Vor dem Kopieren den
server_name
und die Zertifikatspfade anpassen.
# Konfiguration kopieren sudo cp server/deployment/nginx.conf \ /etc/nginx/sites-available/iuk-backend.conf # server_name und ssl_certificate-Pfade anpassen sudo nano /etc/nginx/sites-available/iuk-backend.conf # Aktivieren sudo ln -s /etc/nginx/sites-available/iuk-backend.conf \ /etc/nginx/sites-enabled/
Dieses Shared Secret ist allen Einsatzkräften bekannt und stellt die erste Sicherheitsebene dar. Es ist kein personenbezogenes Konto — alle Einsatzkräfte teilen denselben Zugang.
# .htpasswd erstellen (PASSWORT_HIER durch echtes Passwort ersetzen) printf "einsatz:$(openssl passwd -apr1 'PASSWORT_HIER')\n" \ | sudo tee /etc/nginx/.htpasswd # Zugriffsrechte setzen sudo chmod 640 /etc/nginx/.htpasswd sudo chown root:www-data /etc/nginx/.htpasswd
einsatz kann beliebig angepasst werden.
Das Passwort sollte lang und komplex sein, aber von allen Einsatzkräften gemerkt werden können —
z. B. eine Passphrase aus drei Wörtern.
sudo nginx -t nginx: configuration file /etc/nginx/nginx.conf test is successful sudo systemctl reload nginx
Die Service-Unit liegt unter
server/deployment/iuk-backend.service.
Der Dienst läuft als eigener unprivilegierter Benutzer iuk-backend.
sudo cp server/deployment/iuk-backend.service \ /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now iuk-backend # Status prüfen sudo systemctl status iuk-backend
Öffentliche Endpunkte
Ausnahmen vom Basic-Auth-Schutz — und warum
Drei Pfadbereiche sind bewusst vom Basic-Auth-Schutz ausgenommen. Sie werden von Personen aufgerufen, die das Einsatzkräfte-Passwort weder kennen noch kennen müssen.
| Pfad | Erreichbar für | Grund |
|---|---|---|
| /static/ | Alle | CSS/JS für die Selbstregistrierungsseite — Betroffene laden diese ohne Passwort |
| /wsapi | TETRA-Gateway | TETRA-Client authentifiziert sich selbst per X-Api-Key-Header |
| /shelter/{id}/{id} /self_registration |
Betroffene Personen | QR-Code-Kiosk am Eingang der Betreuungsstelle — Betroffene melden sich selbst an |
/shelter/…/self_registration/print-Endpunkt
(QR-Code-Ausdruck für Personal) bleibt hinter Basic Auth und erfordert zusätzlich
die Berechtigung shelter:guest.
Schutzmaßnahmen überprüfen
Nach dem Deployment jede Maßnahme einmalig verifizieren.
HOSTNAME durch den echten Hostnamen ersetzen.
curl -sI https://HOSTNAME/login \ | grep -E "Strict-Transport|X-Frame|X-Content-Type|Referrer" strict-transport-security: max-age=63072000; includeSubDomains x-frame-options: DENY x-content-type-options: nosniff referrer-policy: strict-origin-when-cross-origin
curl -o /dev/null -w "%{http_code}\n" https://HOSTNAME/login 401
curl -u einsatz:PASSWORT -o /dev/null -w "%{http_code}\n" \ https://HOSTNAME/login 200
# UUID durch einen echten Wert aus der Datenbank ersetzen curl -o /dev/null -w "%{http_code}\n" \ https://HOSTNAME/shelter/<missionId>/<shelterId>/self_registration 200
curl -o /dev/null -w "%{http_code}\n" \ https://HOSTNAME/static/css/app.css 200
curl -u einsatz:PASSWORT -o /dev/null -w "%{http_code}\n" \ https://HOSTNAME/health 403
curl -o /dev/null -w "%{http_code}\n" \ https://HOSTNAME/shelter/<missionId>/<shelterId>/self_registration/print 401
curl -u einsatz:PASSWORT -X TRACE \ -o /dev/null -w "%{http_code}\n" https://HOSTNAME/login 405
curl -u einsatz:PASSWORT \ -o /dev/null -w "%{http_code}\n" "https://HOSTNAME/foo/../../etc/passwd" 403
curl -u einsatz:PASSWORT -A "sqlmap/1.7.8" \ -o /dev/null -w "%{http_code}\n" https://HOSTNAME/login 403
sudo tail -20 /var/log/nginx/waf_blocked.log
Extern über SSL Labs (Ziel: Note A oder A+). Lokal ohne externe Abhängigkeit:
# testssl.sh (https://testssl.sh) lokal installieren ./testssl.sh https://HOSTNAME/
sudo certbot renew --dry-run