= Security-Header in der NGINX-Konfiguration = [[PageOutline]] == Das Problem == `add_header` in NGINX vererbt nicht so, wie man es erwartet. Die Direktiven werden von der uebergeordneten Ebene '''nur dann''' geerbt, wenn auf der aktuellen Ebene '''kein einziges''' `add_header` steht. Ein beliebiges `add_header` in einem `location`-Block loescht damit saemtliche Header aus dem `server`-Block -- stillschweigend, ohne Warnung beim Konfigurationstest. Genau das ist der Grund, warum der `Strict-Transport-Security`-Header bei Nextcloud fehlte: die empfohlene Nextcloud-Konfiguration setzt in den PHP- und Asset-Locations eigene Header. Ab NGINX 1.29.3 laesst sich das mit `add_header_inherit merge;` abstellen. Unsere Version ist aelter, deshalb die Loesung ueber wiederholte Includes. '''Vorsicht bei einer spaeteren Umstellung auf `merge`:''' die Direktive haengt die Werte der uebergeordneten Ebene an, sie ersetzt sie nicht. Bei gleichnamigen Headern entstehen Duplikate. Der Fehlermodus wechselt damit von "Header fehlt" zu "Header doppelt" -- bei `X-Frame-Options` verwerfen mehrere Browser den Header dann komplett, bei CSP wird die Schnittmenge beider Policies durchgesetzt. == Aufbau == Drei Ebenen, per Komposition statt per Kopie: {{{ base-headers.inc ueberall unschaedlich | +-- public-headers.inc alles unter vpn21.net, HSTS-Schalter | | | +-- apex-headers.inc vpn21.net / www.vpn21.net | +-- nextcloud-headers.inc | +-- usvn-headers.inc | +-- shop-headers.inc geplant | +-- intern-headers.inc *.p21.net, kein HSTS }}} Alle Dateien liegen in `conf.d/` und folgen dem Namensschema `*.inc`. Die Endung ist wichtig: `conf.d/*.conf` wird von NGINX per Glob automatisch eingebunden, `*.inc` nicht. == Einbindung == In den `server`-Block '''und''' in jeden `location`-Block, der eigene `add_header`-Direktiven hat -- am besten als erste Zeile: {{{#!nginx server { include conf.d/nextcloud-headers.inc; location ~ \.php$ { include conf.d/nextcloud-headers.inc; add_header Cache-Control "no-store"; ... } } }}} == Pruefung == Vergessene Bloecke finden: {{{#!sh nginx -T | awk '/^\s*location/{loc=$0; has=0} /add_header/{if(loc && !has){print loc; has=1}}' }}} Jeden Location-Typ einmal abklopfen -- PHP, CSS/JS, Fonts, statische Dateien, `/.well-known/*`: {{{#!sh curl -sI https://cloud.vpn21.net/ curl -sI https://cloud.vpn21.net/nextcloud/index.php curl -sI https://cloud.vpn21.net/nextcloud/core/css/server.css }}} Beim Pruefen im Browser auf den Cache achten: angezeigt werden die Header der urspruenglichen Antwort. Mit Shift-Reload gegenpruefen. == Die einzelnen Header == ||= Header =||= Ebene =||= Begruendung =|| || `X-Content-Type-Options: nosniff` || base || verhindert MIME-Sniffing, z.B. hochgeladenes Bild als Skript || || `X-Permitted-Cross-Domain-Policies: none` || base || Adobe-Clients, weitgehend historisch || || `X-Frame-Options: SAMEORIGIN` || base || Clickjacking-Schutz, abgeloest durch CSP frame-ancestors || || `Referrer-Policy` || pro Site || Wert unterschiedlich, siehe unten || || `Strict-Transport-Security` || public / apex || noch nicht aktiviert || || `X-Robots-Tag: noindex, nofollow` || Nextcloud, USVN || '''niemals''' global || || `Content-Security-Policy` || pro Site || nie global, siehe unten || || `X-XSS-Protection` || -- || deprecated, nicht setzen || === X-Frame-Options === "same origin" bedeutet Schema + Host + Port. Daraus folgt: * dienstuebergreifendes Einbetten ist blockiert, z.B. VIS auf `iobroker.p21.net`, das `nodered.p21.net` einbettet * eine HTTP-Seite, die die HTTPS-Seite desselben Hosts einbettet, ist bereits cross-origin -- relevant bei einer schrittweisen TLS-Umstellung der internen Dienste Derzeit betten die internen Dashboards nur vom gleichen Host ein, deshalb steht `SAMEORIGIN` in `base-headers.inc`. Sollte sich das aendern, wandert der Header in die oeffentlichen Dateien und wird intern durch eine CSP mit `frame-ancestors` ersetzt. === Referrer-Policy === Bewusst nicht in `base-headers.inc`, weil der sinnvolle Wert je Site abweicht: * `no-referrer` fuer Nextcloud (setzt es selbst), USVN und den Apex * `strict-origin-when-cross-origin` fuer den Shop -- `no-referrer` bricht unter Umstaenden Zahlungsdienstleister, Affiliate-Rueckverfolgung und Analytics === X-Robots-Tag === Der Hauptgrund gegen eine einzige globale Header-Datei. `none` ist aequivalent zu `noindex, nofollow`; auf dem Shop waere das ein Totalschaden -- ohne Fehlermeldung, mit Wochen Verzoegerung sichtbar. Der Selbstcheck von Nextcloud vergleicht ausserdem gegen den Literal-String `noindex, nofollow`. Semantisch gleichwertige Schreibweisen wie `none` erfuellen ihn nicht. === X-XSS-Protection === Nicht setzen. Der Header steuerte den XSS-Filter von Internet Explorer, Chrome, Edge und Safari; Firefox hat ihn nie implementiert, Edge hat den Filter 2018 entfernt, Chrome 2019. Der Filter konnte in eigentlich sicheren Seiten selbst XSS-Luecken erzeugen. OWASP empfiehlt den Wert `0` oder das Weglassen. Gegen XSS hilft CSP. Der Header steht noch in der Nextcloud-Beispielkonfiguration -- Altlast. === Content-Security-Policy === CSP gibt dem Browser eine Positivliste vor, aus welchen Quellen er Skripte, Stylesheets, Bilder, Schriften, Verbindungen und Frames laden darf. Alles nicht Aufgefuehrte wird verweigert. Das ist die eigentliche Verteidigung gegen Cross-Site-Scripting: eingeschleustes Inline-JavaScript wird nicht ausgefuehrt, weil Inline-Skripte nicht erlaubt sind. Fuer legitime Inline-Skripte gibt es '''Nonces''': der Server erzeugt pro Request eine Zufallszahl, schreibt sie in den Header und an jedes eigene `