Les certificats SSL avec Let's Encrypt

sam. 27 févr. 2016 by Marmotte

Let's Encrypt est une autorité de certification, reconnue par les navigateurs web actuels, qui propose de générer des certificats SSL de manière totalement automatique et gratuite. Cela permet donc à n'importe qui de proposer un site web en HTTPS, sans effrayer les utilisateurs avec une alerte sur la validité du certificat SSL (qui sont de plus en plus anxiogènes), en limitant l'intervention manuelle à l'installation initiale, et sans débourser un seul centime.

Cette solution n'est probablement pas la plus adaptée pour un site e-commerce, qui préfèrera certainement un certificat SSL payant, avec une validation étendue (EV), par exemple. Mais elle conviendra parfaitement pour un site web personnel, d'association, ou de petite entreprise, n'ayant pas les moyens, ou l'envie, de payer quelques dizaines ou centaines d'euros annuels, pour un certificat SSL.

Dans cet article, j'explique l'installation du client officiel Let's Encrypt (actuellement en version 0.4.0), et son utilisation avec un serveur web Apache.

Fonctionnement

La génération automatique du certificat SSL s'appuie sur une validation permettant de vérifier que celui qui le demande maîtrise bien le domaine pour lequel le certificat sera généré.

La méthode de validation est nommée ACME Challenge. Son fonctionnement est simple : Le serveur Let's Encrypt envoie un token au client, puis tente de le lire via le protocole défini. Cette validation est possible via différents protocoles (HTTP, DNS, etc.). Je n'utilise pour le moment que le protocole HTTP pour cela.

Les certificats générés sont valides pour une durée de trois mois.

Le client Let's Encrypt propose la possibilité de tout faire automatiquement : Installation des dépendances, génération du certificat, configuration automatique d'Apache et/ou nginx, etc. Cependant, cette fonctionnalité nécessite de lui laisser les droits de root, et de lui permettre de modifier automatiquement les virtualhosts du serveur, ce que je refuse. Ma configuration est donc un peu plus compliquée, mais lui donne bien moins de droits.

Installation

Il existe différents clients pour Let's Encrypt. J'ai choisi d'utiliser le client officiel, qui convient à mon utilisation, et ne m'a jamais posé de problème.

Ce client étant écrit en python, je l'installe dans un environnement virtuel, puisqu'il n'est pas disponible dans les dépôts des versions de Debian que j'utilise. Son installation nécessite tout de même quelques dépendances, qui se trouvent dans les dépôts de Debian.

$ sudo apt-get install --no-install-recommends python-virtualenv ca-certificates dialog libaugeas0 augeas-lenses gcc python-dev libssl-dev libffi-dev
$ sudo virtualenv /opt/virtualenv/letsencrypt
$ sudo /opt/virtualenv/letsencrypt/bin/pip install letsencrypt

Note : Sous Debian Jessie, il est nécessaire d'installer le paquet virtualenv en plus, puisqu'il contient l'exécutable virtualenv, qui se trouvait dans le paquet python-virtualenv dans les versions précédentes.

Comme l'exécution en root n'est pas obligatoire pour son fonctionnement, je lui crée un utilisateur dédié. Il est inutile de donner plus de droits que nécessaire à un programme, surtout installé hors dépôts.

$ sudo adduser letsencrypt

Enfin, il est nécessaire de créer les répertoires dans lesquels Let's Encrypt va travailler, puisqu'il n'aura pas le droit de les créer sans les droits de root.

$ sudo mkdir -p /{etc,var/log,var/www}/letsencrypt
$ sudo chown -R letsencrypt:letsencrypt /{etc,var/log,var/www}/letsencrypt

Configuration

Let's Encrypt

Les paramètres par défaut sont adaptés à une utilisation interactive. Je crée donc un fichier de configuration, pour définir les paramètres que j'utilise lors de chaque exécution, ce qui évite de devoir les préciser dans chaque commande tapée.

Le fichier de configuration de Let's Encrypt est /etc/letsencrypt/cli.ini.

rsa-key-size = 4096
email = <webmaster@example.com>

authenticator = webroot
webroot-path = /var/www/letsencrypt

agree-tos = True
non-interactive = True
keep-until-expiring = True

rsa-key-size définit la longueur de la clé RSA utilisée. La valeur par défaut est 2048, ce qui est considéré comme faible par de nombreuses personnes.

email définit simplement l'adresse email utilisée pour la récupération des informations en cas de perte du compte Let's Encrypt.

authenticator définit la méthode d'authentification utilisée. Ici, j'utilise donc la méthode de copie automatique du token dans un répertoire servi en HTTP.

webroot-path définit le répertoire dans lequel le client Let's Encrypt déposera le fichier du token.

agree-tos permet d'accepter automatiquement les conditions du service, nécessaire pour une utilisation non interactive.

non-interactive signale au client Let's Encrypt qu'on veut l'utiliser de manière totalement non interactive.

keep-until-expiring permet de ne renouveler les certificats que lorsque c'est nécessaire. Si un renouvellement est demandé alors que le certificat a encore plus de 30 jours (par défaut) de validité, il ne sera pas effectué.

Apache

Pour que le serveur de Let's Encrypt puisse lire le token déposé par le client sur notre serveur, il est nécessaire de le permettre, via une configuration d'Apache. Pour un site web simple, il serait tout à fait possible de dire au client Let's Encrypt de déposer ce fichier dans le répertoire du site web lui même, mais cette façon de faire pose plusieurs problèmes :

  • Cela nécessite de donner les droits au client Let's Encrypt d'écrire dans un sous répertoire du site web.
  • En cas de multiples sites web hébergés sur le même serveur, il faudrait préciser un chemin différent au client Let's Encrypt, selon le site web pour lequel on veut générer un certificat.
  • Certaines applications web ne permettent pas aussi simplement d'ajouter des fichiers dans leur arborescence.
  • Notre serveur Apache peut n'être qu'un reverse proxy vers un autre serveur web.
  • J'en oublie certainement d'autres...

La solution que j'ai mise en place sur mes serveurs est donc de définir un alias global au niveau d'Apache, pour définir un répertoire unique sur le serveur, dans lequel le client Let's Encrypt ira déposer ses tokens, pour tous les vhosts. Je crée donc le fichier /etc/apache2/conf.d/letsencrypt (ou /etc/apache2/conf-enabled/letsencrypt.conf pour Debian Jessie), contenant :

ProxyPass /.well-known/acme-challenge !
Alias /.well-known/acme-challenge /var/www/letsencrypt/.well-known/acme-challenge
<Directory /var/www/letsencrypt/.well-known/acme-challenge>
    Options -Indexes
    Order allow,deny
    Allow from all
</Directory>

Note : La directive ProxyPass permet de retirer l'URL définie des reverse proxies des vhosts. Sans cette directive, les URL commençant par /.well-known/acme-challenge seraient transmises à l'autre serveur web, plutôt que servies localement.

Il est ensuite nécessaire de recharger la configuration d'Apache :

$ sudo service apache2 reload

Utilisation

L'exécutable du client Let's Encrypt peut être appelé avec la commande /opt/virtualenv/letsencrypt/bin/letsencrypt. Il est nécessaire de préciser le chemin complet de l'exécutable, puisqu'il se trouve dans un environnement virtuel.

Un paramètre --help affiche l'aide courte, et --help all affiche l'aide complète.

Tests

Chaque commande du client Let's Encrypt peut être exécutée en mode test, en ajoutant le paramètre --dry-run. Cela permet de vérifier la syntaxe et la validité des requêtes envoyées, sans déclencher les limitations mises en place par Let's Encrypt. Je recommande donc d'exécuter chaque nouvelle commande d'abord avec ce paramètre, puis de le retirer quand la commande semble fonctionner correctement.

Génération d'un certificat

La génération d'un certificat peut se faire de différentes manières : Validation automatique ou manuelle. Mon but étant de tout automatiser, j'utilise donc la sous-commande certonly, qui va générer, et valider automatiquement, des certificats. Cette opération doit être exécutée par l'utilisateur letsencrypt créé plus tôt.

$ /opt/virtualenv/letsencrypt/bin/letsencrypt certonly --domain <subdomain.example.com>

Note : Le parmètre --domain permet de définit le nom de domaine auquel le certificat s'applique. Il est possible d'en ajouter d'autres, pour générer un certificat valide sur plusieurs domaines.

Modification des virtualhosts

Les certificats générés sont automatiquement placés dans le répertoire /etc/letsencrypt/archive/<subdomain.example.com>/, et un lien symbolique vers le certificat en cours de validité est placé dans /etc/letsencrypt/live/<subdomain.example.com>/ Quatre fichiers sont disponibles pour chaque certificat :

  • cert.pem : Le certificat du serveur seul
  • privkey.pem : La clé privée du certificat
  • chain.pem : La chaîne de certificats intermédiaires, depuis le certificat racine, sans le certificat du serveur
  • fullchain.pem : La chaîne complète de certificats, incluant les certificats intermédiaires, et le certificat du serveur

Il suffit donc d'indiquer les chemins vers ces fichiers dans les virtualhosts d'Apache.

SSLEngine on
SSLCertificateFile      /etc/letsencrypt/live/<subdomain.example.com>/cert.pem
SSLCertificateKeyFile   /etc/letsencrypt/live/<subdomain.example.com>/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/<subdomain.example.com>/chain.pem

Note : Depuis Apache 2.4, il est possible d'utiliser la chaîne complète comme certificat, en remplaçant donc le fichier cert.pem par fullchain.pem, et en retirant la directive SSLCertificateChainFile.

Il ne reste plus qu'à recharger la configuration d'Apache pour que le certificat soit pris en compte.

$ sudo service apache2 reload

Renouvellement automatique

L'un des avantages de Let's Encrypt est d'être totalement automatisé. Le client let's Encrypt dispose donc d'une fonctionnalité de renouvellement automatique des certificats, avec la sous-commande renew.

J'ai ajouté un cron à l'utilisateur letsencrypt (avec la commande sudo crontab -e -u letsencrypt), pour l'exécuter tous les jours.

0 0 * * * /opt/virtualenv/letsencrypt/bin/letsencrypt renew

Sans paramètre supplémentaire, cette commande vérifie la durée de validité de tous les certificats existants. Tant qu'un certificat est valide pour une durée supérieure à 30 jours, elle ne fait rien. Dès que la validité d'un certificat tombe en dessous de 30 jours, il est renouvelé automatiquement.

Note : Exécuter la commande tous les jours, plutôt que tous les deux mois, par exemple, permet de retenter automatiquement le renouvellement s'il échoue lors de l'éxécution du cron.

Cela permet aussi de ne pas être bloqué par les limitations de Let's Encrypt. En effet, chaque domaine ne peut demander que cinq certificats au maximum tous les sept jours. Avoir une exécution unique du renouvellement, tous les deux mois, ne permettrait donc d'avoir que cinq certificats au total, ce qui n'est pas forcément suffisant.