Depuis maintenant plusieurs années, j’utilise le nom de domaine mickaelgillot.xyz qui pointe vers plusieurs services hébergés sur un VPS chez Gandi.net, dont notamment le blog que vous êtes en train de lire. Mais récemment, j’ai également réservé d’autres domaines tels que mickaelgillot.fr et mickaelgillot.com ; principalement pour me prévenir de cyber-squat. Je me suis donc naturellement dit que je devrais rediriger ces domaines vers mon domaine principal ; quitte à les payer autant qu’ils servent à quelque chose. Mais je ne voulais pas simplement rediriger vers le domaine nu (naked domain (en) : domaine de deuxième niveau, sans préfixe) sans tenir compte de la requête ou du préfixe. Je voulais créer une totale isométrie entre ces domaines, de sorte que, quels que soient le sous-domaine et les paramètres de la requête, je redirige systématiquement vers son équivalent avec le TLD .xyz (top-level domain (en) : domaine de premier niveau). Par exemple, toto.mickaelgillot.fr/tata?foo=bar doit rediriger vers toto.mickaelgillot.xyz/tata?foo=bar. Et bien sûr, une requête HTTP doit systématiquement être redirigée vers la même requête en HTTPS. Aujourd’hui, c’est chose faite pour ma part et je vous propose de vous expliquer comment je m’y suis pris afin de vous permettre de le reproduire pour vos domaines.
Certificat TLS et challenge HTTP
D’abord on va reprendre quelques bases pour permettre à tout le monde de comprendre et de réaliser ce tutoriel, quel que soit votre point de départ. Toutefois, je pars du principe que vous avez déjà un site web servi par un serveur web nginx en HTTP en écoute sur le port 80. On va donc commencer par rediriger les requêtes HTTP (non sécurisé) vers les mêmes requêtes en HTTPS (sécurisé), et pour ça je vais commencer par vous expliquer ce qu’est un certificat TLS, en quoi consiste un challenge HTTP et comment se procurer un certificat signé par Let’s Encrypt avec certbot pour enfin configurer la redirection HTTP vers HTTPS dans votre nginx. Si tout cela est déjà clair pour vous, n’hésitez pas à passer à la partie suivante qui traite de notre sujet principal.
Un certificat TLS (anciennement SSL) est un fichier permettant de certifier l’identité d’un interlocuteur, ici le serveur web qui transmet les données (texte, média, ressource, etc) à afficher au client web (le navigateur web). Ainsi, on peut être certain que le serveur répondant au nom de domaine mickaelgillot.xyz
est bien mon VPS hébergé chez Gandi, et pas une tierce personne qui aurait usurpé mon identité (attaque du milieu, man-in-the-middle). Pour être sûr de ça, il a fallu que le certificat soit signé par une autorité de certification (AC) qui s’est préalablement assuré que ce domaine pointe bien vers la bonne machine. Et pour prouver cela, il existe plusieurs méthodes.
Pour s’assurer que la personne qui demande le certificat pour un nom de domaine est bien la même qui contrôle la machine nommée par celui-ci, une méthode possible est le challenge HTTP. L’autorité de certification (AC) génère un fichier unique qui devra être déposé sur un serveur web et accessible publiquement depuis le nom de domaine demandé. Si l’AC constate la présence de ce fichier, c’est que la personne qui commande ce certificat est bien propriétaire de la machine qui héberge le serveur web. Le challenge HTTP est réussi. L’AC fournit alors un certificat TLS qu’elle aura signé et qui sera distribué par le serveur web à chaque requête HTTPS vers ce domaine, assurant aux clients que cette machine est bien celle qu’elle prétend être. Le protocole HTTPS permet alors d’établir une connexion chiffrée entre le client et le serveur, rendant illisibles pour autrui les communications entre les deux.
Heureusement pour nous, il existe désormais une autorité de certification qui délivre des certificats TLS signés gratuitement : Let’s Encrypt ; et un outil bien pratique qui s’occupe de toutes les démarches pour commander le certificat auprès de l’AC et de réaliser le challenge HTTP : certbot.
Commander un certificat TLS avec certbot
D’abord, il faut installer certbot. Pour cela, le plus simple reste d’utiliser le gestionnaire de paquets de votre distribution. Pour l’exemple, j’utilise APT sur une Debian 11 Bullseye. Si ça n’est pas votre cas, cherchez simplement un paquet nommé certbot.
$ sudo apt update && sudo apt install certbot
Une fois certbot installé, il suffit de commander un certificat TLS pour notre nom de domaine nu. Je prendrai comme exemple le domaine example.org
. On va également préciser d’utiliser notre serveur nginx qui doit déjà écouter sur le port 80, afin de réaliser le challenge HTTP.
$ sudo certbot --nginx -d example.org
À partir de cette commande, certbot va examiner le fichier de configuration nginx du site pointé par ce domaine, commander le certificat TLS, réaliser le challenge HTTP puis enfin vous proposer d’éditer le fichier de configuration pour rediriger les requêtes HTTP vers le HTTPS. Je vous recommande d’accepter. On pourrait évidemment le faire nous-mêmes, mais il se trouve que certbot fait très bien le travail la plupart du temps, sauf configuration nginx trop particulière.
Dans notre cas, votre fichier de configuration devrait ressembler à ça. Nous allons décortiquer un peu ce que tout ça signifie.
server {
server_name example.org;
location / {
# [Des lignes de conf spécifiques à votre site...]
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = example.org) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name example.org;
listen [::]:80;
listen 80;
return 404; # managed by Certbot
}
On repère ici deux blocs server qui correspondent à deux sites web. Le premier écoute sur le port 443 (HTTPS), en IPv4 et IPv6, et sur le domaine example.org
. Les lignes qui suivent renseignent sur le certificat TLS à fournir au client qui tenterait d’établir une connexion suite à une requête HTTPS.
Le second site écoute sur le port 80 (HTTP), également en IPv4 et IPv6, et sur le même domaine example.org
. Seulement, par défaut, le site renvoie un code 404 (Not found). En revanche, si le nom de domaine est bien example.org
, le serveur nginx indique une redirection permanente (301) vers https://$host$request_uri
. Ici $host
correspond au nom de domaine et $request_uri
à la requête faite au serveur web. Ainsi, le client sera bien redirigé vers le site HTTPS avec la même requête, en servant la même ressource demandée, en toute transparence.
Vous pouvez tester votre configuration nginx avec la commande suivante :
$ sudo nginx -t
Et si tout va bien, vous pouvez recharger votre nouvelle configuration nginx :
$ sudo systemctl reload nginx
Voilà ! Vous pouvez maintenant constater que tout fonctionne en vous rendant sur http://example.org
. Vous serez automatiquement redirigé vers l’URL https://example.org
.
Rediriger un domaine vers un autre
À partir de là, si vous avez compris les étapes précédentes, ce qui va suivre ne devrait pas vous poser de problèmes. Nous souhaitons commencer à rediriger un autre domaine en notre possession – disons example.com
– vers notre domaine canonique example.org
. Nous allons d’abord créer une configuration nginx pour créer un site web qui redirigera les requêtes HTTP de example.com
vers le HTTPS de example.org
. Ensuite, nous commanderons un certificat TLS en utilisant certbot et modifierons la configuration nginx pour rediriger les requêtes HTTP sur example.com
vers le HTTPS sur example.com
, qui lui-même redirigera vers le HTTPS sur example.org
.
On commence donc pas créer un site minimal en HTTP qui redirige toutes les requêtes vers notre hôte canonique en préservant le corps de la requête. Pour ça, créez un nouveau fichier de configuration séparé qui devrait ressembler à ça :
server {
server_name example.com;
listen [::]:80;
listen 80;
return 301 https://example.org$request_uri;
}
Comme vous pouvez le voir, c’est très succinct. On a repris l’instruction redirect
en changeant simplement $host
par example.org
. Encore une fois, testez votre configuration avant de recharger cette nouvelle configuration.
Maintenant, il ne reste plus qu’à commander un certificat TLS pour example.com
avec certbot, comme nous l’avons fait précédemment pour example.org
.
$ sudo certbot --nginx -d example.com
Comme précédemment, certbot vous proposera d’éditer la configuration nginx pour rediriger les requêtes HTTP de ce site vers leurs équivalents en HTTPS. De nouveau, il est recommandé d’accepter. Voici à quoi devrait finalement ressembler la configuration nginx de example.com :
server {
server_name example.com;
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
return 301 https://example.org$request_uri;
}
server {
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name example.com;
listen [::]:80;
listen 80;
return 404; # managed by Certbot
}
Pour confirmer et appliquer ces modifications, testez et rechargez la configuration nginx. Vous pouvez alors constater avec votre navigateur web qu’en consultant http://example.com
, vous êtes redirigé vers https://example.org
.
Commander un certificat TLS wildcard avec certbot et LiveDNS
Bon, c’est sympa mais c’est pas exactement ce que je vous ai vendu. Si vous êtes encore ici, c’est que vous avez d’autres sites sur des sous-domaines de example.org
et vous aimeriez que les internautes soient redirigés vers ces sites depuis leurs équivalents avec le domaine example.com
. Par exemple, blog.example.com
redirigerait vers blog.example.org
. On pourrait créer autant de configurations nginx pour chacun de ces sites mais primo, ça serait long à faire et compliqué à maintenir et secundo, ça impliquerait de répéter l’opération à chaque nouveau site futur. Une vraie plaie. Personne n’a envie de ça. C’est précisément pour ça qu’on a inventé le certificat wildcard.
Un certificat TLS wildcard permet d’authentifier un domaine et tous ses sous-domaines à partir d’un seul certificat, même ceux qui n’existent pas encore au moment de la commande. Virtuellement, un certificat wildcard peut donc authentifier une infinité de domaines. C’est pour cette raison qu’il n’est pas possible de commander un tel certificat en utilisant un challenge HTTP comme précédemment, car il serait impossible d’inspecter chacun des domaines que pourrait couvrir ce certificat. On utilise alors un autre type de challenge : le challenge DNS.
Contrairement au challenge HTTP où l’autorité de certification vérifiait la propriété d’une machine pointée par le domaine demandé, c’est directement la propriété du domaine que l’AC va vérifier avec un challenge DNS. Pour prouver que l’on détient bien le domaine, l’AC fournit alors une clé générée aléatoirement et demande au propriétaire du domaine d’éditer sa zone DNS pour ajouter un enregistrement de type TXT avec comme valeur la clé fournie. Si l’AC constate effectivement qu’un tel enregistrement existe, il valide la propriété du domaine et fournit un certificat qu’il aura préalablement signé.
Pour le challenge HTTP, tout se passait sur la machine. Certbot n’avait donc qu’à commander un certificat auprès de Let’s Encrypt, placer le fichier de preuve fourni pour valider la propriété de la machine. Mais pour le challenge DNS, certbot va devoir éditer la zone DNS. Dans mon cas, la zone DNS est détenue par le prestataire qui me fournit le VPS : Gandi. Il va donc falloir autoriser certbot à modifier cette zone DNS, et pour cela on va passer par une API mise à disposition par Gandi : LiveDNS. Certbot ne permet pas nativement de communiquer avec cette API. En revanche, il dispose d’un système d’extensions (plugins) pour enrichir les fonctionnalités. Et il se trouve justement qu’une personne a déjà implémenté une extension certbot pour communiquer avec l’API LiveDNS de Gandi ; elle est disponible sur Github. Certbot va également avoir besoin de s’authentifier auprès de Gandi pour éditer la zone DNS de votre domaine en votre nom ; il va donc falloir lui renseigner une clé API liée à votre compte et fournie par Gandi sur simple demande. Voyons plus en détail comment s’y prendre.
D’abord, nous allons supprimer le certificat TLS commandé pour example.com
. Les certificats générés par certbot sont identifiés par un nom, le plus souvent le nom du domaine qu’il authentifie. Si on demande un nouveau certificat pour un domaine déjà existant, certbot n’écrase pas nécessairement l’ancien mais ajoute simplement des chiffres après pour créer le nom de ce nouveau certificat. Ce n’est pas forcément gênant, mais ça nous obligerait à modifier la configuration nginx des sites de ces domaines, et de toute façon ils ne nous serviront plus une fois que nous aurons le certificat wildcard. Pour supprimer un certificat avec certbot, on utilise la commande suivante :
$ sudo certbot delete --cert-name example.com
Si le nom du certificat n’est pas correct, vous pouvez consulter la liste des certificats avec la commande suivante et utiliser le nom adéquat :
$ sudo certbot certificates
Maintenant, nous allons générer votre clé API LiveDNS depuis l’interface d’administration de Gandi. Pour cela, il faut se rendre sur account.gandi.net/fr. Rendez vous ensuite dans la section Sécurité et demandez à générer une clé API. Notez bien cette clé car vous ne pourrez plus y accéder par la suite. Si vous la perdez, il faudra en générer une nouvelle et mettre à jour tous les services qui ont recours à cette API.
Installez l’extension certbot-plugin-gandi
avec pip
. Si vous n’avez pas déjà pip
installé sur votre machine, installez le avec votre gestionnaire de paquets.
$ sudo pip install certbot-plugin-gandi
Créez ensuite un fichier renseignant votre clé API afin que certbot puisse l’utiliser. On paramètre les permissions pour qu’il soit uniquement accessible en lecture.
$ sudo mkdir /etc/letsencrypt/gandi
$ sudo echo "dns_gandi_api_key=APIKEY" > /etc/letsencrypt/gandi/gandi.ini
$ sudo chmod 600 /etc/letsencrypt/gandi/gandi.ini
Enfin on peut commander le certificat en utilisant certbot. On peut alors utiliser la commande suivante. Je vous détaille chaque paramètre :
$ sudo certbot certonly --authenticator dns-gandi --dns-gandi-credentials /etc/letsencrypt/gandi/gandi.ini -d domain.com
certonly
: on demande juste à commander le certificat TLS auprès de l’autorité de certification Let’s Encrypt. Pas besoin d’éditer la configuration nginx, elle pointe déjà vers l’emplacement où sera déposé le futur certificat.--authenticator dns-gandi
: on précise la méthode d’authentification, ici on utilise le plugin dns-gandi--dns-gandi-credentials /etc/letsencrypt/gandi/gandi.ini
: on transmet au plugin dns-gandi l’emplacement du fichier qui contient la clé API pour s’authentifier auprès de l’API LiveDNS de Gandi afin d’éditer la zone DNS-d example.com
: on renseigne finalement le nom de domaine pour lequel on commande le certificat. On peut noter qu’il suffit de préciser le domaine nu, mais le certificat TLS inclura également tous les sous-domaines (*.example.com
).
Voilà, on a maintenant un certificat wildcard à la place du certificat TLS pour le domaine nu seul. Comme dit précédemment, pas besoin de changer la configuration nginx, elle pointe déjà vers ce nouveau certificat puisque certbot lui donne comme nom le domaine nu, par défaut.
Rediriger tous les sous-domaines
Maintenant que nous avons un certificat wildcard, nous pouvons authentifier n’importe quel sous-domaine. Nous allons donc modifier la configuration nginx du site pour le domaine example.com
pour l’étendre à tous ses sous-domaines. Au lieu de rediriger systématiquement vers example.org
, on redirigera vers le sous-domaine équivalent.
Pour cela, on utilise une expression régulière pour écouter les requêtes sur tous les server name qui sont des sous-domaines de example.com
en capturant le préfixe, puis on change la redirection pour ajouter la partie capturée au domaine canonique example.org
. La configuration nginx de example.com devrait alors ressembler à ça :
server {
server_name "~^(?<sub>([\w\-]+\.)?)example\.com$";
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
return 301 https://${sub}example.org$request_uri;
}
server {
server_name "~^(?<sub>([\w\-]+\.)?)example\.com$";
listen [::]:80;
listen 80;
return 301 https://$host$request_uri;
}
Décortiquons la ligne server_name :
~
: indique que ce qui suit est une expression régulière^
: indique le début de l’expression régulière(?<sub>)
: crée un groupe de capture nommésub
([\w\-]+\.)
: capture tous les mots composés de caractères alphanumériques et du caractère-
et finissant par un caractère.
?
: indique que le groupe précédent se trouve au plus une fois$
: indique la fin de l’expression régulière
On capture le préfixe du sous-domaine pour le concaténer à notre domaine canonique lors de la redirection. On peut enfin tester notre configuration nginx et la recharger pour appliquer les changements.
Nous voici donc au terme de ce tutoriel. Après ces instructions vous devriez donc être capable de créer une configuration nginx pour rediriger n’importe quel sous-domaine d’un nom de domaine alternatif vers le sous-domaine équivalent de votre nom de domaine canonique en HTTPS authentifié par un certificat wildcard qui pour être renouvelé automatiquement par certbot par un challenge DNS grâce à LiveDNS.
Si j’ai fait une erreur ou que des parties de ce tutoriel ne sont pas claires, je vous invite à commenter cet article afin que je puisse vous répondre et éventuellement corriger et enrichir ce tutoriel.