Ayant participé à la bêta privée de let’s encrypt, j’ai pu jouer un peu avec, en particulier pour automatiser la génération de certificat, puisque quand j’ai commencé, il n’y avait pas d’intégration avec nginx et maintenant je ne suis pas friand de laisser un programme modifier mes vhosts.
Installation
Commencez par récupérer l’outils :
git clone https://github.com/certbot/certbot.git letsencrypt
cd letsencrypt
./letsencrypt-auto
Je vous conseille de lancer l’interface afin de lire les conditions d’utilisation du service et de vous familiariser avec son fonctionnement (informations demandées et challenge de validation).
Bon l’interface est sympa, mais la lancer tous les trois mois pour générer des dizaines de certificats1, ce n’est pas envisageable. Passons donc à l’automatisation.
Automatisation
Créez un fichier /etc/letsencrypt/config.cli.ini
:
text = True
agree-dev-preview = True
rsa-key-size = 4096
agree-tos = True
authenticator = webroot
webroot-path = /tmp/letsencrypt/public_html
Le plus important ici, est les deux dernières options qui permettent de gérer
l’authentification avec notre propre serveur http. Let’s encrypt va déposer les
fichiers dans webroot-path
et tenter de les récupérer via le nom de domaine du
certificat dans le sous-dossier .well-known
.
Voici donc la configuration pour nginx à placer dans chacun de vos vhost :
location /.well-known/acme-challenge {
add_header Content-Type application/jose+json;
root /tmp/letsencrypt/public_html;
}
Pendant que vous êtes dans la configuration de nginx, profitez en pour générer une configuration TLS convenable : https://mozilla.github.io/server-side-tls/ssl-config-generator/.
Pour finir le script à placer en crontab pour mettre à jour l’ensemble de vos certificats :
#!/bin/bash
source "$(dirname $(realpath $BASH_SOURCE))/config/letsencrypt.sh"
get_vhosts()
{
local site=$1
local vhosts
vhosts=$(grep 'server_name ' $site | tr -d ' ' | sed 's/server_name//' | sed 's/;//' | grep -v '.onion$')
vhosts=($vhosts)
echo ${vhosts[*]}
}
count_char()
{
local str=$1
local char=$2
echo "$str" | grep -oF "$char" | wc -l
}
get_main_vhost()
{
local vhosts=$1
local nb_dot
local main_vhost
nb_dot=$(count_char $vhosts '.')
if [ $nb_dot -gt 1 ]
then
main_vhost=$(echo ${vhosts[0]} | cut -d . -f $nb_dot-)
else
main_vhost=${vhosts[0]}
fi
echo $main_vhost
}
get_email()
{
local vhosts=$1
local main_vhost
local email
main_vhost=$(get_main_vhost $vhosts)
email="postmaster@$main_vhost"
echo $email
}
get_lastchange()
{
local vhosts=$1
local pem="/etc/letsencrypt/live/${vhosts[0]}/fullchain.pem"
local lastchange=-1
if [ -e $pem ]
then
lastchange=$(expr $(expr $(date +%s) - $(date +%s -r $pem)) / 86400)
fi
echo $lastchange
}
transform_vhost_to_arg()
{
local vhosts=$1
local vhost
local vhosts_arg=''
for vhost in ${vhosts[@]}
do
vhosts_arg="$vhosts_arg -d $vhost"
done
echo $vhosts_arg
}
main()
{
if [ "$1" = '--dry-run' ]
then
local dry_run=true
shift
else
local dry_run=false
fi
if [ "$1" = '--force' ]
then
local force=true
shift
else
local force=false
fi
if [ $# -gt 0 ]
then
local sites=$@
else
local sites=$(find /etc/nginx/sites-enabled)
fi
if [ ! -e "$LETSENCRYPT_WEBROOT" ]
then
mkdir -p "$LETSENCRYPT_WEBROOT"
fi
for site in $sites
do
local status='skip'
echo -n "$site: "
local vhosts=$(get_vhosts $site)
if [ -n "$vhosts" ]
then
local email=$(get_email $vhosts)
local vhosts_arg=$(transform_vhost_to_arg "$vhosts")
local lastchange=$(get_lastchange $vhosts)
if [ $force = true -o $lastchange -lt 0 -o $lastchange -gt 60 ]
then
if [ $dry_run = false ]
then
certbot certonly $vhosts_arg \
--email "$email" -c /etc/letsencrypt/config.cli.ini \
--renew-by-default
if [ $? -eq 0 ]
then
status='pass'
else
status='fail'
fi
if [ -f "$(dirname $(realpath $BASH_SOURCE))/config/${vhosts[0]}" ]
then
bash "$(dirname $(realpath $BASH_SOURCE))/config/${vhosts[0]}"
fi
else
echo $vhosts_arg $email
fi
fi
fi
echo "$status"
done
if [ $dry_run = false ]
then
systemctl reload nginx
fi
}
main $@
Un peu de bash velu, le plus compliqué étant de retrouver le nom de domaine racine pour renseigner l’email.
Notez, qu’afin de tenir compte des limitations, seul les certificats de plus de 60 jours sont renouvelés.
Une fois que vous avez votre premier certificat, vous pouvez l’ajouter à votre vhost :
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Pour aller plus loin, vous pouvez également lire ce sujet.
Proxy
Nginx est un excellent proxy cache2. Dans ce cas il faut commencer par
chercher le challenge en local et s’il n’est pas trouvé se rabattre sur le
serveur primaire (dans le cas où l’on génère le certificat sur ce dernier).
Enfin passer également toutes les autres requêtes via $primary_server_ip
.
location /.well-known/acme-challenge {
add_header Content-Type application/jose+json;
root /tmp/letsencrypt/public_html;
try_files $uri @proxy_pass;
}
location / {
error_page 418 = @proxy_pass;
return 418;
}
location @proxy_pass {
proxy_pass $scheme://$primary_server_ip:$server_port;
proxy_cache STATIC;
proxy_cache_key $host$request_uri;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_cache_purge PURGE from $primary_server_ip;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header X-Cache-Status $upstream_cache_status;
}
Résultat
-
Les certificats joker ne sont pas à l’ordre du jour : https://github.com/letsencrypt/boulder/issues/21. ↩
-
Si on omet la stupidité d’avoir réservé la commande PURGE à la version commerciale. Cela se contourne avec un bout de bash… ↩