Guillaume Fenollar DevOps et SysAdmin Freelance

Guillaume FENOLLAR

Ingénieur Linux/DevOps Indépendant

− Montpellier −

Docker et Let's Encrypt, le certificat TLS facile et gratuit

Loin de moi l'idée de faire un cours sur TLS ici, mais tout le principe du chiffrage asymétrique dont il est question ici repose sur de la confiance envers un tiers, ce tiers étant l'authorité de certification (CA). Les certificats étaient, jusqu'à il y a quelques années, une dépense incontournable pour tout détenteur de site Internet un tant soit peu sérieux, le prix d'un certificat dépendant de la réputation et certifications de la CA. Ce temps-là est révolu.

Qui dans le métier n’a pas entendu parler de Let’s Encrypt ? À l’époque je n’osais même pas y croire tellement ça sonnait trop beau pour être vrai, à prendre à contre-pied les standards que l’industrie avait imposé à tous. Seulement, depuis, le web est devenu omniprésent, et certains, dont Google, se sont efforcés de faire passer un message simple: “Encrypt everything”. Dès lors, le besoin de solutions moins onéreuses tout en restant globales et accessibles à tous sur la planète s’est fait ressentir. Puis vint Let’s Encrypt (prosternez-vous !).

Bref, fini le blabla, passons à l’action. Voici une configuration simple à copier coller pour obtenir des certificats TLS à la demande et le renouvellement automatique. Car n’oublions pas que pour des raisons de sécurité, Let’s Encrypt ne signe les certificats que pour une durée de 3 mois. Et comme nous sommes informaticiens donc fainéants, nous ne voulons pas faire une opération manuelle, même une fois tous les 3 mois, si elle peut être automatisée !

Étape préliminaire, les DNS

Tout d’abord, s’assurer que tous les noms de domaine à vouloir avoir dans le certificat mènent bien vers notre serveur. En effet, bien qu’un certificat ne peut avoir qu’un seul CN (le nom attendu de la ressource), celui-ci peut avoir également des noms alternatifs, qui auront le même poids que le CN, seront reconnus au même titre par le navigateur, et seront donc tous vérifiés par Let’s Encrypt, bien évidemment. Si un seul de ces noms ne pointe pas vers le serveur désiré, ou n’est pas inclus dans la conf qu’on va voir juste après, le processus entier ne fonctionnera pas. Fort heureusement, les logs parleront d’eux-même si tel est le cas.

Serveur web

J’utilise ici Nginx mais ça pourrait être n’importe quel serveur web bien entendu.

Tout d’abord, le fichier service, réduit ici au minimum pour l’exemple :

/etc/systemd/system/nginx.service
[Unit]
Description = Nginx
After = docker.service

[Service]
Environment=NAME=%N
Restart=always
RestartSec=5

ExecStartPre=-/usr/bin/docker rm ${NAME}
ExecStart=/usr/bin/docker run --rm --name ${NAME} \
    -v /srv/nginx/conf.d/:/etc/nginx/conf.d/:ro \
    -v /etc/letsencrypt/:/etc/letsencrypt/ \
    -p 80:80 \
    -p 443:443 \
    nginx:alpine

ExecStop=/usr/bin/docker stop ${NAME}

ExecReload=-/usr/bin/docker exec ${NAME} sh -c "nginx -t && nginx -s reload"

[Install]
WantedBy=multi-user.target

En prime, la configuration Nginx qui redirige vers https sauf pour la vérification Let’s Encrypt

# /srv/nginx/conf.d/mon-domaine
server {
    listen 80;
    listen [::]:80;
    server_name <MON-DOMAINE>
    location /.well-known {
        root   /etc/letsencrypt/webroot;
    }

    location / {
        rewrite ^/(.*)$ https://$host$request_uri permanent;
    }
}


server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name <MON-DOMAINE>

    ssl_certificate     /etc/letsencrypt/live/<MON-DOMAINE>/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/<MON-DOMAINE>/privkey.pem;

    # TODO: Rajoutez votre conf root, proxy, etc...
}

Je compte sur vous pour ne pas copier coller bêtement tout ça sans prendre le temps d’adapter la bête à vos besoins ;)

Certbot, l’agent Let’s Encrypt

Nul besoin d’installer Let’s Encrypt, l’utilisation du docker Certbot va le faire pour nous. Voici le fichier service qui sera lancé pour demander et renouveler un certificat.

# /etc/systemd/system/certbot.service
[Unit]
Description = Let's Encrypt Certbot
After = docker.service

[Service]
Type=oneshot
Environment=NAME=%N
ExecStart=/usr/bin/docker run --rm --name ${NAME} \
             -v "/etc/letsencrypt:/etc/letsencrypt" \
             certbot/certbot certonly -n --expand \
               --webroot --webroot-path /etc/letsencrypt/webroot \
               --email <MON-EMAIL> \
               --agree-tos \
               -d <MON-DOMAINE>

ExecStartPost=-/usr/bin/docker exec nginx nginx -s reload

[Install]
WantedBy=multi-user.target

Notez l’utilisation d’ExecStartPost qui va s’affairer à reloader la conf de Nginx une fois que le certificat aura été créé ou renouvelé, pour que le serveur prenne en compte sa création ou modification.

Ne pas oublier de remplacer les champs “MON-DOMAINE” et “MON-EMAIL”. L’email créera un compte distant, et sera utilisé pour vous envoyer des mails de rappels dans le cas où les certificats arrivent en fin de vie et qu’ils n’ont pas été renouvelés. Il est donc important de mettre une adresse valide !

Un timer, pour ne plus jamais y penser

Je vais utiliser la fonctionalité timers de systemd, mais libre à vous de passer par un bon vieux cron ou autre, le tout étant de ne pas oublier qu’un certificat est signé pour 3 mois seulement. Ici, je lance le process toutes les semaines :

# /etc/systemd/system/certbot.timer
[Unit]
Description=Certbot run

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target

Enfin, ne pas oublier de d’activer le timer en question :

# systemctl start certbot.timer
# systemctl list-timers
NEXT                         LEFT        LAST                         PASSED       UNIT                                ACTIVATES
Mon 2019-08-26 00:00:00 +11  2 days left Mon 2019-08-19 00:00:01 +11  4 days ago   -certbot.timer                      certbot.service

Vérifications et troubleshooting

Les logs de Certbot seront vos meilleurs amis si vous rencontrez un problème lors de la validation. Voici un exemple d’erreur :

systemd[1]: Starting Let's Encrypt Certbot...
docker[25248]: Saving debug log to /var/log/letsencrypt/letsencrypt.log
docker[25248]: Plugins selected: Authenticator webroot, Installer None
docker[25248]: Cert is due for renewal, auto-renewing...
docker[25248]: Renewing an existing certificate
docker[25248]: Performing the following challenges:
docker[25248]: http-01 challenge for guillaume.fenollar.fr
docker[25248]: http-01 challenge for matomo.fenollar.fr
docker[25248]: Using the webroot path /etc/letsencrypt/webroot for all unmatched domains.
docker[25248]: Waiting for verification...
docker[25248]: Challenge failed for domain matomo.fenollar.fr
docker[25248]: http-01 challenge for matomo.fenollar.fr
docker[25248]: Cleaning up challenges
docker[25248]: Some challenges have failed.
docker[25248]: IMPORTANT NOTES:
docker[25248]:  - The following errors were reported by the server:
docker[25248]:    Domain: matomo.fenollar.fr
docker[25248]:    Type:   connection
docker[25248]:    Detail: dns :: DNS problem: NXDOMAIN looking up A for
docker[25248]:    matomo.fenollar.fr
docker[25248]:    To fix these errors, please make sure that your domain name was
docker[25248]:    entered correctly and the DNS A/AAAA record(s) for that domain
docker[25248]:    contain(s) the right IP address. Additionally, please check that
docker[25248]:    your computer has a publicly routable IP address and that no
docker[25248]:    firewalls are preventing the server from communicating with the
docker[25248]:    client. If you're using the webroot plugin, you should also verify
docker[25248]:    that you are serving files from the webroot path you provided.

Nul besoin de plus de log que ça, l’erreur est très claire ici, les DNS de mon nouveau domaine ne sont pas corrects :)

Et voici un log tout beau tout propre, quand l’opération se passe bien :

systemd[1]: Starting Let's Encrypt Certbot...
docker[31474]: Saving debug log to /var/log/letsencrypt/letsencrypt.log
docker[31474]: Plugins selected: Authenticator webroot, Installer None
docker[31474]: Obtaining a new certificate
docker[31474]: IMPORTANT NOTES:
docker[31474]:  - Congratulations! Your certificate and chain have been saved at:
docker[31474]:    /etc/letsencrypt/live/guillaume.fenollar.fr/fullchain.pem
docker[31474]:    Your key file has been saved at:
docker[31474]:    /etc/letsencrypt/live/guillaume.fenollar.fr/privkey.pem
docker[31474]:    Your cert will expire on 2019-11-06. To obtain a new or tweaked
docker[31474]:    version of this certificate in the future, simply run certbot
docker[31474]:    again. To non-interactively renew *all* of your certificates, run
docker[31474]:    "certbot renew"
docker[31474]:  - If you like Certbot, please consider supporting our work by:
docker[31474]:    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
docker[31474]:    Donating to EFF:                    https://eff.org/donate-le
docker[31594]: 2019/08/08 14:08:41 [notice] 19#19: signal process started

Dans tous les cas, openssl restera votre outil de choix afin d’analyser les champs d’un certificat TLS d’un serveur sans avoir à passer par un navigateur. Voici un petit one-liner que vous devriez avoir au bout des doigts :

openssl s_client -connect MON-DOMAINE:443 -server MON-DOMAINE -showcerts | openssl x509 -noout -text

Et si Let’s Encrypt vous évite bien des tracas, n’oubliez pas de faire un don !