Jak optimalizovat TLS na webovém serveru Nginx a generování wildcard certifikátu od Let’s Encrypt

10. prosince 2018

Ukážeme si a vysvětlíme, jak nastavit a optimalizovat TLS na webovém serveru Nginx. Součástí textu je také kapitola věnovaná generování wildcard certifikátu od Let's Encrypt.

Předpokládané znalosti

Očekává se od čtenáře alespoň základní znalost práce s webovým serverem Nginx, schopnost úpravy jeho konfiguračních souborů a alespoň základní představu o fungování HTTPS. Kroky jako instalace a nastavení jiných aspektů webového serveru Nginx spadají mimo zaměření tohoto textu.

Úvod

Tento článek by měl čtenáře seznámit s tím, jak na webovém serveru Nginx (běžícím na operačním systému typu UNIX – v našem případě na Ubuntu Server 18.04 LTS) kvalitně nastavit TLS, tedy šifrovanou komunikaci při připojení přes HTTPS. Měřítkem kvality nastavení, které je poměrně široce rozšířené, bude v tomto případě otestování bezplatnou službou SSL Server Test provozovanou společností Qualsys. Výsledkem takového otestovaní je známka od F (nejhorší) po A+ (nejlepší). Naším cílem tedy bude z tohoto testování získat známku A+ a navíc implementovat další doporučená nastavení, která přispějí bezpečnosti a optimalizaci (a z výsledku testu odstraní většinu “oranžových” kolonek, které upozorňují na možné zlepšení konfigurace). Jednotlivé kroky nastavení se také pokusíme vysvětlit, aby si čtenář neodnesl jenom sadu konfiguračních direktiv, které by bez hlubšího pochopení vkládal do konfiguračních souborů. Všechna nastavení budeme dělat ze stavu, ve kterém se Nginx nachází po instalaci, tedy obsahující pouze výchozí konfiguraci.

Certifikát a doména

Abychom vůbec mohli nastavit HTTPS, které bude pro uživatelské prohlížeče důvěryhodné, budeme potřebovat certifikát a ten je obvykle vázán na jednu či více domén. Certifikační autoritou, od které takový certifikát získáme, bude organizace Let’s Encrypt, která tyto certifikáty vydává bezplatně. Jediný háček, který tento certifikát má, je platnost pouze na 90 dní. Certifikát se však dá opakovaně obnovovat (na dalších 90 dní).

Generování wildcard certifikátu

Získání certifikátu od organizace Let’s Encrypt lze realizovat mnoha způsoby, pro hlubší ponoření do možností generování a managementu certifikátů doporučujeme pěkný článek na serveru root.cz, který seznamuje čtenáře s použitím acme.sh klienta, a pak samozřejmě dokumentaci samotného acme.sh klienta na githubových stránkách. Co ovšem zmíněný článek neobsahuje, je generování certifikátů pro wildcard domény, u kterých se postupuje trochu jinak, a proto zde najdete také kapitolu věnující se i této úzce související problematice. Chceme-li totiž certifikát, kterým obsloužíme všechny naše domény třetího řádu (třeba i ty budoucí), pak je nutné vytvořit DNS záznam s výzvou, kterou ověří Let’s Encrypt. Ten získáme následujícím příkazem (keylength můžeme samozřejmě podle libosti upravit):
acme.sh --issue -d example.com -d '*.example.com' --dns --keylength 2048 --yes-I-know-dns-manual-mode-enough-go-ahead-please
To nám vygeneruje dvě acme výzvy, které vložíme jako TXT záznam do DNS zóny naší domény a až se DNS záznam propíše, tak spustíme příkaz:
acme.sh --renew -d example.com -d '*.example.com' --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please
Pokud vše proběhlo úspěšně, máme nyní certifikát, který můžeme použít pro jakoukoliv subdoménu naší domény. Generování by mělo vytvořit následující soubory:
ca.cer
fullchain.cer
example.com.cer
example.com.conf
example.com.csr.conf
example.com.key
Nevýhodou ověřování přes DNS záznam však je, že za devadesát dní je nutné postup opakovat (a upravit DNS záznam), abychom prodloužili platnost certifikátu. V případech, kdy máme DNS vedené u některé z větších služeb jako je Cloudflare, které poskytuje API pro správu, umí acme.sh klient s tímto API komunikovat a tak i upravovat DNS záznamy, čímž bychom měli být schopni tento úkon poměrně snadno automatizovat. Používání wildcard certifikátu přináší sice větší pohodlnost spravování domén, nicméně taky přínáší bezpečnostní riziko, neboť budete všechny subdomény (resp. servery za nimi stojícími) spravovat jedním privátním klíčem, což samozřejmě zvyšuje riziko jeho kompromitace. Je tedy na vašem zvážení, pro které situace je lepší generovat individuální certifikáty nebo jeden wildcard.

Základní nastavení HTTPS Nginxu

Budeme vycházet ze zmíněného článku na serveru root.cz, tedy že vygenerované certifikáty a klíč máme v domovském adresáří uživatele letsencrypt a mají vhodným způsobem nastavena přístupová práva. Nejjednodušší nastavení HTTPS uděláme přidáním následujícíh direktiv do server bloku konfigurace:
ssl_certificate /home/letsencrypt/.acme.sh/example.com/fullchain.cer;
ssl_certificate_key /home/letsencrypt/.acme.sh/example.com/example.com.key;
a upravením direktivy listen na následující podobu:
listen 443 ssl;
listen [::]:443 ssl;
Třešničkou na dortu bude přesměrování veškerého provozu z nešifrovaného HTTP na šifrované HTTPS. Toho docílíme přidáním dalšího server bloku konfigurace s následujícím obsahem:
server {
      server_name _;
      listen 80 default_server;
      listen [::]:80 default_server;
      return 301 https://$host$request_uri;
}
Nyní máme funkční HTTPS a když otestujeme náš server SSL Testem, měli bychom dostat známku mezi B a A, Nginx má totiž poměrně rozumnou výchozí konfiguraci. Nyní se vrhneme na další způsoby vylepšení našeho HTTPS.

Vypnutí SSL

Protokol pro bezpečnou komunikaci se s časem stále vyvíjí v reakci na zranitelnosti, které v něm byly odhaleny. Historicky se tato technologie nazývala SSL (Secure Sockets Layer) a první veřejně používána byla SSL verze 2.0 od roku 1995. Následovalo SSL 3.0 v roce 1996. Pak došlo k přejmenování na TLS a první TLS ve verzi 1.0 přišlo na svět v roce 1999. Od té doby došlo k dalšímu vývoji TLS a nejvyšší verze je aktuálně TLS 1.3 vydaná v roce 2018. Bohužel název SSL se vryl do paměti mnoha lidí a tak se běžně používá v situacích, kdy se vlastně mluví o TLS. Samotné protokoly SSL 2.0 a SSL 3.0 (a dokonce i TLS 1.0) už jsou považovány za zastaralé a zranitelné a doporučuje se je nepoužívat. Ponechat TLS 1.0 zapnuté má smysl pouze v případě potřeby kompatibility se staršími zařízeními. Nginx umožňuje vyjmenovat, které varianty SSL či TLS smí používat, následující direktivou:
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
V době psaní tohoto článku je rozšíření TLS verze 1.3 zatím poměrně v plenkách, ale proč nebýt připraven, že? (Nginx konkrétně podporuje TLSv1.3 od verze 1.15.3 při kompilaci s OpenSSL verze 1.1.1. Oba tyto balíky nejsou v době psaní tohoto článku ve stable repozitářích.)

Výběr šifrovacích sad

Stejně jako se vyvíjel samotný protokol SSL a jeho nástupce TLS, tak se vyvíjí i svět šifer. Některé jsou považovány za zranitelné, jiné nikoliv (nebo méně). Pokud použijete nějakou nevhodnou a zranitelnou šifrovací sadu, zmíněný SSL Server Test vám to dá určitě vědět. Zde hodně záleži na tom, jaký kompromis mezi zpětnou kompatibilitou a mírou bezpečnosti chcete nebo potřebujete. Důležité je zmínit, že záleží na pořadí, v jakém jednotlivé šifrovací sady do konfigurace uvedete, první mají samozřejmě prioritu. My jsme pro náš ptestovací server použili tyto sady, které nám (v říjnu 2018) v testu nepřinesly žádná varování a všechny by měly podporovat forward secrecy (viz dále):
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;
Druhou direktivou zajistíme, jak její název sám napovídá, aby při vyjednávání s klientem o tom, jakou sadu pro komunikaci použít, měl přednost server a ne klient. K výběru šifrovacích sad (i vzhledem ke zpětné kompatibilitě a míře bezpečí) můžeme získat kvalitní nápovědu na stránkách wiki.mozzila.org.

DH parametr a dopředná bezpečnost (forward secrecy)

Jedná se o parametr, který se používá při Diffieho-Hellmanově výměně klíčů, což je způsob, jakým si server a klient dokáží domluvit společné "tajemství", aniž by si ho museli vzájemně předat. Tímto způsobem si tak mohou vygenerovat unikátní šifrovací klíče pro každé spojení, čímž se zajistí, že v případě kompromitace jednoho klíče nelze dešifrovat i všechny předchozí zaslané zprávy. Tomu se říká (perfect) forward secrecy (PFS). Moc hluboko do toho tématu zde zacházet nebudeme, nicméně zmíníme, že pro domluvu bezpečného "tajemství" potřebuje server dostatečně dlouhé bezpečené prvočíslo (vhodné je alespoň 2048 bitů, více viz. útok Logjam). Generování takového čísla však je poměrně náročný úkol, pokud by se měl dělat při každém spojení. Z bezpečnostního hlediska navíc ani nevadí, pokud je použito opakovaně. Generování tohoto bezpečného prvočísla provedeme následujícím příkazem:
openssl dhparam -out /home/letsencrypt/.acme.sh/example.com/dhparam.pem 2048
A následně vložíme direktivu do konfigurace Nginxu:
ssl_dhparam /home/letsencrypt/.acme.sh/example.com/dhparam.pem;
U některých starších systémů může být toto problematické (například Java 6 si neporadí s delším parametrem než 1024 bitů). Toto nastavení se týká zejména šifrovacích sad, které začínají písmeny DHE (Diffie-Hellman Ephemeral) a podporují forward secrecy. Novější typ ECDHE (Elliptic Curve Diffie-Hellman Ephemeral), které jsou, jak název napovídá, založené na eliptických křivkách, parametr nepotřebují a jejich použití je výpočetně méně náročné. Pokud se tedy rozhodnete nasadit pouze moderní sady (ovšem bez kompatibility se staršími zařízeními), nemusíte si lámat hlavu generováním DH parametru.

OCSP razítkování

Online Certificate Status Protocol (OCSP) je protokol, kterým si klient (uživatelův prohlížeč) ověří platnost našeho certifikátu u jeho vydavatele. V běžném provozu to znamená další spojení, které musí klient vykonat, a to jednak zpomaluje celý proces navázání spojení s naším serverem a jednak také představuje riziko pro klientovo soukromí, neboť tímto způsobem může vydavatel certifikátu (nebo podvržená třetí strana) sledovat, na které stránky klient přistupuje. Nginx proto umí toto ověření svého vlastního certifikátu pravidelně získávat sám s digitálním podpisem certifikační autority a předat ho připojujícím se klientům, čímž se tento problém odstraní. Toho můžeme docílit přidáním následujících direktiv do konfigurace:
resolver 217.31.204.130;
ssl_stapling on;
ssl_stapling_verify on;
Direktiva resolver Nginxu udává, jaký DNS resolver má používat a to i v případě OCSP razítkování. Druhá direktiva povoluje samotné OCSP razítkování. Poslední direktiva ověřuje odpovědi získané od certifikační autority. Protože jsme v našem případě uvedli v direktivě ssl_certificate soubor s celým řetězem certifikátů, nemělo by být nutné již přidávat direktivu ssl_trusted_certificate.

Caching TLS spojení

V prvotní fázi spojení dochází mezi klientem a serverem k vyjednání způsobu zabezpečené komunikace a výměně klíčů. Následně dojde k vyřízení samotného požadavku (tedy například stažení stránky) a spojení se ukončí. Při novém spojení (byť se stejným klientem) je pak nutné projít celým vyjednávacím procesem znovu. To přidává značnou režii navíc, která se promítne jak zpomalením celé komunikace, tak i větší výpočetní zátěží pro náš server. V tento okamžik přichází na scénu TLS cache, do které si server může dočasně uložit výsledky takových vyjednávání a při novém příchozím spojení si ušetří práci s jejich opětovným vyjednáváním. Cache povolíme přidáním těchto direktiv do konfigurace:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 60m;
První direktiva povolí cache sdílet mezi servervými workery, pojmenuje cache "SSL" a nastaví její velikost v bytech - v tomto případě 10 megabytů (podle Nginx dokumentace pojme jeden megabyte zhruba 4000 spojení). Druhou direktivou pak nastavíme, jak dlouho bude vyjednané spojení platné (v minutách) - v našem případě tedy 60 minut.

Použití HTTP2

HTTP2 je novější verze protokolu HTTP, která přináší řadu výkonostních vylepšení pro zrychlení komunikace jako je odbavení více požadavků současně v rámci jednoho spojení nebo možnost serveru něco poslat klientovi dříve, než o to požádá. Důvod, proč toto zmiňuji v článku zaměřeném na TLS, je ten, že je HTTP2 zpravidla použitelné pouze při zabezpečené komunikaci - většina prohlížečů z různých důvodu neimplementovala HTTP2 pro nešifrovanou komunikaci. Povolení HTTP2 tedy v kombinaci s TLS provedeme úpravou direktivy listen do následující podoby:
listen 443 ssl http2;
listen [::]:443 ssl http2;

HSTS (HTTP Strict Transfer Policy)

Po nasazení TLS je poměrně běžné veškerý nešifrovaný provoz přesměrovat na HTTPS. Úplně první požadavek je však zpravidla vyslán nezabezpečeně a vystavuje vás tak útoku SSL Strip. Zmíněné HSTS bylo vymyšleno právě jako obrana před tímto typem útoku. Jedná se o hlavičkový parametr serverové odpovědi, kterým klientovi (prohlížeči uživatele) při první návštěvě řeknete, aby na tuto konkrétní doménu chodil vždy automaticky pouze skrze bezpečné HTTPS. To nastavíme skrze následující direktivu:
add_header Strict-Transport-Security "max-age=15778800" always;
Jak název napovídá, max-age udává, jak dlouho se tato informace v prohlížeči uchová (v sekundách - v tomto případě je to počet sekund šesti měsíců) od poslední návštěvy webové stránky. SSL Server Test považuje šest měsícu jako minimum. Slovo always na konci pouze dodává, aby byl tento hlavičkový parametr posílán vždy nezávisle na tom, jaký je kód odpovědi. Alternativně je ještě možné použít direktivu i pro veškeré subdomény:
add_header Strict-Transport-Security "max-age=15778800; includeSubDomains" always;
Tato varianta má však stále jednu slabinu a to situace, kdy uživatel navšítíví stránku poprvé (nebo uběhla max-age doba od poslední návštěvy, uživatel smazal cache prohlížeče či přeinstaloval systém aj.). Vývojáři prohlížečů proto udržují takzvaný preload seznam, který obsahuje seznam domén, na které se i při první návštěvě připojí uživatel přes HTTPS (a prohlížeč ani neumožní se připojit jinak). Než si však ukážeme, jak se na tento seznam dostat, je důležité říct, že není snadná a rychlá cesta zpět k nezabezpečenému HTTP. Pokud je vaše doména na preload listu, budou prohlížeče přistupovat přes HTTPS automaticky také na všechny subdomény, tedy je musíte mít vybavené certifikátem. Hlavičkový parametr musí obsahovat includeSubDomains a max-age musí být minimálně jeden rok (31536000 sekund). Postupujte tedy velmi opatrně! Před nasazením doporučuji pročíst další doporučení na hstspreload.org. Na preload list se pak můžete dostat upravením direktivy do následujícího tvaru:
add_header Strict-Transport-Security "max-age=15778800; includeSubDomains; preload" always;

CAA

Certification Authority Authorization (CAA) je způsob validace, který se snaží posílit celou infrastrukturu veřejných klíčů (PKI). Myšlenka je ta, že pro svou doménu vyberete pouze určitou certifikační autoritu, která má jediná právo vydávat pro ní certifikáty, a tento záznam uvedete do DNS záznamu. Od září roku 2017 by měla každá certifikační autorita před vydáním certifikátu tento záznam zkontrolovat a případně odmítnout certifikát vydat. DNS záznam vypadá následovně:
example.com. CAA 128 issue "letsencrypt.org"
example.com. CAA 128 issuewild "letsencrypt.org"
Druhý ze záznamů upravuje vydávání wildcard certifikátů. Případně je možné ještě přidat URL, na kterou by autorita měla hlásit nevalidní požadavek o vydání certifikátu. Například takto:
example.com. CAA 128 iodef "mailto:hostmaster@example.com"

Vznik tohoto textu byl podpořen Nástrojem Evropské unie pro propojení Evropy.
drawing