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.

X-Frame-Options

Con questo header specifichi se sia possibile per altri siti incorporare in essi  il tuo contenuto o meno. Questo impedisce attacchi di clickjacking .

X-Content-Type-Options

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.

X-XSS-Protection

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.