Title: Yet another tutorial about Let's Encrypt
Author: Sebastien Badia
Category: Tech
Tags: https, letsencrypt
Date: 2016-03-07 20:00

![Logo Let's Encrypt](//letsencrypt.org/images/letsencrypt-logo-horizontal.svg)

Voilà 1 million de certificats [Let's Encrypt](//letsencrypt.org/) délivrés (les certificats délivrés par [Let's Encrypt](https://crt.sh/?Identity=%25&iCAID=7395)); La littérature sur le sujet ne manque pas, mais je vais quand même en apporter une de plus, pour la partie compilation :-)

Pour rappel, Let's Encrypt, est un projet porté par l' « Internet Security Research Group » (ISRG), qui vise à automatiser, rendre accessible à tous, de manière ouverte et gratuite, des certificats SSL

## Génération

Nous allons ici, ne pas utiliser le client officiel; principalement parce qu'il intègre trop de fonctionnalités (serveur web intégré, …), mais aussi parce qu'il ne répond pas vraiment aux besoins de cet article (arborescence flexible, serveur nginx, …; et que c'est plus facile à lire un script de 200 lignes qu'un énorme client…)

Le client que nous allons utiliser est donc le [acme-tiny](https://github.com/diafygi/acme-tiny) (écrit en python). Lors de mes pérégrinations sur la toile (oui ok ça fait un peu vieillot, j'aurais pu dire « alors que je surfais sur le web », … :-D), je suis tombé sur ce script [letsencrypt.sh](https://github.com/lukas2511/letsencrypt.sh). Le coté FQDN à un endroit, et le rechargement lorsque les AltNames changent sont des features appréciables. Après c'est du bash, donc comme vous voulez :)

### Création des certificats

De mon coté, j'ai opté pour une arborescence de ce type (certificats générés dans un répertoire dédié, avec un utilisateur dédié).

```
$ mkdir -p /etc/letsencrypt/{certs,challenges,csr,pem,private}
/etc/letsencrypt/
├── certs
├── challenges
├── csr
├── pem
└── private
```

Il suffit donc alors de :

* Créer une clé pour le domaine en question
* Créer une demande de signature (CSR) en y incluant les noms DNS alternatifs
* Lancer le client (qui va se charger de créer un challenge) et signer si le challenge est ok.
* Télécharger et concaténer le certificat obtenu avec le certificat intermédiaire de Let's Encrypt

Le challenge sert ici à ce que let's encrypt vérifie que nous contrôlons bien le
domaine pour lequel nous demandons un certificat. Il faut d'ailleurs ajouter à
la configuration du serveur web, l'endroit où seront stockés ces challenges.

```nginx
location /.well-known/acme-challenge/ {
  alias /etc/letsencrypt/challenges/gitoyen.net/;
  try_files $uri =404;
}
```

Côté certificat intermédiaire, ils sont disponibles sur le site de [Let's Encrypt](https://letsencrypt.org/certificates/).

Bon donc du coup ça donne cela dans un mini-script (pas super beau…)

```bash
#!/bin/bash
# bootstrap letsencrypt

account=$1
cert=$2
dns=$3

mkdir -p /etc/letsencrypt/{certs,challenges,csr,pem,private}

pushd /etc/letsencrypt
  if [[ ! -f "./private/${account}.key" ]]; then
    openssl genrsa 4096 > "./private/${account}.key"
  fi
  if [[ ! -f ./pem/intermediate.pem ]]; then
    wget -O - https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem > ./pem/intermediate.pem
  fi
  echo "##### ${cert} #####"
  if [[ ! -f "./private/${cert}.key" ]]; then
    openssl genrsa 4096 > "./private/${cert}.key"
  fi
  mkdir -p "./challenges/${cert}"
  openssl req -new -sha256 -key "./private/${cert}.key" -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=%s" "${dns}")) > "./csr/${cert}.csr"
  acme_tiny.py --account-key "./private/${account}.key" --csr "./csr/${cert}.csr" --acme-dir "/etc/letsencrypt/challenges/${cert}/" > "./certs/${cert}.crt"
  cat "./certs/${cert}.crt" ./pem/intermediate.pem > "./pem/${cert}.pem"
popd
```

Qu'il suffit de lancer comme ceci (pour l'exemple je génère un certificate
gitoyen.net avec comme domaines gérés gitoyen.net et www.gitoyen.net (oui de la pub au passage))

```bash
$ bash bootstrap-letsencrypt.sh gitoyen gitoyen.net 'DNS:gitoyen.net,DNS:www.gitoyen.net'
##### gitoyen.net #####
Parsing account key...
Parsing CSR...
Registering account...
Already registered!
Verifying www.gitoyen.net...
www.gitoyen.net verified!
Verifying gitoyen.net...
gitoyen.net verified!
Signing certificate...
Certificate signed!
```

Plutôt cool non ? Il suffit alors d'adapter la configuration du serveur web, pour
prendre en compte ces certificats. Et passer tout en HTTPS ! (dans notre cas,
c'est un nginx en reverse proxy qui gère le SSL et forward en HTTP sur une IP
privée).

```nginx
# Gitoyen
server {
  server_name gitoyen.net planet.gitoyen.net;
  rewrite ^(.*) https://$host$1 permanent;
}

# Gitoyen SSL
# DNS:gitoyen.net,DNS:planet.gitoyen.net
server {
  listen 443;
  ssl on;
  client_max_body_size 20M;
  server_name gitoyen.net www.gitoyen.net;
  ssl_certificate /etc/letsencrypt/pem/gitoyen.net.pem;
  ssl_certificate_key /etc/letsencrypt/private/gitoyen.net.key;
  ssl_session_timeout 5m;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA;
  ssl_session_cache shared:SSL:50m;
  ssl_prefer_server_ciphers on;
  ssl_dhparam /etc/letsencrypt/dh4096.pem;
  ssl_ecdh_curve secp384r1;
  add_header Strict-Transport-Security max-age=15768000;
  resolver 127.0.0.1;
  location /.well-known/acme-challenge/ {
    alias /etc/letsencrypt/challenges/gitoyen.net/;
    try_files $uri =404;
  }
  location / {
    proxy_pass       http://10.41.1.143;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Ssl on;
  }
}
```

Pour la génération du dhparams (4096 bits c'est bien)

```bash
openssl dhparam -out dh4096.pem 4096
```

Et vous voila donc avec une super config et du HTTPS automatisé, on peut aller
faire un tour sur [imirhil](https://tls.imirhil.fr/https/gitoyen.net) pour un
check de la conf et bien sûr pour pouvoir briller en société…

## Supervision

Bon c'est bien beau, mais avec tout ça les certificats sont valables que 90j
(oui mort à CertPatrol :-() mais pour le coup il va donc falloir superviser
tout cela.

### Via Certificate Monitor

Si vous utilisez le sympa [certificatemonitor](https://certificatemonitor.org/) ([code source](https://github.com/RaymiiOrg/certificate-expiry-monitor)) de Raymii, il suffit alors d'ajouter vos FQDN dans l'interface. Et ainsi recevoir une notification avant l'expiration de vos certificats.

### Via Checkmk

Sinon si vous avez déjà une bonne veille supervision à base de nagios ou checkmk ou compatible, il suffit d'ajouter les checks via le script `check_http`.

Dans la conf `main.mk` de checkmk:

```checkmk
# /etc/checkmk/main.mk
legacy_checks = [
  ## Gitoyen
  ( ( "check-certificate!gitoyen.net", "Certificate Gitoyen - Letsencrypt", True), ['baloo.gitoyen.net']),
  ( ( "check-certificate!www.gitoyen.net", "Certificate www Gitoyen - Letsencrypt", True), ['baloo.gitoyen.net']),
]
```

Et ne pas oublier la `check_command` associée (on émet un warning lorsque que le certificat expire à J-30 et un critical à J-7)

```nagios
# /etc/checkmk/conf.d/commands_extra.mk
define command {
  command_name    check-certificate
  command_line    $USER1$/check_http -H $ARG1$ -C 30,7 --sni
}
```

## Renouvellement

Il existe des scripts de renouvellement automatique:

* [letsencrypt-renew](https://github.com/octopuce/octopuce-goodies/tree/master/letsencrypt-renew)
* [letsencrypt.sh](https://github.com/lukas2511/letsencrypt.sh)

Mais je ne sais pas encore quoi en penser, pour le moment je réagis lorsque que
le check passe en warning avec ce mini script bash.

```bash
#!/bin/bash

account='gitoyen'
certs='gitoyen.net www.gitoyen.net'

pushd /etc/letsencrypt
  for cert in $certs
  do
    echo "##### ${cert} #####"
    acme_tiny.py --account-key ./private/gitoyen.key --csr ./csr/${cert}.csr --acme-dir /etc/letsencrypt/challenges/${cert}/ > ./certs/${cert}.crt
    cat ./certs/${cert}.crt ./pem/intermediate.pem > ./pem/${cert}.pem
  done
popd
systemctl restart nginx
```

## Chocolat

Même si le modèle des CA est bancal, il n'y a plus de raison maintenant de ne pas proposer du HTTPS partout !
