---
categories: web security
title: Howto Let's Encrypt
...

* Documentation : <https://letsencrypt.org/docs/>
* Documentation Certbot : <https://certbot.eff.org/docs/>
* Rôle Ansible : <https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/certbot/>

[Let's Encrypt](https://letsencrypt.org/) est une autorité de certification fournissant des certificats [SSL](HowtoSSL) valables 90 jours gratuitement et automatiquement.
L'obtention d'un certificat signé s'effectue via le protocole [ACME (Automated Certificate Management Environment)](https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment),
ce qui nécessite d'avoir temporairement un serveur HTTP sur le port TCP/80 de l'adresse IP correspondant au _Common Name_ du certificat à signer.
Le certificat intermédiaire de Let's Encrypt est également signé par l'autorité de certification [IdenTrust](https://www.identrust.com/) préinstallée dans la plupart des navigateurs.


## Installation

On installe certbot et ses dépendances :

~~~
# apt install certbot

$ certbot --version
certbot 0.31.0
~~~

### 

### Configuration du Serveur Web

Pour vérifier que la demande est légitime, Let's Encrypt doit accéder à un fichier temporaire via HTTP sur le port TCP/80 de l'adresse IP correspondant au _Common Name_ du certificat à signer, du type *http://www.example.com/.well-known/acme-challenge/hNgN_ygEFf-XiHJd6VErwNbfRcpP2CbJmIN3qpJXZOQ*

#### Apache

Configuration :

~~~{.apache}
<IfModule jk_module>
    SetEnvIf Request_URI "/.well-known/acme-challenge/*" no-jk
</IfModule>
<IfModule proxy_module>
    ProxyPass /.well-known/acme-challenge/ !
</IfModule>
Alias /.well-known/acme-challenge /var/lib/letsencrypt/.well-known/acme-challenge
<Directory "/var/lib/letsencrypt/.well-known/acme-challenge">
    Options -Indexes
    Allow from all
    Require all granted
</Directory>
~~~

#### Nginx

Préférez importer le snippet `/etc/nginx/snippets/letsencrypt.conf`, mais si une configuration manuelle est nécessaire:

Configuration Jessie :

~~~
location ~ /.well-known/acme-challenge {
    alias /var/lib/letsencrypt/.well-known/acme-challenge;
    try_files $uri =404;
    allow all;
}
~~~

Configuration Stretch :

~~~
location ~ /.well-known/acme-challenge {
    alias /var/lib/letsencrypt/;
    try_files $uri =404;
    allow all;
}
~~~


## Génération du certificat

~~~
$ certbot certonly --webroot --webroot-path /var/lib/letsencrypt/ -d example.com,www.example.com --cert-name example.com
~~~

Pour interroger les serveurs de "staging" de Let's Encrypt – afin de tester la validation d'une demande sans risque – on peut ajouter l'option `--test-cert`. Tout le processus est identique à l'appel normal, mais le certificat généré sera non-signé par une autorité reconnue.

Pour faire les actions normalement sauf la génération du certificat et la modification locale des fichiers, il est possible d'utiliser l'option `--dry-run`.

En combinant `--test-cert` et `--dry-run` on peut donc faire un test de génération de certificat sans modification locale et sans risque de pénalisation en cas d'échex répétés.

## Renouvellement du certificat

Les certificats Let's Encrypt sont valables 90 jours. Un timer systemd roule automatiquement à chaque jour pour revalider le certificat.

Pour effectuer des actions post-renouvellement (recharger le service web, par exemple), on repose sur les hooks. Ils sont automatiquement exécutés s'ils sont détectés et exécutables.

Pour Apache (`/etc/letsencrypt/renewal-hooks/deploy/apache.sh`):

~~~{.bash}
#!/bin/sh

error() {
    >&2 echo "${PROGNAME}: $1"
    exit 1
}
debug() {
    if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then
        >&2 echo "${PROGNAME}: $1"
    fi
}
daemon_found_and_running() {
    test -n "$(pidof apache2)" && test -n "${apache2ctl_bin}"
}
config_check() {
    ${apache2ctl_bin} configtest > /dev/null 2>&1
}
letsencrypt_used() {
    grep -q -r -E "letsencrypt" /etc/apache2/
}
main() {
    if daemon_found_and_running; then
        if letsencrypt_used; then
            if config_check; then
                debug "Apache detected... reloading"
                systemctl reload apache2
            else
                error "Apache config is broken, you must fix it !"
            fi
        else
            debug "Apache doesn't use Let's Encrypt certificate. Skip."
        fi
    else
        debug "Apache is not running or missing. Skip."
    fi
}

readonly PROGNAME=$(basename "$0")
readonly VERBOSE=${VERBOSE:-"0"}
readonly QUIET=${QUIET:-"0"}

readonly apache2ctl_bin=$(command -v apache2ctl)

main
~~~

Pour Nginx (`/etc/letsencrypt/renewal-hooks/deploy/nginx.sh`) :

~~~{.bash}
#!/bin/sh

error() {
    >&2 echo "${PROGNAME}: $1"
    exit 1
}
debug() {
    if [ "${VERBOSE}" = "1" ] && [ "${QUIET}" != "1" ]; then
        >&2 echo "${PROGNAME}: $1"
    fi
}
daemon_found_and_running() {
    test -n "$(pidof nginx)" && test -n "${nginx_bin}"
}
config_check() {
    ${nginx_bin} -t > /dev/null 2>&1
}
letsencrypt_used() {
    grep -q --dereference-recursive -E "letsencrypt" /etc/nginx/sites-enabled
}
main() {
    if daemon_found_and_running; then
        if letsencrypt_used; then
            if config_check; then
                debug "Nginx detected... reloading"
                systemctl reload nginx
            else
                error "Nginx config is broken, you must fix it !"
            fi
        else
            debug "Nginx doesn't use Let's Encrypt certificate. Skip."
        fi
    else
        debug "Nginx is not running or missing. Skip."
    fi
}

readonly PROGNAME=$(basename "$0")
readonly VERBOSE=${VERBOSE:-"0"}
readonly QUIET=${QUIET:-"0"}

readonly nginx_bin=$(command -v nginx)

main
~~~

D'autres hooks sont disponibles dans le [dépôt Ansible](https://gitea.evolix.org/evolix/ansible-roles/src/branch/stable/certbot/files/hooks).

Pour exécuter un hook manuellement, par exemple après la création du certificat :

~~~
# VERBOSE=1 RENEWED_LINEAGE=/etc/letsencrypt/live/<cert-name> /etc/letsencrypt/renewal-hooks/deploy/<hook>.sh
~~~

## Lister les certificats

~~~
# certbot certificates
~~~

## Suppression d'un certificat

~~~
# certbot delete --cert-name www.example.com
~~~


## Automatisation

Pour automatiser l'installation de _Certbot_ et la génération/renouvellement de certificats, nous utilisons des scripts : [Evoacme](https://forge.evolix.org/projects/ansible-roles/repository/revisions/stable/show/certbot).


## FAQ

### Certificat Wildcard ou EV

Let's Encrypt permet de générer un certificat Wildcard (via challenge DNS) mais pas EV (Extended Validation).

### Je n'ai pas de port TCP/80 accessible

Let's Encrypt vérifie la légitimité de la demande en faisant une requête DNS sur l'enregistrement DNS correspondant au _Common Name_ du certificat à signer (si il n'y a pas d'enregistrement DNS propagé mondialement, c'est donc impossible d'utiliser Let's Encrypt) et effectue une requête HTTP vers le port TCP/80 de l'adresse IP : il faut donc obligatoirement avoir temporairement un service HTTP sur le port TCP/80... au moins temporairement.

### Challenge DNS

Si notre serveur web n'est pas accessible de l'extérieur, on peut utiliser un challenge par DNS plutôt que via une page HTTP.

~~~
$ certbot -d domain.example.com --manual --preferred-challenges dns certonly
~~~

Il suffira alors de créer l'entrée DNS que certbot affichera et continuer le processus interactif.


### Rate-limits

Doc officielle : <https://letsencrypt.org/docs/rate-limits/>

L'API de Let's Encrypt dispose un « rate limiting » afin d'éviter les abus.

- 20 certificats pour 1 domaine maximum par semaine. 1 domaine est la partie juste avant le TLD. www.example.com, le domaine est example.com. blog.exemple.fr, le domaine est exemple.fr ; Si vous faites 1 certificat par sous-domaines, vous êtes donc limités à 20 par semaine. Il est donc préférable de regrouper plusieurs sous-domaines dans un seul certificat (SAN). La limite du SAN est de 100 sous-domaines. La limite est donc de 2000 certificats pour 1 domaine par semaine si vous mettez 100 sous-domaines par certificats.
- La limite de renouvellement d'un certificat est de 5 par semaine. Si votre procédure de renouvellement automatique échoue, au bout de 5 jours vous allez être banni pendant 1 semaine.
- La limite d'échec de validation du challenge est de 5 par heures (par adresse IP et utilisateur enregistré par email) ;

Si vous échouez 5 fois à valider le challenge DNS, on peut penser qu'il suffit d'attendre 1h, sauf qu'en fait non. Le « bannissement » est pour 1 semaine !
