TL;DR něco k nastavení Nginx
Ahoj,
když se tady poslední dobou probírá dost Nginx, tak jsem si říkal, že
zkusím napsat nějaké poznámky, které jsem jinde uceleně nenašel, k
věcem, co mi přišly špatné. Snad to někomu pomůže. Přeci jen je prima,
pokud ty služby máme nastaveny správně. Omlouvám se všem, pro které to
byly sovy v Aténách.
Předně musím říct, že já mám Nginx nesmírně rád; myslím si, že Igor
Sysojev odvedl výbornou práci a trošku mu závidím.
Již jsem nějaký ten Nginx nastavil (včetně vlastních modulů), takže vím,
co mne také na něm hněvá.
1) tip: dejte si pozor na to, že Nginx v upstreamu nepoužívá při HTTPS
SNI. `proxy_ssl_server_name on`, defaultně je to off;
2) Nginx neumí ještě ani v roce 2020 volbu ekvivalentní `strict-sni` v
HAProxy, bug je na to veden 8 let a z vývojářů je cítit lehká arogance a
nepochopení stavu věci [
https://trac.nginx.org/nginx/ticket/195].
Někteří to řeší tak, že dávají HAProxy před Nginx, což je IMO overkill.
Pokud mi na HTTPS přistupuje klient a neuvede SNI pole v TLS, Nginx
použije defaultně první TLS certifikát, který má v konfiguraci uvedený.
Mi se to nelíbí a nedává mi to smysl. Je radost se podívat, jak mají
weby vedené u Cloudflare ve Qualys SSL reportu pouze jeden certifikát.
(Tady je vidět rozdíl: nějaký náhodný Cloudflare hostovaný web:
https://www.ssllabs.com/ssltest/analyze.html?d=oops.ml&s=2606%3a4700%3a…
a tady se omlouvám Janovi, že jsem vzal jeho web, ale nic jiného mne
nenapadlo:
https://www.ssllabs.com/ssltest/analyze.html?d=www.zazen-nudu.cz&s=37.2…
(certificate 2 pro No SNI))
Z vlastních zkušeností mohu říci, že bez SNI a s chybným SNI, mám
výhradně závadný provoz z Ruska a Číny a takový, kde si jakýsi člen
vpsFree, co měl "mé" IPv4 a IPv6 adresy předemnou, zapomenul zrušit
záznamy v DNS u svých klientů.
Možnost je snadno opatchovat Nginx, nebo trochu zneužít nastavení TLS.
Pokud nepoužijeme TLSv1.3, nýbrž pouze TLSv1.2(!), je možné Nginx jako
první TLS http/server nastavit tak, že bude podporovat pouze aNULL
cipher suite (tj. bez autentizace, takže nebude nabízet certifikát pro
Server Hello) a takovéto spojení budeme zavírat bez odpovědi:
```
$ less /etc/nginx/nginx.conf
http {
ssl_protocols TLSv1.2; # we do not want TLSv1.3 because of "strict
sni" workaround
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256";
ssl_ecdh_curve "prime256v1";
# aka "strict sni" workaround
#
https://trac.nginx.org/nginx/ticket/195
server {
listen *:443 ssl http2;
listen [::]:443 ssl http2;
server_name --;
ssl_ciphers "ADH-AES128-SHA256"; # aNULL cipher, no authentication,
no cert :)
ssl_stapling off;
ssl_certificate certs/dummy.pem; # it could be sneak-oil etc.
ssl_certificate_key certs/dummy.key;
return 444; # non-standard code that closes the connection
}
server {
listen *:443 ssl http2;
listen [::]:443 ssl http2;
server_name
example.com;
root /srv/http/example.com;
index index.txt index.html;
ssl_certificate certs/example.com.pem;
ssl_certificate_key certs/example.com.key;
// …
}
server {
listen *:443 ssl http2;
listen [::]:443 ssl http2;
server_name sample.cz;
root /srv/http/sample.cz;
index index.txt index.html;
ssl_certificate certs/sample.cz.pem;
ssl_certificate_key certs/sample.cz.key;
// …
}
}
```
OK, funguje to pěkně, funguje to dokonce správně, ale s použitím TLSv1.3
to padá. Ovšem zdá se, že to vývojáři nakonec řešit budou. :)
(Dalo by se to asi i řešit přes iptables,
https://github.com/Lochnair/xt_tls, ale do toho jsem se pouštět
nechtěl.)
3) Nginx chybně pracuje s OCSP Must-Staple certifikáty:
Pokud máte v certifikátu "OCSP Must-Staple" assertion (OID
1.3.6.1.5.5.7.1.24), což tedy doporučuji, podporuje je i Let's Encrypt,
Nginx jako první odpověď po restartu OCSP nepošle(!), teprve si jej
získá a bude jej již běžně posílat až s druhou a další odpovědí. Ovšem
to není košér, protože první odpověď je bez OCSP, ovšem s cert.
příznakem Must-Staple.
Vyřešil jsem to tak, že jsem si na nginx.service v systemd (protože jej
mám rád) nabindoval druhou service (spouštěnou/zastavenou automaticky
spolu s nginx), která mi provede první dotazy přes s_client v openssl:
```
$ less /etc/systemd/system/nginx-ocspfix.service
[Unit]
Description=nginx OCSP fix
Wants=network-online.target
After=network-online.target
BindsTo=nginx.service
After=nginx.service
[Service]
Type=oneshot
User=nobody
Group=nobody
ExecStart=/usr/local/bin/nginx-ocspfix.sh
RemainAfterExit=true
[Install]
WantedBy=nginx.service
```
a
```
$ less /usr/local/bin/nginx-ocspfix.sh
#!/bin/bash
for c in /etc/nginx/certs/*.pem; do
domains=$(openssl x509 -noout -text -in $c | grep DNS: | perl -l
-0777 -ne '@names=/\bDNS:([^\s,]+)/g; print join("\n", sort @names);' |
sort -u)
for d in $domains; do
openssl s_client -connect 127.0.0.1:443 -servername $d -status <
/dev/null > /dev/null 2> /dev/null
done
done
```
4) Pikoška – malé tiskací "P", které mne stálo hromadu času, ale spolu s
Googlem jsem to dohromady dal (vize přílohu):
Ve své konfiguraci preferuji své pořadí cipher suites (AES-GCM), ovšem s
tím, že umožňuji využít CHACHA20-POLY1305, pokud klient preferuje ji,
protože je energeticky méně náročná, typicky nemá-li klient v CPU
akcelerované AES-GCM (AES-NI instrukce pro AES a PCLMULQDQ pro Galois
Field operace v x86), jedná se většinou o nějaké Androidy.
Nginx to nastavit umožňuje s novým openssl, ovšem je mu trošku pomoci s
konfigurací. Opět využívám systemd – v proměnných prostředí mu předávám
vlastní konfiguraci openssl.conf:
```$ less /etc/nginx/openssl.conf
openssl_conf = default_conf
[default_conf]
ssl_conf = ssl_sect
[ssl_sect]
system_default = system_default_sect
[system_default_sect]
Options = ServerPreference,PrioritizeChaCha
```
a
```
$ less /etc/systemd/system/nginx.service.d/override.conf
[Unit]
Wants=network-online.target
After=network-online.target
[Service]
Environment=OPENSSL_CONF=/etc/nginx/openssl.conf
```
A jde to! :)
5) QUIC (HTTP/3) netřeba ani vzpomínat, Cloudflare dokonce připravilo
patche a nic…
OK, dost stesku nad Nginx, přeji všem pěkně nastavený Nginx, ať už dělá
proxy nebo servíruje obsah! :)
Petr