HTTPS
La plupart des navigateurs aujourd'hui exigent, en dehors d'un environnement de développement (sur localhost
), une connexion sécurisée par SSL (https).
Nous allons sécuriser notre API en utilisant un reverse proxy :
On utilise un logiciel de serveur web (ex. nginx ou apache) qui fait déjà très bien la tache de gérer des connexions https. Ce serveur va accepter des connexions entrantes sur le protocole https (normalement le port 443), les déchiffrer, et les rediriger vers notre API.
Notre API NodeJS va écouter sur un port sur notre réseau interne (pas accessible au public). Il accepte les connexions de notre serveur Apache.
Vous trouverez le projet fonctionnel de ce chapitre ici
Un reverse proxy en local
Commençons par tester l'utilisation de nginx et cette notion de reverse-proxy en local.
Nous allons utiliser nginx pour notre exemple, en l'ajoutant à notre docker-compose.dev.yml
:
On ouvre des ports 80 (http) et 443 (https), et on monte un fichier de configuration de nginx qui se trouvera dans notre projet sous nginx/api-dev.nginx.conf
:
Cette configuration est la plus simple possible pour un reverse proxy sur nginx. Nous précisons le port d'écoute et le nom d'hôte du serveur.
Ensuite on précise un ou plusieurs location
, un chemin (URL) local, qui doit être traité par nginx. Ici, on précise que toutes les requêtes entrantes sur le chemin /
(donc toutes les requêtes) doivent être redirigées vers notre api, sur le port 5050.
Notez l'utilisation du nom d'hôte vscode_api
, qui est le nom du service dans notre Dev Container. Docker est capable de convertir ce nom en adresse IP.
Redémarrer votre Dev Container (F1, puis reconstruire le container).
Si tout se passe bien, vous pourriez modifier votre Postman, en utilisant plutôt le port 80 (ou en enlevant complètement le port). La requête passera d'abord par nginx avant d'être redirigé vers notre API.
Critères d'une connexion SSL
Afin d'établir une connexion SSL, il nous faudrait un certificat SSL qui nous identifie, et un tiers qui va valider notre identité (comme discuté en classe lors de notre cours sur Unix SHELL).
Certains services payants pourraient nous fournir ce certificat (gandi, thawte, verisign, etc.), après avoir validé notre identité.
Une solution gratuite existe qui s'appelle certbot
. L'idée est le suivant :
nous mettons en place un serveur web
nous faisons pointer une entrée DNS vers notre serveur
le fait de pouvoir configurer le DNS prouve notre identité
certbot lance une procédure de validation :
un secret est mis à disposition sur notre serveur
les serveurs de certbot essaye de récupérer le secret en utilisant notre nom d'hôte saisie dur le DNS
si certbot arrive à le faire, cela prouve qu'on est admin de le DNS et admin du serveur. Certbot en a assez pour nous créer et valider un certificat
Afin d'utiliser certbot alors, on a besoin de plusieurs ingrédients :
un serveur fonctionnel et accessible sur Internet
un nom de domaine configuré (payant)
Un serveur
Pour cela, je vais utiliser une instance louée sur Scaleway.
Je commence par utiliser un simple docker-compose.stage.setup.yml
, une configuration temporaire qui lance un serveur nginx, et qui contient le service certbot aussi :
Notez qu'on utilise une configuration différente de nginx cette fois-ci, à nginx/api-https-setup.nginx.conf
:
Le nom de serveur, server_name
, doit être le même nom qu'on va configurer chez notre fournisseur de DNS. À voir dans quelques instants.
On crée une location
utilisé par certbot (/.well-known/acme-challenge
) pour servir le secret qu'il va créer pour tester notre domaine.
Notez donc, qu'on a créé quelques volumes pour le partage des fichiers de configuration et secrets entre nginx et certbot:
./nginx/certbot/www
: certbot va créer le secret dans ce dossier, et nginx va servir ce secret à la demande des serveurs certbot./nginx/certbot/conf/
: après la validation, certbot va créer et sauvegarder nos certificats à cet emplacement
Nous allons copier cette configuration sur notre instance accessible sur Internet.
Après avoir installé Docker (si pas encore fait), on lance la configuration :
Le serveur nginx se lance, et écoute sur le port 80.
Un nom de domaine
Cette partie est malheureusement payante, vous pouvez le faire si vous avez déjà un nom de domaine. N'importe quel fournisseur des services web est capable d'en commander pour vous (OVH, Scaleway, Gandi, ... )
Vous accédez à la configuration de la zone DNS, et vous ajoutez une ligne de type A
qui point vers votre instance :
Création des certificats
Nous avons tous ce qu'il faut pour lancer la procédure de création de certificats par certbot. Nous allons lancer ponctuellement le service certbot
de notre configuration docker-compose.stage.setup.yml
, en le demandant de configurer notre nom de domaine (ici test.api.hetic.glassworks.tech
) :
On répond à toutes les questions, et si tout va bien, on termine avec des certificats dans ./nginx/certbot/conf/
.
Super ! Maintenant, on va arrêter notre environnement temporaire, et réutiliser ces certificats dans notre environnement de production.
Environnement de production
Nous n'avons pas encore parlé du déploiement de notre API. Aujourd'hui, on n'a qu'un environnement de développement, dans lequel on lance notre API manuellement.
On va s'occuper d'un build de notre API et son lancement comme un service dans notre docker-compose.stage.yml
.
Transpilation
Nous aimerions construire une version finale de notre API, déjà transpilée en JS, et avec le minimum de dépendances nécessaires pour exécuter.
Pour cela, nous allons ajouter quelques lignes dans notre package.json
:
La commande tsc
effectue définitivement le build de notre API, et enregistre les fichiers en .js pur dans le dossier ./build
(cet emplacement est précisé dans tsconfig.json
).
Le script clean
vide cet emplacement, pour être certain qu'on a un build propre à chaque fois. Il dépend d'une petite librairie rimraf
qu'il faut installer avec :
Le script start-api
sera utilisé après le build, et lancera le javascript résultant du build. Vous remarquerez que l'API commence plus vite avec cette commande (après le build) car la transpilation de Typescript a déjà été faite.
Essayez ces 3 scripts - tout devrait fonctionner en local !
Construire une image Docker
Ensuite, nous allons créer une image Docker qui permet d'être lancé comme un service. Pour cela, on aura un fichier docker/Dockerfile.stage
:
Ce Dockerfile contient les instructions de build d'une image Docker. Il se compose de deux étapes. Une première étape crée une image dans lequel on fait notre transpilation. Notez qu'on exécute les commandes clean
et run
dans cette phase.
Dans la deuxième phase on copie simplement l'artéfact de build de l'étape précédent (le dossier build
) dans une nouvelle image, et on n'installe que les dépendances nécessaires pour l'exécution (npm install --omit-dev
).
Environnement de production
Nous sommes prêts maintenant à créer un docker-compose.stage.yml
qui lance notre SGBDR, notre API (déjà construit), notre serveur nginx, et certbot.
Notez le suivant :
pour le service
api
, on a précisé leDockerfile.stage
qu'on a créé dans l'étape précédentepour le service
nginx
etcertbot
, on a précisé les mêmes emplacements que dans l'étape de configuration de nos certificats
Enfin, la configuration de nginx a changé aussi, car maintenant, on écoute sur le port 443 et on utilise des certificats :
Maintenant, on a deux serveurs :
un serveur http (80) qui sert uniquement à rediriger (via un code 301) la requête vers le port 443
le serveur https (443) qui utilise les certificats pour établir une connexion https, et redirige les requêtes vers notre API.
On est prêt à lancer notre environnement sur notre instance :
Tester avec Postman maintenant, en utilisant plutôt votre nom d'hôte, par exemple :
Vous aurez probablement une erreur du fait que votre API n'a pas le droit d'accéder à la base de données. C'est normal, car ici, on n'a pas parlé de l'initialisation de la base en production.
Ce qui compte, est qu'on a réussi à contacter notre API via https !
Renouvellement des certificats
Un certificat certbot expire après 3 mois. Il est facile de renouveler les certificats via notre configuration avec :
On pourrait, par exemple, créer une ligne dans le CRON pour le faire régulièrement.
Dernière mise à jour