Articolo originale: https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/
Introduzione
Hai mai provato a impostare una qualche sorta di server a casa? Dove devi aprire una nuova porta per ogni servizio? E devi ricordarti quale porta è assegnata a quale servizio e quale sia il tuo indirizzo ip di casa? Si tratta sicuramente di qualcosa che funziona e le persone lo fanno da molto tempo.
Tuttavia non sarebbe carino digitare plex.example.com e avere accesso istantaneo al tuo media server? Questo è esattamente quello che farà per te un reverse proxy e, in combinazione con Docker, è più facile che mai.
Prerequisiti
Docker e Docker-Compose
Dovresti avere installato Docker versione 17.12.0+ e Docker-Compose versione 1.21.0+.
Dominio
Dovresti avere un dominio impostato e un certificato SSL a esso associato. Se non ne hai uno, segui questa mia guida su come ottenerlo gratis con LetsEncrypt.
Cosa Tratterà Questo Articolo
Credo fermamente occorre comprendere quello che si sta facendo. Un tempo avrei seguito le guide senza avere il minimo indizio su come risolvere i problemi. Se questo è quello che vuoi fare qui c'è un ottimo tutorial che tratta l'impostazione. Anche se i miei articoli sono più lunghi, dovresti alla fine riuscire a comprendere come funziona il tutto.
Quello che imparerai qui è cos'è un reverse proxy, come impostarlo e come metterlo in sicurezza. Ho fatto del mio meglio per dividere l'argomento in sezioni, separate da intestazioni, quindi sentiti libero di saltarne qualcuna, se lo desideri. Consiglio di leggere l'articolo per intero prima di iniziare l'impostazione.
Cos'è un Reverse Proxy?
Proxy Normale
Iniziamo con il concetto di un proxy normale. Anche se questo è un termine molto diffuso nella comunità tecnologica, non è l'unico ambito nel quale viene utilizzato. Proxy significa che una certa informazione passa attraverso una terza parte, prima di giungere a destinazione.
Diciamo che se non vuoi che un servizio conosca il tuo IP, puoi usare un proxy. Un proxy è un server che è stato impostato per questo specifico scopo. Se il server proxy che stai usando è situato, ad esempio, in Amsterdam, l'IP che verrà mostrato al mondo esterno sarà l'IP del server di Amsterdam. Gli unici a conoscere il tuo IP saranno quelli che hanno il controllo del server proxy.
Reverse Proxy
Per metterla in termini semplici, un proxy aggiunge un livello di mascheramento. Per un reverse proxy il concetto è lo stesso, eccetto che invece di mascherare le connessioni verso l'esterno (tu che accedi a un server web), maschera le connessioni in entrata (quelli che accedono al tuo server). Tu fornisci semplicemente un URL tipo esempio.com, e ogniqualvolta qualcuno accede a quell'URL il tuo reverse proxy si prenderà cura di dove indirizzare questa richiesta.
Supponi di avere due server impostati nella tua rete interna. Server1 è su 192.168.1.10 e Server2 è su 192.168.1.20. Attualmente il tuo reverse proxy sta inviando le richieste che provengono da esempio.com a Server1. Un giorno devi fare degli aggiornamenti alla pagina web. Invece di buttare giù il sito per manutenzione, crei semplicemente una nuova impostazione su Server2. Una volta fatto, cambiando semplicemente una singola riga nella configurazione del tuo reverse proxy e ora le nuove richieste vengono indirizzate verso Server2. Assumendo che il reverse proxy sia impostato correttamente non dovresti assolutamente avere tempi di inattività.
Tuttavia, forse il maggior vantaggio nell'avere un reverse proxy è che puoi avere servizi in esecuzione su una moltitudine di porte, ma tenere aperte solo le porte 80 e 443, HTTP e HTTPS rispettivamente. Tutte le richieste arriveranno alla tua rete su queste due porte e il reverse proxy si occuperà del resto. Tutto ciò avrà molto più senso una volta che inizieremo con l'impostazione del proxy.
Impostare il Contenitore
Cosa Fare
docker-compose.yaml
:
version: '3'
services:
reverse:
container_name: reverse
hostname: reverse
image: nginx
ports:
- 80:80
- 443:443
volumes:
- <percorso/alla/tua/configurazione>:/etc/nginx
- <percorso/ai/tuoi/certificati>:/etc/ssl/private
Prima di tutto, dovresti aggiungere un nuovo servizio al tuo file docker-compose. Puoi chiamarlo come desideri, in questo caso io ho scelto reverse. Qui ho scelto semplicemente nginx come immagine, tuttavia in ambiente di produzione è buona pratica specificare una versione nel caso in cui ci siano aggiornamenti futuri non retrocompatibili.
Poi dovresti creare due volumi docker per due cartelle. /etc/nginx è dove sono conservati tutti i tuoi file di configurazione e /etc/ssl/private è dove si trovano i tuoi certificati SSL. È MOLTO importante che la cartella di configurazione NON esista sul sistema ospitante quando si lancia il contenitore per la prima volta. Quando farai partire il contenitore tramite docker-compose la cartella verrà automaticamente creata e popolata con i contenuti provenienti dal container. Se hai creato una cartella di configurazione vuota nel tuo sistema, questa cartella sarà montata, di conseguenza la cartella all'interno del contenitore sarà anch'essa vuota.
Perché Funziona
Non c'è molto da dire qui. Per lo più è come avviare un qualsiasi altro contenitore con docker-compose. Quello che dovresti notare qui è che hai collegato le porte 80 e 443. È da lì che tutte le richieste arriveranno, e saranno inoltrate a qualsiasi servizio vorrai specificare.
Configurare Nginx
Cosa Fare
Ora nel tuo sistema ospitante dovresti avere la cartella di configurazione. Andando su questa directory, dovresti vedere un insieme di diversi file e una cartella chiamata conf.d
. Al suo interno saranno sistemati tutti i tuoi file di configurazione. Attualmente c'è un singolo file default.conf
, che puoi cancellare.
Sempre all'interno di conf.d
, crea due cartelle: sites-available
e sites-enabled
. Portati in sites-available
e crea il tuo primo file di configurazione. Andremo a impostare una voce per Plex, ma sei libero di usare un altro servizio che ti piacerebbe impostare. Non ha nessuna importanza il nome con il quale salvare il file, tuttavia io preferisco chiamarlo plex.conf
.
Ora apri il file e digita quanto segue:
upstream plex {
server plex:32400;
}
server {
listen 80;
server_name plex.example.com;
location / {
proxy_pass http://plex;
}
}
Vai alla directory sites-enabled
e digita il seguente comando:
ln -s ../sites-available/plex.conf .
Verrà creato un link simbolico al file nell'altra cartella. Ora rimane solo una cosa, vale a dire modificare il file nginx.conf
nella cartella di configurazione. Se apri questo file, dovresti vedere quanto segue come ultima riga:
include /etc/nginx/conf.d/*.conf;
Modificala in:
include /etc/nginx/conf.d/sites-enabled/*.conf;
Per fare in modo che un reverse proxy funzioni davvero, devi ricaricare il servizio nginx all'interno del contenitore. Dal sistema ospitante esegui docker exec <nome-contenitore> nginx -t
. Questo manderà in esecuzione un controllo di sintassi per i tuoi file di configurazione. Dovrebbe stampare un messaggio di verifica positiva. Ora esegui docker exec <nome-contenitore> nginx -s reload
. Questo invia un segnale al processo nginx dicendo che è necessario un riavvio, congratulazioni! Ora hai in esecuzione un reverse proxy e dovresti riuscire a raggiungere il tuo server a plex.example.com (assumendo che tu abbia eseguito l'inoltro della porta 80 al tuo host dal tuo router).
Anche se il reverse proxy sta funzionando, viene eseguito su HTTP, che non fornisce alcuna crittografia. La parte che segue sarà dedicata a mettere in sicurezza il tuo proxy, e ottenere un punteggio perfetto su SSL Labs.
Perché Funziona
Il File di Configurazione
Come puoi vedere, il file plex.conf
consta di due parti. Una parte upstream
e una parte server
. Partiamo da server
. Qui è dove definisci la porta che dovrà ricevere le richieste in arrivo, con quale dominio questa configurazione dovrà trovare corrispondenza e verso dove dovrebbe eseguire l'inoltro.
Per il modo in cui è impostato questo server, dovresti generare un file per ogni servizio per il quale vuoi passare le richieste tramite proxy, quindi ovviamente devi distinguere in qualche modo quale file deve ricevere ciascuna richiesta. Questo è il compito della direttiva server-name
. Successivamente abbiamo la direttiva location
.
Nel nostro caso ci serve solo una direttiva location
, tuttavia ne puoi avere quante ne desideri. Immagina di avere un sito web con un frontend e un backend. A seconda dell'infrastruttura che stai usando, dovresti avere il frontend in un contenitore e il backend in un altro. Potresti quindi avere location / {}
, che invierà le richieste al frontend, e location /api/ {}
, che invierà le richieste al backend. Improvvisamente ti ritrovi con servizi multipli in esecuzione su un unico domino.
Per quanto riguarda la parte upstream
, può essere usata per il bilanciamento del carico. Se vuoi saperne di più puoi dare un'occhiata alla documentazione ufficiale qui. Per il nostro semplice caso, è sufficiente che tu definisca il nome host o l'indirizzo ip del servizio che deve essere oggetto di proxy, verso quale porta dovrebbe eseguire l'inoltro e poi fai riferimento al nome definito in upstream nella direttiva location
.
Hostname vs Indirizzo IP
Per capire cos'è un hostname (nome host), facciamo un esempio. Supponi di trovarti sulla tua rete di casa all'indirizzo 192.168.1.0. Successivamente imposti un server su 192.168.1.10 e su questo esegui Plex. Ora puoi accedere a Plex su 192.168.1.10:32400, fintanto che ti trovi nella stessa rete. Un'altra possibilità è di dare al server un nome host. In questo caso l'hostname sarà plex. Ora puoi accedere a Plex digitando plex:32400 nel tuo browser!
Lo stesso concetto fu introdotto in docker-compose dalla versione 3. Se dai un'occhiata al file docker-compose creato in precedenza in questo articolo, noterai che gli ho passato una direttiva hostname: reverse
. Ora tutti gli altri contenitori possono accedere al mio reverse proxy utilizzando il suo nome host. Una cosa molto importante da notare è che il nome del servizio deve coincidere con quello di hostname. Questa è una cosa che i creatori di docker-compose hanno scelto di imporre.
Un'altra cosa veramente importante da ricordare è che, nella modalità predefinita, i contenitori docker sono inseriti nella loro propria rete. Il che significa che non potrai accedere al contenitore usando il suo hostname, se ti trovi su un portatile collegato alla rete del tuo sistema ospitante. Solo i contenitori sono in grado di accedere a ciascuno degli altri tramite il loro hostname.
Per riepilogare e rendere più chiaro. Nel tuo file docker-compose aggiungi la direttiva hostname
per i tuoi servizi. Per la maggior parte delle volte i tuoi contenitori otterranno un nuovo IP ogni volta che li farai ripartire, per cui fare riferimento a un contenitore tramite hostname, significa che non importa quale indirizzo IP esso riceverà.
Sites-available e Sites-enabled
Perché stiamo creando le directory sites-available
e sites-enabled
directories? Non si tratta di qualcosa di mia creazione. Se installi Nginx su un server, vedrai che queste cartelle sono già presenti. Tuttavia visto che Docker è concepito avendo in mente il concetto di microservizio, laddove ogni contenitore dovrebbe fare una cosa sola, queste cartelle sono omesse nel contenitore. Le stiamo ricreando nuovamente, a causa del modo in cui utilizziamo il contenitore.
Sì, potresti sicuramente creare una cartella sites-enabled
oppure ospitare i tuoi file di configurazione in conf.d
. In questo modo, sei in grado di avere in giro una configurazione passiva. Supponiamo che tu stia facendo manutenzione e non vuoi avere il servizio attivo; rimuovi semplicemente il link simbolico e riaggiungendolo quando vuoi che il servizio sia nuovamente attivo.
Link Simbolici
I link simbolici sono una caratteristica molto potente del sistema operativo. Personalmente non li ho mai usati prima di impostare un server Nginx, ma da allora li sto usando ovunque posso. Supponiamo che stai lavorando su 5 progetti diversi ma tutti questi progetti usano in un qualche modo lo stesso file. Potresti copiare il file in ogni progetto, e fare riferimento ad esso direttamente, oppure potresti piazzare il file in un solo posto, e nei 5 progetti creare link simbolici verso quel file.
Questo porta due vantaggi: lo spazio occupato sarà fino a 4 volte meno di quanto avresti utilizzato altrimenti e, quello più potente di tutti; puoi modificare il file in un solo posto e le modifiche si ripercuoteranno su tutti i 5 progetti in una sola volta! Questa è stata un po' una divagazione, ma penso che sia valsa la pena menzionarlo.
Mettere in Sicurezza il Proxy Nginx
Cosa Fare
Vai alla tua cartella di configurazione, crea 3 file e inserisci i seguenti dati:
common.conf
:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
common_location.conf
:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
ssl.conf
:
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ecdh_curve secp384r1;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384 OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256";
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparams.pem;
ssl_certificate /etc/ssl/private/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
Ora apri il file plex.conf
e modificalo come segue (attenzione alle righe 6, 9, 10 e 14):
upstream plex {
server plex:32400;
}
server {
listen 443 ssl;
server_name plex.example.com;
include common.conf;
include /etc/nginx/ssl.conf;
location / {
proxy_pass http://plex;
include common_location.conf;
}
}
Ora torna alla radice della tua cartella di configurazione ed esegui il seguente comando:
openssl dhparam -out dhparams.pem 4096
Ci vorrà molto tempo per il completamento del processo, fino a un'ora in alcuni casi.
Se hai seguito il mio articolo circa l'ottenimento di un certificato SSL LetsEncrypt, il tuo certificato dovrebbe trovarsi in </percorso/alla/tua/configurazione/letsencrypt>/etc/letsencrypt/live/<dominio>/
.
Quando ho aiutato un amico a impostarlo sul suo sistema, siamo incappati in alcuni problemi, in quanto non poteva aprire i file che erano situati in quella directory. Molto probabilmente a causa di qualche problema di permessi. La soluzione semplice per questo problema è di creare una directory SSL, tipo </percorso/alla/tua/configurazione/nginx>/certs
, quindi montarla nella cartella /etc/ssl/private
del contenitore con Nginx. Nella nuova cartella creata, poi dovresti creare dei link simbolici ai certificati che si trovano nella tua cartella di configurazione LetsEncrypt.
Una volta che il comando openssl
ha terminato la sua esecuzione, dovrai eseguire docker exec <nome-contenitore> nginx -t
per assicurarti che tutta la sintassi sia corretta, quindi riavviare il contenitore eseguendo docker exec <nome-contenitore> nginx -s reload
. A questo punto tutto dovrebbe essere in esecuzione e ora dovresti avere un reverse proxy funzionante e perfettamente sicuro!
Perché Funziona
Se osservi il file plex.conf
, c'è solo una modifica importante, vale a dire su quale porta il reverse proxy è in ascolto e specificare che quella è una connessione ssl. Poi ci sono altri tre posti nei quali stiamo includendo gli altri tre file che abbiamo creato. Sebbene SSL sia di per sé sicuro, questi altri file lo rendono ancora più sicuro. Tuttavia, se per qualche ragione non vuoi includere questi file, dovrai spostare ssl-certificate
e ssl-certificate-key
all'interno del file .conf
. Questo è richiesto affinché una connessione HTTPS possa funzionare.
Common.conf
Guardando il file common.conf
file, abbiamo aggiunto quattro header diversi. Gli header sono qualcosa che il server invia al browser in ogni risposta. Questi header dicono al browser di comportarsi in un certo modo, e spetta al browser applicarli.
Strict-Transport-Security (HSTS)
Questo header dice al browser che le connessioni dovrebbero essere fatte utilizzando HTTPS. Una volta che questo header è stato aggiunto, il browser non ti consente di effettuare semplici connessioni HTTP al server, assicurando che tutte le comunicazioni siano sicure.
Con questo header specifichi se sia possibile per altri siti incorporare in essi il tuo contenuto o meno. Questo impedisce attacchi di clickjacking .
Supponiamo che tu abbia un sito dove gli utenti possono caricare dei file. Non c'è sufficiente validazione sui file pertanto un utente può caricare con successo sul server un file php
, laddove il server si aspetta che sia caricata una immagine. L'attaccante potrebbe essere in grado di accedere al file caricato. Ora il server risponde con una immagine, tuttavia il MIME-type di quel file è text/plain
. Il server 'annusa' il file, quindi esegue lo script php, consentendo all'attaccante di ottenere una Esecuzione Remota di Codice -- RCE (Remote Code Execution).
Con questo header impostato a 'nosniff', il browser non guarderà il file e semplicemente lo tratterà come qualunque cosa il server dice al browser che sia.
Anche se questo header è più necessario su browser meno recenti, è così semplice aggiungerlo che anche tu dovresti farlo. Alcuni attacchi XSS (Cross-site Scripting) possono essere molto intelligenti, mente altri sono molto rudimentali. Questo header dice ai browser di eseguire una scansione delle vulnerabilità semplici e di bloccarle.
Common_location.conf
X-Real-IP
Visto che i tuoi server sono dietro a un reverse proxy, se cerchi di rilevare l'IP del richiedente otterrai sempre l'IP del reverse proxy. Questo header è aggiunto in modo che tu possa vedere quale IP stia effettivamente richiedendo il tuo servizio.
X-Forwarded-For
Talvolta una richiesta di un utente passa attraverso client multipli prima di raggiungere il tuo server. Questo header include un elenco di tutti quei client.
X-Forwarded-Proto
Questo header mostrerà quale protocolo è in uso tra client e server.
Host
Questo assicura che sia possibile fare una ricerca DNS inversa sul nome del dominio. Viene usato quando la direttiva server_name
è diversa da quella alla quale stai inoltrando.
X-Forwarded-Host
Mostra l'effettivo host della richiesta invece del reverse proxy.
X-Forwarded-Port
Aiuta a identificare su quale porta il client ha richiesto il server.
Ssl.conf
SSL è un argomento enorme di per sé, e troppo grande per iniziare a spiegarlo in questo articolo. Ci sono molti eccellenti tutorial in circolazione su come funziona l'handshake SSL, eccetera. Se vuoi esaminare uno specifico file, suggerisco di osservare i protocolli e le cifrature utilizzate e che differenza fanno.
Reindirizzare HTTP verso HTTPS
Quelli più attenti forse possono aver notato che in questa versione sicura si è in ascolto solo sulla porta 443. Il che vuol dire che chiunque cerchi di accedere al sito tramite https://* otterrebbe l'accesso, ma cercare di connettersi via http://* risulterebbe in un errore. Fortunatamente c'è un facile rimedio per questo. Crea un file redirect.conf
con il seguente contenuto:
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
Ora assicurati che il file si trovi nella tua cartella sites-enabled
e, una volta ricaricato il processo Nginx nel contenitore, tutte le richieste alla porta 80 saranno inoltrate alla porta 443 (HTTPS).
Pensieri Finali
Ora che il tuo sito è attivo e funzionante, puoi andare su SSL Labs ed eseguire un test per vedere quanto è sicuro. Al momento della stesura di questo articolo, dovresti ottenere un punteggio perfetto. Tuttavia c'è una cosa importante da notare.
Ci sarà sempre un equilibrio tra sicurezza e convenienza. In questo caso il peso è decisamente dal lato della sicurezza. Se esegui il test su SSL Labs e scorri verso il basso, vedrai che ci sono più dispositivi che non saranno in grado di connettersi al tuo sito, perché non supportano i nuovi standard.
Quindi tienilo a mente quando lo configuri. In questo momento sto solo eseguendo un server a casa, dove non devo preoccuparmi che molte persone possano accedervi. Ma se esegui una scansione su Facebook, vedrai che non avranno un punteggio così alto, tuttavia il loro sito è accessibile da più dispositivi.