Kévin Subileau

Espace personnel

let's encrypt logo

Let's Encrypt - Installation manuelle et sécurisée sur Apache

Let's Encrypt est une nouvelle autorité de certification qui délivre des certificats SSL gratuits. Oui, j'ai bien dit gratuit, là où auparavant il fallait parfois débourser jusqu'à une centaine d'euros par an pour un certificat !

Mais désormais, il n'y a plus aucune raison pour ne pas laisser tomber son certificat auto-signé et mettre en place le HTTPS avec un véritable certificat SSL, certifié par une autorité, et qui n'affichera donc pas dans les navigateurs une page d'alerte qui ferait fuir vos visiteurs.

Dans cet article, je vais donc vous présenter la méthode que j'ai utilisée pour mettre en place un certificat SSL gratuit de Let's Encrypt sur mon VPS.

Le site de Let's Encrypt suggère pour cela d'utiliser le client Python officiel, totalement automatisé. Mais personnellement, je ne suis pas très fan de l'idée qu'un script, dont j'ignore le contenu, s’exécute automatiquement avec les permissions root, manipule des fichiers de configurations sensibles (tel que ceux d'Apache), et contrôle la sécurité de mon serveur Web.

De plus, le script officiel requiert selon moi beaucoup trop de dépendances à installer, et propose trop de fonctionnalités par rapport à ce qui m'est réellement utile. Je suis en effet totalement capable de faire l'installation et la configuration initiale moi-même, manuellement. Je n'ai besoin d'un script que pour renouveler automatiquement le certificat, rien de plus.

Je suis donc parti à la recherche d'une solution alternative pour obtenir un certificat Let's Encrypt, et je suis finalement tombé sur acme-tiny. C'est un script de seulement 200 lignes, que j'ai donc pu facilement analyser pour vérifier qu'il ne comportait rien de malveillant, et qui n'a besoin que de Python, quelques modules de base et la commande openssl pour fonctionner. Le plus souvent, tout ceci est déjà présent sur une configuration classique.

La méthode proposée ci dessous est adaptée pour obtenir une sécurité optimale sur un serveur Debian Wheezy ou Jessie, avec Apache 2.2 ou 2.4.10, et OpenSSL 1.0.1. Il est toutefois important que vous compreniez ce que vous faites et que vous ajustiez la configuration proposée selon votre système et vos besoins.

Préparation du serveur

Vérification des prérequis

On commence par quelques petites vérifications et préparations sur le serveur. Tout d'abord, on s'assure que Python 2.7.x est bien installé en utilisant la commande suivante :

python --version

Si vous obtenez un message command not found, il vous faudra passer par la case installation de Python avant de poursuivre.

Profitez en également pour récupérer la version d'Apache et d'OpenSSL installée sur votre système respectivement avec les commandes ci-dessous :

sudo apache2ctl -v
openssl version

Création d'un utilisateur dédié

A partir de ce point, je vous conseille de passer une bonne fois pour toute en root, car la plupart des commandes qui vont suivre vont nécessiter ces permissions pour fonctionner. Pour cela, vous pouvez utiliser la commande sudo -i.

On va maintenant créer un utilisateur letsencrypt, auquel on attribuera le strict minimum de permissions pour pouvoir créer et renouveler des certificats. Cet utilisateur sera responsable de l'exécution du script de renouvellement automatique, que nous verrons plus bas. Ainsi, ce script ne s'exécutera pas en root et ne pourra pas modifier accidentellement des fichiers importants du système. On créé également un dossier pour contenir tous les fichiers associés à Let's Encrypt :

mkdir /etc/letsencrypt
adduser --system --home /etc/letsencrypt --no-create-home --disabled-login \
    --disabled-password --group letsencrypt

On créé ensuite un ensemble de sous-dossiers pour bien ranger les choses :

  • bin : Dossier stockant les scripts acme-tiny et de renouvellement automatique.
  • certs : Contiendra les certificats, rangés par date d'obtention dans un sous-dossier archive, avec un lien symbolique live pointant vers le dossier contenant les certificats actuellement utilisés.
  • private : Les clés privées, ne devant absolument pas être divulguées, seront dans ce dossier avec le minimum de permissions.
  • challenges : Ce dossier sera utilisé pour déposer les fichiers temporaires nécessaires pour valider votre demande de certificats.

Voici les commandes à lancer pour créer tous ces dossiers :

cd ~letsencrypt
mkdir -p ~letsencrypt/{bin,certs/archive/$(date --utc +'%FT%TZ'),private,challenges}

Sans plus attendre, on ajuste quelques permissions sur les dossiers que l'on vient de créer :

chown -R letsencrypt:letsencrypt ~letsencrypt
chown letsencrypt:www-data ~letsencrypt/challenges

chmod 755 ~letsencrypt
chmod -R 775 ~letsencrypt/certs
chmod 750 ~letsencrypt/{bin,private,challenges}

Ensuite, on créé le lien symbolique pointant vers le dossier des certificats en cours d'usage. L'intérêt de ce lien est qu'ainsi le script de renouvellement ne modifiera pas directement les fichiers en production. Il créera plutôt un nouveau dossier, où il préparera tout le nécessaire, et ne redirigera le lien qu'une fois terminé et en l'absence d'erreur.

ln -s archive/$(date --utc +'%FT%TZ') ~letsencrypt/certs/live

Autoriser l'utilisateur letsencrypt à redémarrer Apache

Lors du renouvellement automatique du certificat, le script doit être en mesure de pouvoir redémarrer Apache afin que le nouveau certificat soit pris en compte. Pour cela, on va autoriser l'utilisateur letsencrypt à faire un sudo sans mot de passe, mais seulement sur la commande permettant de redémarrer Apache.

Pour cela, on créé et on édite un nouveau fichier sudoers avec la commande ci-dessous :

visudo -f /etc/sudoers.d/letsencrypt-apache2ctl-graceful

Dans ce nouveau fichier, on écrit le contenu suivant :

letsencrypt ALL=NOPASSWD: /usr/sbin/apache2ctl graceful

Ainsi, l’utilisateur letsencrypt, et donc le script de renouvellement, peut désormais lancer la commande sudo apache2ctl graceful sans mot de passe.

Téléchargement du script acme-tiny

On termine l'étape de préparation du serveur en récupérant une copie du script acme-tiny permettant d'obtenir le certificat Let's Encrypt avec la commande suivante :

wget -O ~letsencrypt/bin/acme_tiny.py https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py

Préparation de Apache

Durant le processus d'obtention du certificat, il faut prouver à Let's Encrypt que l'on a bien le contrôle administratif du domaine pour lequel on fait la demande. Pour cela, Let's Encrypt va nous demander d'héberger temporairement un fichier particulier à une adresse précise.

Ce fichier sera déposé par le script acme-tiny dans le dossier /etc/letsencrypt/challenges, qui doit être accessible par le web à l'adresse http://mondomaine.fr/.well-known/acme-challenge.

Pour cela, il est nécessaire de modifier quelque peu la configuration d'Apache, selon la version que vous utilisez.

Apache 2.4

Pour Apache 2.4, on commence par créer un nouveau fichier de configuration :

vi /etc/apache2/conf-available/letsencrypt.conf

Dans celui-ci, on écrit le contenu suivant :

Alias "/.well-known/acme-challenge" "/etc/letsencrypt/challenges"

# Retourne un code 404 plutot que Forbidden lorsque l'on tente d'acceder au dossier
RedirectMatch 404 ^/.well-known/acme-challenge(/$|$)

<Directory "/etc/letsencrypt/challenges">
    Options None
    AllowOverride None
    Require all granted

    <IfModule mod_headers.c>
        Header set Content-Type "application/jose+json"
    </IfModule>
</Directory>

On active ensuite cette nouvelle configuration avec les commandes ci-dessous :

a2enconf letsencrypt
apache2ctl graceful

Apache 2.2

Sur Apache 2.2, on créé un nouveau fichier de configuration global :

vi /etc/apache2/conf.d/letsencrypt

Dans ce dernier, on dispose le contenu suivant :

Alias "/.well-known/acme-challenge" "/etc/letsencrypt/challenges"

# Retourne un code 404 plutot que Forbidden lorsque l'on tente d'acceder au dossier
RedirectMatch 404 ^/.well-known/acme-challenge(/$|$)

<Directory "/etc/letsencrypt/challenges">
    Options None
    AllowOverride None
    Order allow,deny
    Allow from all

    <IfModule mod_headers.c>
        Header set Content-Type "application/jose+json"
    </IfModule>
</Directory>

Puis on redémarre le serveur Apache pour prendre en compte les changements :

apache2ctl graceful

Vérification de l'accessibilité du dossier

Une fois la configuration d'Apache appliquée, on peut ensuite vérifier que le dossier de challenge est bien accessible sur le Web. Pour cela, créez un fichier de test à l'intérieur :

echo "Hello World" > ~letsencrypt/challenges/test.txt

Assurez-vous ensuite que vous pouvez récupérer ce fichier depuis un navigateur Web à l'adresse http://mondomaine.fr/.well-known/acme-challenge/test.txt (en remplaçant évidemment par votre propre nom de domaine). Si ce n'est pas le cas, vérifiez la configuration. Sinon, vous pouvez supprimer le fichier de test et passer à la suite :

rm ~letsencrypt/challenges/test.txt

Installation de Let's Encrypt

Création des clés

L'étape suivante consiste à générer un ensemble de clés et de fichiers nécessaires à Let's Encrypt pour vous authentifier et générer le certificat. En effet, il n'est pas nécessaire d'ouvrir un compte ni même de fournir des informations personnelles pour obtenir un certificat auprès de ce service. Let's Encrypt demande seulement une clé utilisateur pour vous reconnaître.

On commence donc par créer cette première clé d'une longueur de 4096 bits. Cette clé peut être partagée entre plusieurs serveurs, mais doit toujours rester parfaitement en sécurité.

openssl genrsa 4096 > ~letsencrypt/private/account.key

Puis on génère la clé privée du certificat. Cette clé doit obligatoirement être différente de la clé utilisateur et doit impérativement être gardée secrète !

openssl genrsa 4096 > ~letsencrypt/private/domain.key

On poursuit en créant la demande de signature de certificat (CSR), qui sera envoyée à Let's Encrypt par le protocole ACME pour faire générer le certificat. Pour cela, il y a deux possibilités. Soit votre certificat n'est utilisé que par un seul domaine, et dans ce cas vous utiliserez la commande suivante, en remplaçant bien sûr mondomaine.fr par votre propre domaine :

openssl req -new -sha256 -key ~letsencrypt/private/domain.key \
    -subj "/CN=mondomaine.fr" > ~letsencrypt/private/domain.csr

Soit vous partagez le même certificat sur plusieurs domaines (mondomaine.fr et www.mondomaine.fr par exemple), et il vous faut alors utiliser l'extension SAN (Subjet Alternative Names) dans la CSR :

openssl req -new -sha256 -key ~letsencrypt/private/domain.key -subj "/" -reqexts SAN \
    -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:mondomaine.fr,DNS:www.mondomaine.fr")) > ~letsencrypt/private/domain.csr

Vous pouvez alors inclure autant de domaines que vous souhaitez simplement en ajoutant une entrée DNS:autredomaine.fr par domaine.

Obtention du certificat

Nous y voici enfin, il est temps d'obtenir ce fameux certificat SSL gratuit ! Il vous suffit pour cela de lancer la commande suivante :

sudo -u letsencrypt -- python ~letsencrypt/bin/acme_tiny.py \
    --account-key ~letsencrypt/private/account.key --csr ~letsencrypt/private/domain.csr \
    --acme-dir ~letsencrypt/challenges/ > ~letsencrypt/certs/live/signed.crt

Si tout se passe bien, vous devriez obtenir un nouveau fichier signed.crt, c'est votre certificat !

Pour qu'il puisse être utilisé, il faut encore récupérer le certificat intermédiaire de Let's Encrypt et construire le certificat chainé :

sudo -u letsencrypt -- wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > ~letsencrypt/certs/live/intermediate.pem
sudo -u letsencrypt -- cat ~letsencrypt/certs/live/signed.crt ~letsencrypt/certs/live/intermediate.pem > ~letsencrypt/certs/live/chained.pem

Installation du certificat sur Apache

L'étape suivante consiste à configurer Apache afin d'utiliser les certificats fraîchement obtenus.

Je vous propose ci-dessous un exemple de configuration, libre à vous de l'ajuster en fonction de vos besoins, du niveau de sécurité souhaité et de la compatibilité nécessaire avec les navigateurs de vos visiteurs. Vous pouvez vous aider pour cela du générateur de configuration proposé par Mozilla.

Si vous souhaitez en savoir plus sur la configuration SSL avancée sur Apache, je vous conseille les pages suivantes (en anglais) :

Apache 2.4

Sur Apache 2.4, il faut modifier le fichier /etc/apache2/mods-available/ssl.conf afin d'y insérer les directives suivantes :

SSLCipherSuite          ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder     on
SSLProtocol             all -SSLv3
SSLCompression          off
SSLSessionTickets       off

# OCSP Stapling
SSLUseStapling          on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache        shmcb:/var/run/ocsp(128000)

Puis, pour chaque virtual host, il faut ajouter les lignes suivantes dans le fichier de configuration situé sous /etc/apache2/site-available/ :

<VirtualHost *:443>
    ...
    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/certs/live/chained.pem
    SSLCertificateKeyFile   /etc/letsencrypt/private/domain.key

    ...
</VirtualHost>

Apache 2.2

Pour Apache 2.2, le fichier /etc/apache2/mods-available/ssl.conf devra contenir les lignes suivantes :

SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
SSLHonorCipherOrder on
SSLProtocol all -SSLv3 -SSLv2

Ensuite, pour chaque virtual host, on ajoute les lignes de configuration suivantes :

<VirtualHost *:443>
    ...
    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/certs/live/signed.crt
    SSLCertificateKeyFile   /etc/letsencrypt/private/domain.key
    SSLCertificateChainFile /etc/letsencrypt/certs/live/intermediate.pem

    ...
</VirtualHost

Une fois la configuration appliquée, quelle que soit la version d'Apache, il faudra bien sûr lui demander de recharger sa configuration avec la commande apache2ctl graceful.

Automatisation du renouvellement

Le seul inconvénient des certificats gratuits émis par Let's Encrypt est qu'ils ne sont valables que pour 90 jours. Je ne sais pas vous, mais je n'est pas très envie de devoir me connecter sans cesse sur mes serveurs pour faire le renouvellement. C'est pourquoi nous devons automatiser cela avec un petit script et une tâche cron.

On va donc créer un script de renouvellement automatique dans le dossier /etc/letsencrypt/bin, que l'on nommera par exemple le-renew.sh.

vi ~letsencrypt/bin/le-renew.sh

Pour le contenu du script, je vous propose cet exemple qui ne renouvellera le certificat que lorsque la date d'expiration approche, mais là encore vous pouvez bien entendu le personnaliser. L'essentiel est de relancer le script acme-tiny avec les mêmes paramètres que lors de la création initiale du certificat, puis de reconstruire le certificat chainé et de redémarrer Apache.

#!/bin/bash

# Configure le nombre de jours en dessous duquel le certificat doit être renouvelé.
RENEW=15

# Récupère le timestamp actuel
NOW=$(date +%s)
# Récupère la date d'expiration du certificat actuel
EXPIRE=$(openssl x509 -in ~letsencrypt/certs/live/signed.crt -noout -enddate)
EXPIRE=${EXPIRE:9}
# Convertit la date d'expiration en un timestamp
EXPIRE=$(date --date="$EXPIRE" +%s)
# Calcul le temps restant avant l'expiration du certificat
LIFE=$(($EXPIRE - $NOW))
# Si la durée de vie restante est en dessous du minimum demandé.
if [ "$LIFE" -lt "$(( $RENEW * 86400 ))" ]; then 
    DATE="$(date --utc +'%FT%TZ')"
    mkdir --mode=775 ~letsencrypt/certs/archive/$DATE
    python ~letsencrypt/bin/acme_tiny.py --account-key ~letsencrypt/private/account.key --csr ~letsencrypt/private/domain.csr --acme-dir ~letsencrypt/challenges/ > ~letsencrypt/certs/archive/$DATE/signed.crt || exit
    wget -O ~letsencrypt/certs/archive/$DATE/intermediate.pem https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
    cat ~letsencrypt/certs/archive/$DATE/signed.crt ~letsencrypt/certs/archive/$DATE/intermediate.pem > ~letsencrypt/certs/archive/$DATE/chained.pem
    chmod 644 ~letsencrypt/certs/archive/$DATE/{chained.pem,intermediate.pem,signed.crt}
    rm ~letsencrypt/certs/live
    ln -s archive/$DATE ~letsencrypt/certs/live
    sudo apache2ctl graceful
fi

Je vous propose également une version plus avancée de ce script sur Gist, avec une meilleure gestion des erreurs et des logs, qui peut ensuite être combiné à LogWatch et LogRotate par exemple. A vous de faire votre choix !

Créez ensuite un dossier supplémentaire dédié au stockage des journaux d'exécution (logs):

mkdir /var/log/letsencrypt
chown letsencrypt:adm /var/log/letsencrypt
chmod 2755 /var/log/letsencrypt

Il ne reste plus qu'à créer une tâche cron qui exécutera le script avec l'utilisateur letsencrypt. Pour cela, plusieurs solutions, vous pouvez par exemple ajouter un fichier crontab dans le dossier /etc/cron.d/ :

vi /etc/cron.d/letsencrypt

Dans ce fichier, vous placerez le contenu suivant :

# Renouvellement automatique du certificat Let's Encrypt
# Tous les dimanches à 3H00
0 3 * * 0 letsencrypt /etc/letsencrypt/bin/le-renew.sh 2>> /var/log/letsencrypt/letsencrypt.log

Le script sera alors exécuté tous les dimanches à 3H00. Vous pouvez bien sûr ajuster cette fréquence, mais il est préférable de le faire tourner assez souvent de sorte à ce que plusieurs tentatives puissent avoir lieu durant les 90 jours de validité du certificat.

Fermeture des écoutilles

Il reste encore à ajuster quelques permissions pour renforcer la sécurité des fichiers sensibles et n'autoriser que le strict minimum.

On commence par changer le propriétaire et les permissions des deux scripts, de sorte que seul root puisse écrire dedans, et le groupe letsencrypt puisse les lire et les exécuter :

chown root:letsencrypt ~letsencrypt/bin/{acme_tiny.py,le-renew.sh}
chmod 750 ~letsencrypt/bin/{acme_tiny.py,le-renew.sh}

Maintenant, nous allons réduire les permissions sur les clés privés. L'utilisateur letsencrypt a simplement besoin de lire la clé utilisateur (account.key) et la demande de signature (domain.csr). Il ne doit pas pouvoir accéder à la clé privée du certificat (domain.key), qui ne doit être accessible que par root.

chown root:letsencrypt ~letsencrypt/private/{account.key,domain.csr,domain.key}
chmod 640 ~letsencrypt/private/{account.key,domain.csr}
chmod 600 ~letsencrypt/private/domain.key

Enfin, on fait en sorte que les certificats soient lisibles par tous, mais que seul l'utilisateur letsencrypt puisse les écrire :

chown letsencrypt:letsencrypt ~letsencrypt/certs/live/{chained.pem,intermediate.pem,signed.crt}
chmod 644 ~letsencrypt/certs/live/{chained.pem,intermediate.pem,signed.crt}

Conclusion

Félicitations, votre serveur est maintenant doté d'un certificat gratuit validé par une autorité de certification ! Pour vous assurez qu'il offre une sécurité optimale, je vous conseille de valider votre configuration sur un service tel que SSLLabs. Avec la configuration proposée ici, vous devriez obtenir sans peine une note A voire A+, ce qui est très satisfaisant.

SSLLabs analyse votre serveur et génère une note et un rapport sur sa sécurité.

SSLLabs analyse votre serveur et génère une note et un rapport sur sa sécurité.

Si vous avez une amélioration ou une correction à apporter à cette méthode, n'hésitez pas à la proposer dans un commentaire !

23 Commentaires

Dernier commentaire il y a 14/01/2020

Laisser un commentaire

ou

Champs Requis *.

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>