Nginx e Certificati
E' necessario innanzi tutto disporre di un opportuno certificato X.509.
L'attivazione di HTTPS per un blocco server avviene specificando il parametro "ssl
" per la direttiva listen
. Ad esempio:
...
server {
listen 443 ssl;
...
}
E' necessario specificare il percorso del certificato e della relativa chiave privata.
I percorsi dei file certificato e chiave privata sono specificati dalle direttive ssl_certificate
e ssl_certificate_key
, anch'esse contenute nel blocco server:
...
server {
listen 443 ssl;
server_name www.miosito.com;
ssl_certificate miosito.com.crt;
ssl_certificate_key miosito.com.key;
...
}
In alcuni casi è possibile che sia il certificato che la chiave privata vengano distribuiti dalla CA nello stesso file (in genere in formato PEM). In questo caso, è sufficiente specificare lo stesso nome di file per entrambe le direttive:
...
server {
listen 443 ssl;
server_name www.miosito.com;
ssl_certificate miosito.com.crt;
ssl_certificate_key miosito.com.crt;
...
}
Cifre ed Algoritmi
Per ragioni di sicurezza si può voler configurare il server Nginx per supportare solo alcuni tra i possibili cifrari e protocolli inclusi in TLS/SSL. Si pensi ad esempio al caso in cui una vulnerabilità 0-day venga resa nota. Una possibile contromisura in attesa di una patch potrebbe essere quella di disabilitare i protocolli affetti.
Per specificare i protocolli da utilizzare in HTTPS si ricorre alla direttiva ssl_protocols
. Ad esempio, il blocco seguente abilita solo TLS 1.1 e 1.2, disabilitando TLS 1.0 e TLS 1.3 (laddove supportato):
...
server {
listen 443 ssl;
server_name www.miosito.com;
ssl_certificate miosito.com.crt;
ssl_certificate_key miosito.com.crt;
ssl_protocols TLSv1.1 TLSv1.2;
...
}
Analogamente, è possibile specificare gli algoritmi di cifratura da utilizzare con la direttiva ssl_ciphers
. Ad esempio:
...
server {
listen 443 ssl;
server_name www.miosito.com;
ssl_certificate miosito.com.crt;
ssl_certificate_key miosito.com.crt;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
...
}
Il formato utilizzato dalla direttiva ssl_ciphers è lo stesso utilizzato dalla libreria openssl
, dato che essa è alla base dell'implementazione di HTTPS in Nginx. L'elenco dei cifrari supportati può essere mostrato utilizzando il comando di shell "openssl ciphers
".
Ottimizzazioni
L'utilizzo di HTTPS richiede maggiori risorse computazionali rispetto ad HTTP, non solo perché il traffico deve essere cifrato prima di essere inviato al client, ma soprattutto per la fase di handshake che come sappiamo, ricorre alla crittografia asimmetrica. Pertanto, una prima semplice ottimizzazione consigliata consiste nell'attivare il keepalive per il proprio server HTTPS. In questo modo, i client cercheranno di riutilizzare le connessioni TCP già instaurate per effettuare richieste successive, invece di aprire una nuova connessione per ogni richiesta. Ciò consente di minimizzare il numero di handshake necessari, riducendo l'impatto sulla CPU del server.
Inoltre, è possibile agire sui parametri che regolano il comportamento della cache delle sessioni di Nginx. La cache delle sessioni contiene i parametri TLS/SSL prodotti dagli ultimi handshake. Grazie ad essa, è possibile velocizzare la fase di handshake riutilizzando i parametri già negoziati in precedenza dagli stessi client, invece di ripetere un handshake completo ogni volta. Nginx permette di regolare sia la dimensione della cache in MB che il timeout (tempo trascorso il quale una sessione viene rimossa dalla cache), utilizzando rispettivamente le direttive ssl_session_cache ed ssl_session_timeout del blocco http.
Ad esempio, per impostare la cache delle sessioni a 10 MB, con un timeout di sessione di 10 minuti, si può configurare il blocco http come segue:
...
http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name www.miosito.com;
keepalive_timeout 70;
ssl_certificate miosito.com.crt;
ssl_certificate_key miosito.com.crt;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
...
}
...
}
Entrambi HTTP e HTTPS
Sebbene l'utilizzo di HTTP sia ormai fortemente sconsigliato in favore di una massiccia adozione di HTTPS, vi sono dei casi in cui può essere necessario attivare entrambi i protocolli. A tal scopo, è sufficiente aggiungere una seconda direttiva listen, specificando la porta 80/TCP (HTTP) invece della 443/TCP (HTTPS). Ad esempio:
...
server {
listen 80;
listen 443 ssl;
server_name www.miosito.com;
ssl_certificate miosito.com.crt;
ssl_certificate_key miosito.com.crt;
ssl_protocols TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
...
}
Certificati e Virtual Host name based
Un problema noto nell'utilizzo di HTTPS con i virtual host name based affligge anche Nginx. Nel caso in cui siano presenti più blocchi server, seppur configurati opportunamente ciascuno con il proprio certificato, si verifica infatti un comportamento inatteso: i client ricevono sempre lo stesso certificato, a prescindere dal sito richiesto, causando il fallimento dell'autenticazione del server.
Ad esempio, si supponga di aver configurato il server nel modo seguente:
...
server {
listen 443 ssl;
server_name www.miosito.com;
ssl_certificate www.miosito.com.crt;
...
}
...
server {
listen 443 ssl;
server_name www.miosito.org;
ssl_certificate www.miosito.org.crt;
...
}
Collegandosi al sito miosito.org
, il client riceverà comunque il certificato associato a miosito.com
, che chiaramente risulterà invalido in quanto assegnato ad un dominio diverso. Il problema, risiede nel fatto che HTTP gira "sopra" TLS/SSL: affinchè il client possa richiedere un host in particolare (usando l'header HTTP Host, per l'appunto), la connessione TLS/SSL deve essere stabilita. Tuttavia, per stabilire la connessione è necessario effettuare uno handshake e quindi ricorrere ad un certificato. Nginx in questo caso seleziona quello di default, ovvero il primo specificato.
L'approccio più comunemente adottato in passato per la risoluzione del problema consiste nell'utilizzo di virtual host IP-based invece che name based: si assegna ad ogni sito un indirizzo IP diverso. I nomi di dominio associati ai due siti verranno risolti rispettivamente sui due IP, così da superare l'ambiguità nell'individuazione del sito richiesto ancor prima di aver effettuato l'handshake TLS/SSL.
Esistono altre soluzioni, largamente utilizzate specialmente nell'ambito dell'hosting condiviso, laddove l'assegnazione di un indirizzo IP dedicato per ogni sito ospitato potrebbe non essere possibile. La prima consiste nell'utilizzare un certificato con più nomi host nel campo SubjectAltName
. Questo permette di utilizzare lo stesso certificato per più host.
La soluzione oggi più adottata consiste nel superare il problema alla radice, ovvero consentendo al client di specificare l'host del sito al quale collegarsi durante lo handshake. Ciò è possibile grazie all'estensione del protocollo TLS nota come SNI, ovvero Server name Indication Extension. La SNI è disponibile in Nginx a partire dalla versione 0.8.21 ed è largamente supportata da browser e librerie HTTP da diversi anni.
Esercizio: SSL con Nginx
Preparazione
Lanciamo un contenitore:
docker run -d --name ngalp -v $HOME/ex/certs:/certs \
-p 8888:80 -p 8443:443 nginx:alpine sleep 1000000
Colleghiamoci al contenitore:
docker exec -ti ngalp sh
Andiamo nella directory di configurazione:
cd /etc/nginx
Configurazione SSL
Editare il file di configurazione del server:
conf.d/default.conf
server {
listen 80;
listen 443 ssl;
server_name geekflare.com;
ssl_certificate certs/geekflare.pem;
ssl_certificate_key certs/geekflare.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
Copia dei File
mkdir certs
cp /certs/certificate.pem certs/geekflare.pem
cp /certs/geekflare.key certs/geekflare.key
Risoluzione Nome Indirizzo
Modificare il file /etc/hosts
, aggiungendo la linea:
172.17.0.2 65ebcf7688ab geekflare.com
Test della Configurazione
Col comando:
nginx -t
Lancio del Server
Se non è già partito:
nginx
Se è già partito:
nginx -s reload
E verificare i processi:
ps wax
Quindi uscire:
exit
Connettività Esterna
Apriamo un browser e puntiamolo a https://localhost:8443
.
Naturalmente ci compare il WARNING.
NOTA: Il warning compare solo se in un esercizio precedente NON abbiamo accettato la URL https://localhost:8443
come ecceezione.
Possiamo dal browser rimuovere questa eccezione e riprovare.
Accettiamo il rischio e funziona.