| Version 1 (modified by , 13 days ago) (diff) |
|---|
Security-Header in der NGINX-Konfiguration
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:
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:
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/*:
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, dasnodered.p21.neteinbettet - 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-referrerfuer Nextcloud (setzt es selbst), USVN und den Apexstrict-origin-when-cross-originfuer den Shop --no-referrerbricht 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
<script>. Eingeschleuste Skripte kennen sie nicht.
CSP gehoert niemals in eine globale Datei. Mehrere CSP-Header werden nicht ueberschrieben, sondern alle durchgesetzt -- der Browser bildet die Schnittmenge. Nextcloud erzeugt seine CSP selbst mit Nonces; eine zweite aus NGINX zerlegt die Oberflaeche.
Fuer den Shop spaeter ueber Content-Security-Policy-Report-Only
einfuehren: derselbe Header, der Browser blockiert nichts und meldet nur,
was er blockiert haette. Einige Wochen auswerten, aufraeumen, dann scharf
schalten.
Doppelte Header aus dem Backend
add_header ergaenzt Header des Backends, es ersetzt sie nicht.
- Nextcloud sendet seine Security-Header selbst aus PHP. In der
fastcgi-Konfiguration
fastcgi_param modHeadersAvailable true;setzen, dann unterlaesst Nextcloud das. - USVN laeuft auf Apache hinter NGINX. Dort
proxy_hide_headerverwenden. Die Direktive folgt derselben Vererbungsregel wieadd_headerund gehoert deshalb in dieselbe Include-Datei. Vorher pruefen, was das Backend tatsaechlich sendet:curl -sI http://127.0.0.1:<backend-port>/
HSTS -- offene Entscheidung
Status: nicht aktiviert. Der Schalter steht auskommentiert in
public-headers.inc und apex-headers.inc.
Wirkung: der Browser merkt sich fuer max-age Sekunden, dass der Host nur
ueber HTTPS angesprochen werden darf. Zertifikatswarnungen sind dann nicht
mehr per Klick uebergehbar.
includeSubDomains erweitert die Policy auf die Subdomains dieses
Hosts, nicht auf Geschwister-Hosts. Ein Header von cloud.vpn21.net gilt
fuer *.cloud.vpn21.net, nicht fuer shop.vpn21.net. Damit alle
*.vpn21.net erfasst werden, muss der Header vom Apex vpn21.net kommen.
Unbedenklich, solange die internen Dienste unter einer getrennten Domain
liegen. Vor einer Migration der internen Dienste unter vpn21.net neu
bewerten -- includeSubDomains wuerde sie dann mit erfassen und HTTP
dort abschalten.
Das woechentliche HTTP-Fenster fuer Let's Encrypt ist nicht betroffen: HSTS ist eine Browser-Policy, der ACME-Validator wertet sie nicht aus, HTTP-01-Challenges funktionieren unveraendert.
preload ist eine separate, nur schwer rueckgaengig zu machende
Entscheidung und bleibt vorerst aussen vor.
Namensraum p21.net -- offener Punkt
Die Domain p21.net ist inzwischen oeffentlich vergeben und nicht unsere;
die interne Nutzung ist knapp 30 Jahre alt und historisch gewachsen.
Bisher keine Konflikte. Die Risiken bestehen unabhaengig vom eigenen Setup:
- DNS-over-HTTPS. Browser loesen zunehmend am lokalen Resolver
vorbei auf. Dann landen Anfragen an
iobroker.p21.netbeim fremden Server -- per Klartext-HTTP, samt Cookies und ggf. Basic-Auth-Credentials. Ohne TLS gibt es auch keine Warnung, die das abfaengt. - HSTS-preload des Eigentuempers mit
includeSubDomainswuerde die HTTP-Verbindung zu den internen Diensten schlagartig und dauerhaft verweigern lassen. - Zertifikate fuer
*.p21.netstellt keine oeffentliche CA aus.
Migrationsoptionen
| Option | Kosten | Bewertung |
|---|---|---|
p21.local | keine | ungeeignet. .local ist per RFC 6762 fuer mDNS reserviert. Avahi, Bonjour und systemd-resolved fangen die Namen ab; auf macOS erreicht eine .local-Anfrage den Unicast-Resolver grundsaetzlich nicht.
|
p21.internal | keine | brauchbar. ICANN hat .internal im Juli 2024 dauerhaft von der Delegation ausgenommen, ausdruecklich fuer private Nutzung. Kann nie von Dritten registriert werden. Zertifikate weiterhin nur ueber eigene CA.
|
p21.vpn21.net | keine | beste Variante bei Bedarf an internem TLS. Split-Horizon-DNS, Wildcard-Zertifikat via DNS-01, keine CA-Verteilung auf den Clients. Wechselwirkung mit HSTS beachten, siehe oben. |
p21.org / p21.site | teuer bzw. vergeben | kein Vorteil gegenueber den kostenlosen Optionen |
Der ICANN-Draft zu .internal empfiehlt selbst die Subdomain einer eigenen
Domain als erste Wahl und die reservierte TLD erst danach.
