Changes between Version 1 and Version 2 of WebDiensteNginxSecurityHeader


Ignore:
Timestamp:
Oct 24, 2026, 12:35:13 PM (2 weeks ago)
Author:
Johannes Lode
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • WebDiensteNginxSecurityHeader

    v1 v2  
    227227Der ICANN-Draft zu `.internal` empfiehlt selbst die Subdomain einer eigenen
    228228Domain als erste Wahl und die reservierte TLD erst danach.
     229
     230== Content-Security-Policy fuer den Shop ==
     231
     232Status: noch nicht aktiv. Die Konfiguration steht auskommentiert in
     233`conf.d/shop-headers.inc`.
     234
     235=== Wer sammelt die Reports? ===
     236
     237Der Browser erzeugt sie, legt sie aber nirgends ab. Ohne Reporting-Direktive
     238erscheinen Verstoesse ausschliesslich in der Entwicklerkonsole des Browsers --
     239NGINX bekommt davon nichts mit, es gibt kein Log. Mit Reporting-Direktive
     240schickt der Browser einen POST mit JSON-Body an eine URL, die wir angeben.
     241Den Empfaenger muessen wir selbst stellen.
     242
     243Zwei Mechanismen:
     244
     245 * `report-uri` -- der alte, CSP-eigene Weg, inzwischen deprecated
     246 * `report-to` plus den Header `Reporting-Endpoints` -- der moderne Weg ueber
     247   die allgemeine Reporting-API
     248
     249Browser mit `report-to`-Unterstuetzung ignorieren `report-uri` vollstaendig.
     250Lange war `report-uri` unverzichtbar, weil Firefox und Safari die
     251Reporting-API nicht umgesetzt hatten; seit Maerz 2026 unterstuetzen alle
     252aktuellen Browser `report-to` (Firefox seit Version 149). Wir verwenden
     253daher nur noch `report-to`.
     254
     255=== Dreistufiges Vorgehen ===
     256
     257 1. '''Entwicklerkonsole.''' Solange der Shop nicht oeffentlich ist, nichts
     258    aktivieren. Shop selbst durchklicken -- Katalog, Warenkorb, Checkout,
     259    Zahlungsmodule, Admin -- und die Verstoesse in der Konsole abarbeiten.
     260    Die zeigt sie sofort und mit Zeilennummer, ein Report-Endpunkt liefert
     261    gebuendelt und mit bis zu einer Minute Verzoegerung.
     262 2. '''Report-Only mit Endpunkt.''' Erst wenn der Shop oeffentlich ist und
     263    Felddaten aus echten Browsern gebraucht werden. Blockiert nichts.
     264 3. '''Scharf schalten.''' `Content-Security-Policy-Report-Only` durch
     265    `Content-Security-Policy` ersetzen, Endpunkt beibehalten.
     266
     267=== Endpunkt in NGINX ===
     268
     269In `nginx.conf`, http-Kontext:
     270
     271{{{#!nginx
     272log_format csp escape=json
     273    '{"time":"$time_iso8601","ua":"$http_user_agent",'
     274    '"report":$request_body}';
     275
     276limit_req_zone $binary_remote_addr zone=csp:1m rate=10r/m;
     277
     278# Dummy-Backend, das den POST quittiert
     279server {
     280    listen 127.0.0.1:8099;
     281    location / { return 204; }
     282}
     283}}}
     284
     285Im server-Block des Shops:
     286
     287{{{#!nginx
     288location = /_csp-report {
     289    access_log /var/log/nginx/csp-report.jsonl csp;
     290
     291    client_body_buffer_size 64k;
     292    client_max_body_size    64k;
     293    limit_req zone=csp burst=5 nodelay;
     294
     295    proxy_pass http://127.0.0.1:8099;
     296}
     297}}}
     298
     299Und im Shop-Header:
     300
     301{{{#!nginx
     302add_header Reporting-Endpoints 'csp="https://shop.vpn21.net/_csp-report"' always;
     303add_header Content-Security-Policy-Report-Only "default-src 'self'; report-to csp" always;
     304}}}
     305
     306'''Fallstricke:'''
     307
     308 * Der Umweg ueber `proxy_pass` ist noetig. NGINX liest den Request-Body nur
     309   ein, wenn es ihn weiterreicht; bei einem schlichten `return 204;` bleibt
     310   `$request_body` leer.
     311 * `$request_body` ist nur gefuellt, wenn der Body in den Speicher passt.
     312   Groesserer Body wandert in eine temporaere Datei, die Variable bleibt
     313   leer -- daher `client_body_buffer_size` passend setzen.
     314 * Die Endpunkt-URL muss HTTPS sein. Ueber HTTP verwirft der Browser sie
     315   kommentarlos.
     316 * `escape=json` im `log_format` gibt es ab NGINX 1.11.8.
     317
     318=== Logrotation ===
     319
     320'''NGINX kann nicht selbst rotieren.''' Es gibt keine Groessen- oder
     321Zeitbegrenzung in der Konfiguration; NGINX schreibt in ein offenes
     322File-Handle, bis jemand von aussen eingreift. Der vorgesehene Weg ist:
     323Datei umbenennen, dann `SIGUSR1` an den Master-Prozess, woraufhin NGINX die
     324Logs neu oeffnet. Genau das macht logrotate.
     325
     326Begrenzen laesst sich in der NGINX-Konfiguration nur der Zufluss --
     327`client_max_body_size` pro Eintrag, `limit_req` pro Client. Beides ist oben
     328gesetzt.
     329
     330Der Dateiname lautet bewusst `csp-report.jsonl` und nicht `.log`: die
     331mitgelieferte logrotate-Konfiguration der Distribution greift ueblicherweise
     332auf `/var/log/nginx/*.log` zu. Zwei Stanzas fuer dieselbe Datei quittiert
     333logrotate mit "duplicate log entry" und bricht ab. Die eigene Endung haelt
     334die CSP-Logs aus dem Standard-Glob heraus und erlaubt eine eigene Retention.
     335
     336`/etc/logrotate.d/nginx-csp`:
     337
     338{{{
     339/var/log/nginx/csp-report.jsonl {
     340    size 10M
     341    rotate 5
     342    compress
     343    delaycompress
     344    missingok
     345    notifempty
     346    create 0640 www-data adm
     347    sharedscripts
     348    postrotate
     349        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
     350    endscript
     351}
     352}}}
     353
     354Zur Wirkung von `size`: die Direktive schaltet die zeitbasierte Rotation ab
     355und rotiert nur noch bei Ueberschreiten der Groesse -- aber geprueft wird
     356das erst, wenn logrotate laeuft, ueblicherweise einmal taeglich per
     357systemd-Timer. Eine harte Obergrenze ist das also nicht; bei einem Ausbruch
     358kann die Datei zwischen zwei Laeufen deutlich groesser werden. Wer das enger
     359braucht, laesst logrotate oefter laufen (eigener Timer oder Cron-Eintrag mit
     360`logrotate /etc/logrotate.d/nginx-csp`).
     361
     362Alternative `maxsize` statt `size`: rotiert nach Zeitplan '''oder''' bei
     363Ueberschreiten der Groesse, je nachdem was zuerst eintritt. Sinnvoll, wenn
     364die Datei auch bei wenig Verkehr regelmaessig umgebrochen werden soll.
     365
     366Benutzer und Gruppe in `create` an die Distribution anpassen -- auf einigen
     367Systemen `nginx:nginx` statt `www-data:adm`.
     368
     369=== Rauschfilter -- noch zu entwickeln ===
     370
     371Sobald der Endpunkt oeffentlich erreichbar ist, besteht ein erheblicher Teil
     372der Reports aus Muell: Browser-Erweiterungen, die Skripte injizieren,
     373Virenscanner mit TLS-Interception, von Providern eingeschleuste Inhalte.
     374Die Trennung von relevanten und irrelevanten Reports ist eine eigene
     375Aufgabe.
     376
     377Prompt zum Kopieren, wenn es soweit ist:
     378
     379{{{
     380Ich betreibe einen modified-eCommerce-Shop unter shop.vpn21.net hinter
     381NGINX (Version siehe unten). Der Shop laeuft seit einiger Zeit mit
     382Content-Security-Policy-Report-Only und einem Report-Endpunkt
     383/_csp-report, der die Reports per report-to einsammelt.
     384
     385NGINX loggt die Reports mit log_format csp escape=json als eine JSON-Zeile
     386pro Report nach /var/log/nginx/csp-report.jsonl, Format:
     387{"time":"...","ua":"...","report":<Reporting-API-Payload>}
     388
     389Ich haenge einen Auszug der Logdatei an.
     390
     391Ich moechte mit dir zusammen einen Filter entwickeln, der die relevanten
     392Reports -- also echte Verstoesse meines eigenen Shop-Codes und seiner
     393Module -- von Rauschen trennt. Als Rauschen erwarte ich vor allem:
     394Browser-Erweiterungen, Virenscanner mit TLS-Interception, von Providern
     395eingeschleuste Inhalte, Bots.
     396
     397Bitte gehe so vor:
     3981. Analysiere den Auszug und zeige mir, welche Report-Klassen tatsaechlich
     399   vorkommen und wie haeufig. Rate nicht, zaehle.
     4002. Schlage Unterscheidungskriterien vor und begruende sie. Nenne mir bei
     401   jedem Kriterium, welche echten Verstoesse es faelschlich ausfiltern
     402   koennte.
     4033. Erst danach: ein Auswertungsskript. Sprache nach deinem Vorschlag,
     404   begruende die Wahl. Es soll die Logdatei einlesen, Rauschen abtrennen
     405   und die verbleibenden Verstoesse nach Haeufigkeit gruppiert ausgeben.
     4064. Sag mir, welche CSP-Direktiven ich aus den verbleibenden Verstoessen
     407   ableiten muesste, damit ich von Report-Only auf scharf umstellen kann.
     408
     409Frag nach, wenn etwas unklar ist, statt auf Annahmen aufzubauen.
     410Code-Policy fuer C++ gilt hier vermutlich nicht, aber falls doch Code in
     411C++ entsteht, halte dich daran.
     412}}}
     413
     414Mitzubringen, wenn wir das angehen: ein Auszug aus `csp-report.jsonl` von
     415einigen Tagen bis Wochen, moeglichst ungefiltert, und die dann aktive
     416Report-Only-Policy.