Comment sécuriser une application Node.js conteneurisée avec Nginx, Let's Encrypt et Docker Compose

De Get Docs
Aller à :navigation, rechercher

Introduction

Il existe plusieurs façons d'améliorer la flexibilité et la sécurité de votre application Node.js. L'utilisation d'un proxy inverse comme Nginx vous offre la possibilité d'équilibrer la charge des demandes, de mettre en cache le contenu statique et de mettre en œuvre Transport Layer Security (TLS). L'activation du protocole HTTPS chiffré sur votre serveur garantit que la communication vers et depuis votre application reste sécurisée.

La mise en œuvre d'un proxy inverse avec TLS/SSL sur des conteneurs implique un ensemble de procédures différent du travail direct sur un système d'exploitation hôte. Par exemple, si vous obteniez des certificats de Let's Encrypt pour une application exécutée sur un serveur, vous installeriez le logiciel requis directement sur votre hôte. Les conteneurs vous permettent d'adopter une approche différente. Grâce à Docker Compose, vous pouvez créer des conteneurs pour votre application, votre serveur Web et le client Certbot qui vous permettront d'obtenir vos certificats. En suivant ces étapes, vous pouvez tirer parti de la modularité et de la portabilité d'un workflow conteneurisé.

Dans ce didacticiel, vous allez déployer une application Node.js avec un proxy inverse Nginx à l'aide de Docker Compose. Vous obtiendrez des certificats TLS/SSL pour le domaine associé à votre application et vous assurerez qu'il reçoit une cote de sécurité élevée de SSL Labs. Enfin, vous mettrez en place une tâche cron pour renouveler vos certificats afin que votre domaine reste sécurisé.

Conditions préalables

Pour suivre ce tutoriel, vous aurez besoin de :

  • Un serveur Ubuntu 18.04, un utilisateur non root avec les privilèges sudo et un pare-feu actif. Pour savoir comment les configurer, veuillez consulter ce Guide de configuration initiale du serveur.
  • Docker et Docker Compose installés sur votre serveur. Pour obtenir des conseils sur l'installation de Docker, suivez les étapes 1 et 2 de Comment installer et utiliser Docker sur Ubuntu 18.04. Pour obtenir des conseils sur l'installation de Compose, suivez l'étape 1 de Comment installer Docker Compose sur Ubuntu 18.04.
  • Un nom de domaine enregistré. Ce tutoriel utilisera example.com tout au long. Vous pouvez en obtenir un gratuitement sur Freenom, ou utiliser le bureau d'enregistrement de domaine de votre choix.
  • Les deux enregistrements DNS suivants sont configurés pour votre serveur. Vous pouvez suivre cette introduction à DigitalOcean DNS pour plus de détails sur la façon de les ajouter à un compte DigitalOcean, si c'est ce que vous utilisez :
    • Un enregistrement A avec example.com pointant vers l'adresse IP publique de votre serveur.
    • Un enregistrement A avec www.example.com pointant vers l'adresse IP publique de votre serveur.

Étape 1 - Clonage et test de l'application Node

Dans un premier temps, nous allons cloner le référentiel avec le code de l'application Node, qui inclut le Dockerfile que nous utiliserons pour construire notre image d'application avec Compose. Nous pouvons d'abord tester l'application en la construisant et en l'exécutant avec la commande d'exécution du docker, sans proxy inverse ni SSL.

Dans le répertoire personnel de votre utilisateur non root, clonez le référentiel nodejs-image-demo à partir du compte GitHub de la communauté DigitalOcean. Ce référentiel inclut le code de la configuration décrite dans Comment créer une application Node.js avec Docker.

Clonez le référentiel dans un répertoire appelé node_project :

git clone https://github.com/do-community/nodejs-image-demo.git node_project

Accédez au répertoire node_project :

cd  node_project

Dans ce répertoire, il y a un Dockerfile qui contient des instructions pour construire une application Node en utilisant le Docker node:10 image et le contenu de votre répertoire de projet actuel. Vous pouvez consulter le contenu du Dockerfile en tapant :

cat Dockerfile
OutputFROM node:10-alpine

RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app

WORKDIR /home/node/app

COPY package*.json ./

USER node

RUN npm install

COPY --chown=node:node . .

EXPOSE 8080

CMD [ "node", "app.js" ]

Ces instructions créent une image de nœud en copiant le code du projet du répertoire actuel vers le conteneur et en installant les dépendances avec npm install. Ils tirent également parti de la mise en cache et de la superposition d'images de Docker en séparant la copie de package.json et package-lock.json, contenant les dépendances répertoriées du projet, de la copie du reste de l'application. code. Enfin, les instructions spécifient que le conteneur sera exécuté en tant qu'utilisateur non root node avec les autorisations appropriées définies sur le code d'application et les répertoires node_modules.

Pour plus d'informations sur ces meilleures pratiques Dockerfile et Node image, veuillez consulter la discussion complète dans Étape 3 de Comment créer une application Node.js avec Docker.

Pour tester l'application sans SSL, vous pouvez créer et baliser l'image à l'aide de docker build et de l'indicateur -t. Nous appellerons l'image node-demo, mais vous êtes libre de lui donner un autre nom :

docker build -t node-demo .

Une fois le processus de construction terminé, vous pouvez lister vos images avec images docker :

docker images

Vous verrez le résultat suivant, confirmant la création de l'image de l'application :

OutputREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
node-demo           latest              23961524051d        7 seconds ago       73MB
node                10-alpine           8a752d5af4ce        3 weeks ago         70.7MB

Ensuite, créez le conteneur avec docker run. Nous allons inclure trois drapeaux avec cette commande :

  • -p : Cela publie le port sur le conteneur et le mappe à un port sur notre hôte. Nous utiliserons le port 80 sur l'hôte, mais vous devriez vous sentir libre de le modifier si nécessaire si vous avez un autre processus en cours d'exécution sur ce port. Pour plus d'informations sur la façon dont cela fonctionne, consultez cette discussion dans la documentation Docker sur liaison de port.
  • -d : cela exécute le conteneur en arrière-plan.
  • --name : Cela nous permet de donner au conteneur un nom mémorable.

Exécutez la commande suivante pour créer le conteneur :

docker run --name node-demo -p 80:8080 -d node-demo

Inspectez vos conteneurs en cours d'exécution avec docker ps :

docker ps

Vous verrez une sortie confirmant que votre conteneur d'application est en cours d'exécution :

OutputCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80->8080/tcp   node-demo

Vous pouvez maintenant visiter votre domaine pour tester votre configuration : http://example.com. N'oubliez pas de remplacer example.com par votre propre nom de domaine. Votre application affichera la page de destination suivante :

Maintenant que vous avez testé l'application, vous pouvez arrêter le conteneur et supprimer les images. Utilisez à nouveau docker ps pour obtenir votre CONTAINER ID :

docker ps
OutputCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
4133b72391da        node-demo           "node app.js"       17 seconds ago      Up 16 seconds       0.0.0.0:80->8080/tcp   node-demo

Arrêtez le conteneur avec docker stop. Assurez-vous de remplacer le CONTAINER ID répertorié ici par votre propre application CONTAINER ID :

docker stop 4133b72391da

Vous pouvez maintenant supprimer le conteneur arrêté et toutes les images, y compris les images inutilisées et pendantes, avec docker system prune et le drapeau -a :

docker system prune -a

Tapez y lorsque vous y êtes invité dans la sortie pour confirmer que vous souhaitez supprimer le conteneur et les images arrêtés. Sachez que cela supprimera également votre cache de construction.

Une fois l'image de votre application testée, vous pouvez passer à la construction du reste de votre configuration avec Docker Compose.

Étape 2 - Définition de la configuration du serveur Web

Avec notre application Dockerfile en place, nous pouvons créer un fichier de configuration pour exécuter notre conteneur Nginx. Nous commencerons par une configuration minimale qui inclura notre nom de domaine, racine de document, des informations de proxy et un bloc d'emplacement pour diriger les demandes de Certbot vers le répertoire .well-known, où il placera un temporaire fichier pour valider que le DNS de notre domaine se résout sur notre serveur.

Commencez par créer un répertoire dans le répertoire du projet actuel pour le fichier de configuration :

mkdir nginx-conf

Ouvrez le fichier avec nano ou votre éditeur préféré :

nano nginx-conf/nginx.conf

Ajoutez le bloc de serveur suivant aux requêtes des utilisateurs proxy vers votre conteneur d'application Node et pour diriger les requêtes de Certbot vers le répertoire .well-known. Assurez-vous de remplacer example.com par votre propre nom de domaine :

~/node_project/nginx-conf/nginx.conf

server {
        listen 80;
        listen [::]:80;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name example.com www.example.com;

        location / {
                proxy_pass http://nodejs:8080;
        }

        location ~ /.well-known/acme-challenge {
                allow all;
                root /var/www/html;
        }
}

Ce bloc de serveur nous permettra de démarrer le conteneur Nginx en tant que proxy inverse, qui transmettra les requêtes à notre conteneur d'application Node. Cela nous permettra également d'utiliser le plug-in webroot de Certbot pour obtenir des certificats pour notre domaine. Ce plugin dépend de la méthode de validation HTTP-01, qui utilise une requête HTTP pour prouver que Certbot peut accéder aux ressources d'un serveur qui répond à un nom de domaine donné.

Une fois que vous avez terminé l'édition, enregistrez et fermez le fichier. Pour en savoir plus sur les algorithmes de serveur et de bloc d'emplacement Nginx, veuillez consulter cet article sur Comprendre les algorithmes de sélection de serveur et de bloc d'emplacement Nginx.

Une fois les détails de configuration du serveur Web en place, nous pouvons passer à la création de notre fichier docker-compose.yml, qui nous permettra de créer nos services d'application et le conteneur Certbot que nous utiliserons pour obtenir nos certificats.

Étape 3 - Création du fichier Docker Compose

Le fichier docker-compose.yml définira nos services, y compris l'application Node et le serveur Web. Il spécifiera des détails tels que les volumes nommés, qui seront essentiels pour partager les informations d'identification SSL entre les conteneurs, ainsi que les informations de réseau et de port. Cela nous permettra également de spécifier des commandes spécifiques à exécuter lors de la création de nos conteneurs. Ce fichier est la ressource centrale qui définira comment nos services fonctionneront ensemble.

Ouvrez le fichier dans votre répertoire actuel :

nano docker-compose.yml

Tout d'abord, définissez le service d'application :

~/node_project/docker-compose.yml

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped

La définition de service nodejs comprend les éléments suivants :

  • build : définit les options de configuration, y compris context et dockerfile, qui seront appliquées lorsque Compose créera l'image de l'application. Si vous souhaitez utiliser une image existante d'un registre tel que Docker Hub, vous pouvez utiliser l'instruction d'image à la place, avec des informations sur votre nom d'utilisateur, votre référentiel et votre balise d'image.
  • context : Cela définit le contexte de construction pour la construction de l'image d'application. Dans ce cas, il s'agit du répertoire du projet en cours.
  • dockerfile : Ceci spécifie le Dockerfile que Compose utilisera pour la construction — le Dockerfile que vous avez regardé dans Étape 1.
  • image, container_name : ces noms s'appliquent à l'image et au conteneur.
  • restart : Cela définit la politique de redémarrage. La valeur par défaut est no, mais nous avons configuré le conteneur pour qu'il redémarre à moins qu'il ne soit arrêté.

Notez que nous n'incluons pas les montages liés avec ce service, car notre configuration est axée sur le déploiement plutôt que sur le développement. Pour plus d'informations, veuillez consulter la documentation Docker sur les montages liés et volumes.

Pour permettre la communication entre les conteneurs d'application et de serveur Web, nous ajouterons également un réseau de pont appelé app-network sous la définition de redémarrage :

~/node_project/docker-compose.yml

services:
  nodejs:
...
    networks:
      - app-network

Un réseau de pont défini par l'utilisateur comme celui-ci permet la communication entre les conteneurs sur le même hôte de démon Docker. Cela rationalise le trafic et la communication au sein de votre application, car il ouvre tous les ports entre les conteneurs sur le même réseau de ponts, tout en n'exposant aucun port au monde extérieur. Ainsi, vous pouvez choisir d'ouvrir uniquement les ports dont vous avez besoin pour exposer vos services frontaux.

Définissez ensuite le service webserver :

~/node_project/docker-compose.yml

...
 webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

Certains des paramètres que nous avons définis pour le service nodejs restent les mêmes, mais nous avons également apporté les modifications suivantes :

  • image : Cela indique à Compose d'extraire la dernière image Alpine Nginx de Docker Hub. Pour plus d'informations sur les images alpine, veuillez consulter l'étape 3 de Comment créer une application Node.js avec Docker.
  • ports : cela expose le port 80 pour activer les options de configuration que nous avons définies dans notre configuration Nginx.

Nous avons également spécifié les volumes nommés et les montages liés suivants :

  • web-root:/var/www/html : cela ajoutera les actifs statiques de notre site, copiés sur un volume appelé web-root, au répertoire /var/www/html sur le conteneur.
  • ./nginx-conf:/etc/nginx/conf.d : Cela liera le montage du répertoire de configuration Nginx sur l'hôte au répertoire approprié sur le conteneur, garantissant que toutes les modifications que nous apportons aux fichiers sur l'hôte seront reflétées dans le conteneur.
  • certbot-etc:/etc/letsencrypt : Cela montera les certificats et les clés Let's Encrypt pertinents pour notre domaine dans le répertoire approprié sur le conteneur.
  • certbot-var:/var/lib/letsencrypt : Cela monte le répertoire de travail par défaut de Let's Encrypt dans le répertoire approprié sur le conteneur.

Ensuite, ajoutez les options de configuration pour le conteneur certbot. Assurez-vous de remplacer les informations de domaine et d'e-mail par votre propre nom de domaine et votre adresse e-mail :

~/node_project/docker-compose.yml

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --staging -d example.com  -d www.example.com 

Cette définition indique à Compose d'extraire l'image certbot/certbot de Docker Hub. Il utilise également des volumes nommés pour partager des ressources avec le conteneur Nginx, y compris les certificats de domaine et la clé dans certbot-etc, le répertoire de travail Let's Encrypt dans certbot-var et le code d'application dans web-root. ].

Encore une fois, nous avons utilisé depends_on pour spécifier que le conteneur certbot doit être démarré une fois que le service webserver est en cours d'exécution.

Nous avons également inclus une option command qui spécifie la commande à exécuter au démarrage du conteneur. Elle inclut la sous-commande certonly avec les options suivantes :

  • --webroot : Cela indique à Certbot d'utiliser le plug-in Webroot pour placer les fichiers dans le dossier Webroot pour l'authentification.
  • --webroot-path : Ceci spécifie le chemin du répertoire webroot.
  • --email : votre e-mail préféré pour l'enregistrement et la récupération.
  • --agree-tos : ceci indique que vous acceptez le contrat d'abonnement de ACME.
  • --no-eff-email : Cela indique à Certbot que vous ne souhaitez pas partager votre e-mail avec Electronic Frontier Foundation (EFF). N'hésitez pas à l'omettre si vous préférez.
  • --staging : cela indique à Certbot que vous souhaitez utiliser l'environnement intermédiaire de Let's Encrypt pour obtenir des certificats de test. L'utilisation de cette option vous permet de tester vos options de configuration et d'éviter d'éventuelles limites de demande de domaine. Pour plus d'informations sur ces limites, veuillez consulter la documentation sur les limites de débit de Let's Encrypt.
  • -d : Cela vous permet de spécifier les noms de domaine que vous souhaitez appliquer à votre demande. Dans ce cas, nous avons inclus example.com et www.example.com. Assurez-vous de les remplacer par vos propres préférences de domaine.

Enfin, ajoutez les définitions de volume et de réseau. Assurez-vous de remplacer le nom d'utilisateur ici par votre propre utilisateur non root :

~/node_project/docker-compose.yml

...
volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge

Nos volumes nommés incluent nos volumes de certificat Certbot et de répertoire de travail, ainsi que le volume des actifs statiques de notre site, web-root. Dans la plupart des cas, le pilote par défaut pour les volumes Docker est le pilote local, qui sous Linux accepte des options similaires à la commande de montage. Grâce à cela, nous sommes en mesure de spécifier une liste d'options de pilote avec driver_opts qui montent le répertoire views sur l'hôte, qui contient les actifs statiques de notre application, sur le volume au moment de l'exécution. Le contenu du répertoire peut ensuite être partagé entre les conteneurs. Pour plus d'informations sur le contenu du répertoire views, veuillez consulter Étape 2 de Comment créer une application Node.js avec Docker.

Le fichier docker-compose.yml ressemblera à ceci une fois terminé :

~/node_project/docker-compose.yml

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    networks:
      - app-network

  webserver:
    image: nginx:mainline-alpine
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
    depends_on:
      - nodejs
    networks:
      - app-network

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --staging -d example.com  -d www.example.com 

volumes:
  certbot-etc:
  certbot-var:
  web-root:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/views/
      o: bind

networks:
  app-network:
    driver: bridge  

Une fois les définitions de service en place, vous êtes prêt à démarrer les conteneurs et à tester vos demandes de certificat.

Étape 4 - Obtention de certificats SSL et d'informations d'identification

Nous pouvons démarrer nos conteneurs avec docker-compose up, qui créera et exécutera nos conteneurs et services dans l'ordre que nous avons spécifié. Si nos demandes de domaine aboutissent, nous verrons le statut de sortie correct dans notre sortie et les bons certificats montés dans le dossier /etc/letsencrypt/live sur le conteneur webserver.

Créez les services avec docker-compose up et le drapeau -d, qui exécuteront les conteneurs nodejs et webserver en arrière-plan :

docker-compose up -d

Vous verrez une sortie confirmant que vos services ont été créés :

OutputCreating nodejs ... done
Creating webserver ... done
Creating certbot   ... done

À l'aide de docker-compose ps, vérifiez l'état de vos services :

docker-compose ps

Si tout a réussi, vos services nodejs et webserver devraient être Up et le conteneur certbot sera sorti avec un message d'état 0 :

Output  Name                 Command               State          Ports
------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:80->80/tcp

Si vous voyez autre chose que Up dans la colonne State pour les services nodejs et webserver, ou un état de sortie autre que 0 pour le conteneur certbot, assurez-vous de vérifier les journaux de service avec la commande docker-compose logs :

docker-compose logs service_name

Vous pouvez maintenant vérifier que vos informations d'identification ont été montées sur le conteneur webserver avec docker-compose exec :

docker-compose exec webserver ls -la /etc/letsencrypt/live

Si votre demande a abouti, vous verrez une sortie comme celle-ci :

Outputtotal 16
drwx------ 3 root root 4096 Dec 23 16:48 .
drwxr-xr-x 9 root root 4096 Dec 23 16:48 ..
-rw-r--r-- 1 root root  740 Dec 23 16:48 README
drwxr-xr-x 2 root root 4096 Dec 23 16:48 example.com

Maintenant que vous savez que votre demande aboutira, vous pouvez modifier la définition de service certbot pour supprimer l'indicateur --staging.

Ouvrez docker-compose.yml :

nano docker-compose.yml

Trouvez la section du fichier avec la définition de service certbot et remplacez le drapeau --staging dans l'option command par le drapeau --force-renewal, qui indiquera à Certbot que vous souhaitez demander un nouveau certificat avec les mêmes domaines qu'un certificat existant. La définition du service certbot devrait maintenant ressembler à ceci :

~/node_project/docker-compose.yml

...
  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - web-root:/var/www/html
    depends_on:
      - webserver
    command: certonly --webroot --webroot-path=/var/www/html --email [email protected] --agree-tos --no-eff-email --force-renewal -d example.com -d www.example.com
...

Vous pouvez maintenant exécuter docker-compose up pour recréer le conteneur certbot et ses volumes pertinents. Nous inclurons également l'option --no-deps pour indiquer à Compose qu'il peut ignorer le démarrage du service webserver, puisqu'il est déjà en cours d'exécution :

docker-compose up --force-recreate --no-deps certbot

Vous verrez une sortie indiquant que votre demande de certificat a réussi :

Outputcertbot      | IMPORTANT NOTES:
certbot      |  - Congratulations! Your certificate and chain have been saved at:
certbot      |    /etc/letsencrypt/live/example.com/fullchain.pem
certbot      |    Your key file has been saved at:
certbot      |    /etc/letsencrypt/live/example.com/privkey.pem
certbot      |    Your cert will expire on 2019-03-26. To obtain a new or tweaked
certbot      |    version of this certificate in the future, simply run certbot
certbot      |    again. To non-interactively renew *all* of your certificates, run
certbot      |    "certbot renew"
certbot      |  - Your account credentials have been saved in your Certbot
certbot      |    configuration directory at /etc/letsencrypt. You should make a
certbot      |    secure backup of this folder now. This configuration directory will
certbot      |    also contain certificates and private keys obtained by Certbot so
certbot      |    making regular backups of this folder is ideal.
certbot      |  - If you like Certbot, please consider supporting our work by:
certbot      |
certbot      |    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
certbot      |    Donating to EFF:                    https://eff.org/donate-le
certbot      |
certbot exited with code 0

Une fois vos certificats en place, vous pouvez passer à la modification de votre configuration Nginx pour inclure SSL.

Étape 5 - Modification de la configuration du serveur Web et de la définition de service

L'activation de SSL dans notre configuration Nginx impliquera l'ajout d'une redirection HTTP vers HTTPS et la spécification de notre certificat SSL et des emplacements clés. Cela impliquera également de spécifier notre groupe Diffie-Hellman, que nous utiliserons pour Perfect Forward Secrecy.

Puisque vous allez recréer le service webserver pour inclure ces ajouts, vous pouvez l'arrêter maintenant :

docker-compose stop webserver

Ensuite, créez un répertoire dans votre répertoire de projet actuel pour votre clé Diffie-Hellman :

mkdir dhparam

Générez votre clé avec la commande openssl :

sudo openssl dhparam -out /home/sammy/node_project/dhparam/dhparam-2048.pem 2048

La génération de la clé prendra quelques instants.

Pour ajouter les informations Diffie-Hellman et SSL pertinentes à votre configuration Nginx, supprimez d'abord le fichier de configuration Nginx que vous avez créé précédemment :

rm nginx-conf/nginx.conf

Ouvrez une autre version du fichier :

nano nginx-conf/nginx.conf

Ajoutez le code suivant au fichier pour rediriger HTTP vers HTTPS et pour ajouter des informations d'identification SSL, des protocoles et des en-têtes de sécurité. N'oubliez pas de remplacer example.com par votre propre domaine :

~/node_project/nginx-conf/nginx.conf

server {
        listen 80;
        listen [::]:80;
        server_name example.com www.example.com;

        location ~ /.well-known/acme-challenge {
          allow all;
          root /var/www/html;
        }

        location / {
                rewrite ^ https://$host$request_uri? permanent;
        }
}

server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;

        server_tokens off;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        ssl_buffer_size 8k;

        ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

        ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
        ssl_prefer_server_ciphers on;

        ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

        ssl_ecdh_curve secp384r1;
        ssl_session_tickets off;

        ssl_stapling on;
        ssl_stapling_verify on;
        resolver 8.8.8.8;

        location / {
                try_files $uri @nodejs;
        }

        location @nodejs {
                proxy_pass http://nodejs:8080;
                add_header X-Frame-Options "SAMEORIGIN" always;
                add_header X-XSS-Protection "1; mode=block" always;
                add_header X-Content-Type-Options "nosniff" always;
                add_header Referrer-Policy "no-referrer-when-downgrade" always;
                add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;
                # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
                # enable strict transport security only if you understand the implications
        }

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
}

Le bloc de serveur HTTP spécifie la racine Web pour les demandes de renouvellement Certbot dans le répertoire .well-known/acme-challenge. Il inclut également une directive de réécriture qui dirige les requêtes HTTP vers le répertoire racine vers HTTPS.

Le bloc de serveur HTTPS active ssl et http2. Pour en savoir plus sur la façon dont HTTP/2 itère sur les protocoles HTTP et les avantages qu'il peut avoir pour les performances du site Web, veuillez consulter l'introduction de Comment configurer Nginx avec la prise en charge HTTP/2 sur Ubuntu 18.04. Ce bloc comprend également une série d'options pour vous assurer que vous utilisez les protocoles et chiffrements SSL les plus récents et que l'agrafage OSCP est activé. L'agrafage OSCP vous permet d'offrir une réponse horodatée de votre autorité de certification lors de la prise de contact initiale TLS, ce qui peut accélérer le processus d'authentification.

Le bloc spécifie également vos informations d'identification SSL et Diffie-Hellman et les emplacements des clés.

Enfin, nous avons déplacé les informations de passe proxy vers ce bloc, y compris un bloc d'emplacement avec une directive try_files, pointant les demandes vers notre conteneur d'application Node.js alias, et un bloc d'emplacement pour cet alias, qui comprend des en-têtes de sécurité qui nous permettront d'obtenir des notes A sur des choses comme les sites de test de serveur SSL Labs et Security Headers. Ces en-têtes incluent X-Frame-Options, X-Content-Type-Options, Referrer Policy, Content-Security-Policy, et X-XSS-Protection. L'en-tête HTTP Strict Transport Security (HSTS) est commenté - activez-le uniquement si vous comprenez les implications et avez évalué sa fonctionnalité "preload".

Une fois que vous avez terminé l'édition, enregistrez et fermez le fichier.

Avant de recréer le service webserver, vous devrez ajouter quelques éléments à la définition de service dans votre fichier docker-compose.yml, y compris les informations de port pertinentes pour HTTPS et une définition de volume Diffie-Hellman.

Ouvrez le fichier :

nano docker-compose.yml

Dans la définition de service webserver, ajoutez le mappage de port suivant et le volume nommé dhparam :

~/node_project/docker-compose.yml

...
 webserver:
    image: nginx:latest
    container_name: webserver
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - web-root:/var/www/html
      - ./nginx-conf:/etc/nginx/conf.d
      - certbot-etc:/etc/letsencrypt
      - certbot-var:/var/lib/letsencrypt
      - dhparam:/etc/ssl/certs
    depends_on:
      - nodejs
    networks:
      - app-network

Ensuite, ajoutez le volume dhparam à vos définitions volumes :

~/node_project/docker-compose.yml

...
volumes:
  ...
  dhparam:
    driver: local
    driver_opts:
      type: none
      device: /home/sammy/node_project/dhparam/
      o: bind

Comme pour le volume web-root, le volume dhparam montera la clé Diffie-Hellman stockée sur l'hôte dans le conteneur webserver.

Enregistrez et fermez le fichier lorsque vous avez terminé l'édition.

Recréez le service webserver :

docker-compose up -d --force-recreate --no-deps webserver

Vérifiez vos services avec docker-compose ps :

docker-compose ps

Vous devriez voir une sortie indiquant que vos services nodejs et webserver sont en cours d'exécution :

Output  Name                 Command               State                     Ports
----------------------------------------------------------------------------------------------
certbot     certbot certonly --webroot ...   Exit 0
nodejs      node app.js                      Up       8080/tcp
webserver   nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

Enfin, vous pouvez visiter votre domaine pour vous assurer que tout fonctionne comme prévu. Naviguez dans votre navigateur vers https://example.com, en veillant à remplacer example.com par votre propre nom de domaine. Vous verrez la page de destination suivante :

Vous devriez également voir l'icône de verrouillage dans l'indicateur de sécurité de votre navigateur. Si vous le souhaitez, vous pouvez accéder à la page de destination du test du serveur SSL Labs ou à la page de destination du test du serveur Security Headers. Les options de configuration que nous avons incluses devraient mériter à votre site une note A sur les deux.

Étape 6 — Renouvellement des certificats

Les certificats Let's Encrypt sont valides pendant 90 jours, vous voudrez donc mettre en place un processus de renouvellement automatisé pour vous assurer qu'ils n'expirent pas. Une façon de procéder consiste à créer une tâche avec l'utilitaire de planification cron. Dans ce cas, nous allons planifier une tâche cron à l'aide d'un script qui renouvellera nos certificats et rechargera notre configuration Nginx.

Ouvrez un script appelé ssl_renew.sh dans votre répertoire de projet :

nano ssl_renew.sh

Ajoutez le code suivant au script pour renouveler vos certificats et recharger la configuration de votre serveur Web :

~/node_project/ssl_renew.sh

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew --dry-run && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Ce script affecte d'abord le binaire docker-compose à une variable appelée COMPOSE, et spécifie l'option --no-ansi, qui exécutera les commandes docker-compose sans contrôle ANSI caractères. Il fait ensuite la même chose avec le binaire docker. Enfin, il passe au répertoire ~/node_project et exécute les commandes docker-compose suivantes :

  • docker-compose run : Cela démarrera un conteneur certbot et remplacera le command fourni dans notre définition de service certbot. Au lieu d'utiliser la sous-commande certonly, nous utilisons ici la sous-commande renew, qui renouvellera les certificats qui sont sur le point d'expirer. Nous avons inclus l'option --dry-run ici pour tester notre script.
  • docker-compose kill : cela enverra un signal SIGHUP au conteneur webserver pour recharger la configuration Nginx. Pour plus d'informations sur l'utilisation de ce processus pour recharger votre configuration Nginx, veuillez consulter ce billet de blog Docker sur le déploiement de l'image Nginx officielle avec Docker.

Il exécute ensuite docker system prune pour supprimer tous les conteneurs et images inutilisés.

Fermez le fichier lorsque vous avez terminé l'édition. Rendez-le exécutable :

chmod +x ssl_renew.sh

Ensuite, ouvrez votre fichier root crontab pour exécuter le script de renouvellement à un intervalle spécifié :

sudo crontab -e 

Si c'est la première fois que vous modifiez ce fichier, il vous sera demandé de choisir un éditeur :

crontab

no crontab for root - using an empty one
Select an editor.  To change later, run 'select-editor'.
  1. /bin/ed
  2. /bin/nano        <---- easiest
  3. /usr/bin/vim.basic
  4. /usr/bin/vim.tiny
Choose 1-4 [2]: 
...

En bas du fichier, ajoutez la ligne suivante :

crontab

...
*/5 * * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

Cela définira l'intervalle de travail à toutes les cinq minutes, afin que vous puissiez tester si votre demande de renouvellement a fonctionné ou non comme prévu. Nous avons également créé un fichier journal, cron.log, pour enregistrer la sortie pertinente de la tâche.

Après cinq minutes, vérifiez cron.log pour voir si la demande de renouvellement a réussi ou non :

tail -f /var/log/cron.log

Vous devriez voir une sortie confirmant un renouvellement réussi :

Output- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Killing webserver ... done

Vous pouvez maintenant modifier le fichier crontab pour définir un intervalle quotidien. Pour exécuter le script tous les jours à midi, par exemple, vous modifieriez la dernière ligne du fichier pour qu'elle ressemble à ceci :

crontab

...
0 12 * * * /home/sammy/node_project/ssl_renew.sh >> /var/log/cron.log 2>&1

Vous voudrez également supprimer l'option --dry-run de votre script ssl_renew.sh :

~/node_project/ssl_renew.sh

#!/bin/bash

COMPOSE="/usr/local/bin/docker-compose --no-ansi"
DOCKER="/usr/bin/docker"

cd /home/sammy/node_project/
$COMPOSE run certbot renew && $COMPOSE kill -s SIGHUP webserver
$DOCKER system prune -af

Votre travail cron garantira que vos certificats Let's Encrypt n'arrivent pas à expiration en les renouvelant lorsqu'ils sont éligibles. Vous pouvez également configurer la rotation des journaux avec l'utilitaire Logrotate pour faire pivoter et compresser vos fichiers journaux.

Conclusion

Vous avez utilisé des conteneurs pour configurer et exécuter une application Node avec un proxy inverse Nginx. Vous avez également sécurisé des certificats SSL pour le domaine de votre application et configuré une tâche cron pour renouveler ces certificats si nécessaire.

Si vous souhaitez en savoir plus sur les plugins Let's Encrypt, veuillez consulter nos articles sur l'utilisation du plugin Nginx ou du plugin autonome.

Vous pouvez également en savoir plus sur Docker Compose en consultant les ressources suivantes :

La documentation Compose est également une excellente ressource pour en savoir plus sur les applications multi-conteneurs.