si:infra:acme-dns

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
si:infra:acme-dns [2025/04/24 21:34] – supprimée - modification externe (Unknown date) 127.0.0.1si:infra:acme-dns [2025/04/24 21:34] (Version actuelle) – ↷ Page déplacée de si:acme-dns à si:infra:acme-dns vcalame
Ligne 1: Ligne 1:
 +====== Certificats Letsencrypt via DNS ======
 +
 +La création de certificats Letsencrypt se fait traditionnellement par authentification HTTP en utilisant le client [[https://certbot.eff.org/|certbot]] de l'EFF et ça permet de couvrir la plupart des besoins.
 +Mais il est également possible de créer des certificats Letsencrypt par authentification DNS pour des cas d'usages moins simples et surtout c'est le seul moyen actuellement d'obtenir des certificats Wildcard.
 +
 +Pour les besoins du service XMPP c'est un certificat Wildcard qui a été créé en utilisant le client [[https://dehydrated.io/|dehydrated]].
 +
 +Ici j'explique ce qui a été fait pour mettre cela en place la première fois afin de pouvoir reproduire la démarche ailleurs. Les fichiers de configuration et autres scripts présentés ici ne correspondent plus forcément à la réalité au moment où vous lisez ces lignes, et c'est pas grave hein.
 +
 +===== Principe de fonctionnement =====
 +
 +Le principe du fonctionnement de la validation est similaire que l’on passe par validation DNS-01 ou validation HTTP-01 :
 +
 +  * votre client ACME (certbot, dehydrated, autre) contacte le serveur CA ACME
 +  * le serveur ACME lui retourne une chaine de caractères générée pour l’occasion
 +  * le client ACME rend alors cette chaine de caractères publique :
 +    * soit à une URL http://ledomaine.net/.well-known/acme-challenge dans le cas d’une validation HTTP-01
 +    * soit dans un enregistrement DNS de type TXT *_acme-challenge.ledomaine.net* dans le cas d’une validation par DNS-01
 +  * le client ACME prévient le serveur ACME que ça a été publié
 +  * le serveur ACME est désormais sûr qu’il avait affaire à quelqu’un de légitime valide alors le requête, génère le certificat demandé initialement et le transmet au client ACME
 +
 +Il faut donc pouvoir publier un enregistrement DNS de type TXT sur nos serveurs Bind (en fait sur un seul ça suffit).
 +
 +===== Configurer Bind pour les mises à jour dynamiques =====
 +
 +Techniquement il est possible de configurer directement la zone parinux.org pour accepter les changements dynamiques, avec la commande //nsupdate// par exemple. Mais quand on active cette option alors Bind réorganise complètement le fichier zone à sa façon illisible. Et ça c'est pénible pour les adminsys. Et puis en plus ce fichier zone ne doit plus être édité manuellement comme l'indique la page //man nsupdate// :
 +
 +> Zones that are under dynamic control via nsupdate or a DHCP server should not be edited by hand. Manual edits could conflict with dynamic updates and cause data to be lost.
 +
 +Donc le plus simple c'est créer une délégation DNS uniquement pour //_acme-challenge.parinux.org//. C'est ce qui est suggéré dans la [[https://github.com/lukas2511/dehydrated/wiki/example-dns-01-nsupdate-script|doc de dehydrated]].
 +
 +Donc pour cela dans le fichier de zone de //parinux.org// ajouter l'enregistrement suivant (et penser à modifier le serial dans le foulée) :
 +
 +<code>
 +_acme-challenge NS ns1.parinux.org.
 +</code>
 +
 +Créer une clef secrète qui ne sera partagée qu'entre le serveur Bind et les machines ayant besoin de publier des enregistrements DNS dynamiquement.
 +
 +<code>
 +cd /tmp
 +dnssec-keygen -r /dev/urandom -a hmac-sha512 -b 128 -n HOST rndc-acme
 +</code>
 +
 +La clef secrète se trouve dans un fichier nommé ///tmp/Krndc-acme.xxxx// et ressemble à quelque chose comme ça : //cZh3zV8Uf7aIOxjv8ZA8iA==//.
 +Ensuite, créer un fichier ///etc/bind/rndc-acme.key// :
 +
 +<file /etc/bind.rndc-acme.key>
 +key "rndc-acme" {
 +        algorithm hmac-sha512;
 +        secret "cZh3zV8Uf7aIOxjv8ZA8iA==";
 +};
 +</file>
 +
 +Dans le fichier ///etc/bind/named.conf.local// déclarer cette nouvelle zone et inclure le fichier de la clef secrète :
 +
 +<file /etc/bind/named.conf.local.sample>
 +include "/etc/bind/rndc-acme.key";
 +zone "_acme-challenge.parinux.org" {
 +  file "/var/cache/bind/_acme-challenge.parinux.org.zone";
 +  type master;
 +  masterfile-format text;
 +  allow-update { key "rndc-acme"; };
 +};
 +</file>
 +
 +Il faut maintenant peupler un minimum cette zone dynamique (un SOA et un NS). Créer le fichier ///var/cache/bind/_acme-challenge.parinux.org.zone// comme ceci :
 +
 +<file /var/cache/bind/_acme-challenge.parinux.org.zone>
 +_acme-challenge.parinux.org. IN SOA troll4.parinux.org. admin.parinux.org. (
 +       2018112200
 +       86400
 +       300
 +       1814400
 +       86400
 +)
 +  NS   troll4.parinux.org.
 +</file>
 +
 +Permettre à Bind de modifier ce fichier :
 +
 +<code>
 +sudo chown bind: /var/cache/bind/_acme-challenge.parinux.org.zone
 +</code>
 +
 +Puis ''%%sudo systemctl restart bind9%%''.
 +
 +Maintenant on peut tester la publication dynamique. On peut déjà vérifier qu'on peut manuellement ajouter/modifier/supprimer l'enregistrement DNS depuis troll4 elle-même (par la suite il faudra pouvoir faire le même chose depuis un autre machine ayant la clef secrète).
 +
 +<code>
 +sudo nsupdate -k /etc/bind/rndc.key <<EOF
 +server 127.0.0.1
 +update delete _acme-challenge.parinux.org
 +update add _acme-challenge.parinux.org 8000 IN TXT "coucou tout va bieng ?"
 +send
 +EOF
 +</code>
 +
 +Puis
 +
 +<code>
 +dig +short _acme-challenge.parinux.org
 +</code>
 +Voilà. Ça, c'est fait.
 +
 +===== Certificat Wildcard avec dehydrated =====
 +
 +Sur la machine ayant besoin d'un certificat Wildcard (ici la machine xmpp-1 hébergeant ejabberd) créer le fichier ///etc/parinux/rndc-acme.key// contenant la même clef secrète que sur le serveur Bind :
 +
 +<file etc/parinux/rndc-acme.key>
 +key "rndc-acme" {
 +        algorithm hmac-sha512;
 +        secret "cZh3zV8Uf7aIOxjv8ZA8iA==";
 +};
 +</file>
 +
 +<code>sudo apt install dehydrated dnsutils ntp</code>
 +
 +Temporairement, le temps de tester la configuration on va utiliser le serveur ACME de staging.
 +
 +<code>echo 'CA="https://acme-staging-v02.api.letsencrypt.org/directory"' | sudo tee /etc/dehydrated/conf.d/staging.sh</code>
 +
 +On configure dehydrated pour faire de la validation DNS-01 dans le fichier ///etc/dehydrated/conf.d/auth-dns.sh// :
 +
 +<file /etc/dehydrated/conf.d/auth-dns.sh>
 +CHALLENGETYPE="dns-01"
 +HOOK=/etc/dehydrated/hook-custom.sh
 +</file>
 +
 +On créé le script hook ///etc/dehydrated/hook-custom.sh// :
 +
 +<file>
 +#!/usr/bin/env bash
 +
 +set 
 +set -u
 +set -o pipefail
 +
 +NSUPDATE="nsupdate -k /etc/parinux/rndc-acme.key"
 +DNSSERVER="192.168.2.55"  # l'IP de troll4, le serveur DNS master
 +TTL=300
 +
 +case "$1" in
 +    "deploy_challenge")
 +        printf "server %s\nupdate add _acme-challenge.%s. %d in TXT \"%s\"\nsend\n" "${DNSSERVER}" "${2}" "${TTL}" "${4}" | $NSUPDATE
 +        ;;
 +    "clean_challenge")
 +        printf "server %s\nupdate delete _acme-challenge.%s. %d in TXT \"%s\"\nsend\n" "${DNSSERVER}" "${2}" "${TTL}" "${4}" | $NSUPDATE
 +        ;;
 +    "deploy_cert")
 +        # on concatène le certificat et la clef pour ejabberd. C'est pas obligatoire mais c'est plus simple.
 +        cat /var/lib/dehydrated/certs/parinux.org/fullchain.pem /var/lib/dehydrated/certs/parinux.org/privkey.pem > /etc/ejabberd/parinux.org.ejabberd.pem
 +        sudo -u ejabberd ejabberdctl reload_config
 +
 +        ;;
 +    "unchanged_cert")
 +        # do nothing for now
 +        ;;
 +    "startup_hook")
 +        # do nothing for now
 +        ;;
 +    "exit_hook")
 +        # do nothing for now
 +        ;;
 +esac
 +
 +exit 0
 +</file>
 +
 +Il faut rendre ce script exécutable :
 +
 +<code>sudo chmod +x /etc/dehydrated/hook-custom.sh</code>
 +
 +On indique le ou les domaines qu’on souhaite dans ///etc/dehydrated/domains.txt// :
 +
 +<file /etc/dehydrated/domains.txt>
 +parinux.org *.parinux.org
 +</file>
 +
 +La première fois :
 +
 +<code bash>dehydrated --register --accept-terms</code>
 +
 +Puis on génère ou régénère les certificats :
 +
 +<code bash>dehydrated -c</code>
 +
 +Si tout s'est bien passé, alors pour peut générer un vrai certificat cette fois en supprimant le fichier ///etc/dehydrated/conf.d/staging.sh// :
 +
 +<code>
 +rm /etc/dehydrated/conf.d/staging.sh
 +dehydrated --register --accept-terms
 +dehydrated -c --force # --force pour écraser le précédent certificat staging
 +</code>
 +
 +Il faut maintenant créer une tâche cron pour renouveler le certificat automatiquement.
 +On créé le fichier ///etc/cron.weekly/acme-renew// suivant :
 +
 +<file /etc/cron.weekly/acme-renew>
 +#! /bin/bash
 +/usr/bin/dehydrated -c 
 +</file>
 +
 +Et on le rend exécutable :
 +
 +<code>chmod +x /etc/cron.weekly/acme-renew</code>
 +
 +===== Créer le certificat avec Certbot =====
 +
 +Cette méthode nécessite le plugin [[https://certbot-dns-rfc2136.readthedocs.io/en/stable/|certbot-dns-rfc2136]].
 +
 +Installer les paquets nécessaires :
 +
 +<code>
 +sudo apt install certbot python3-certbot-dns-rfc2136
 +</code>
 +
 +Définir les variables qui seront utilisées par la suite :
 +
 +<code>
 +export DOMAIN=mondomaine.net
 +export DNSSERVER=x.x.x.x
 +</code>
 +
 +Créer le fichier ///etc/letsencrypt/nsupdate-credentials-${DOMAIN}.ini// :
 +
 +<code>
 +cat << EOF | sudo tee /etc/letsencrypt/nsupdate-credentials-${DOMAIN}.ini
 +dns_rfc2136_server = ${DNSSERVER}
 +dns_rfc2136_port = 53
 +dns_rfc2136_name = rndc-acme-challenge.${DOMAIN}-key
 +dns_rfc2136_secret = xxxxxxxxxxxxxxxx
 +dns_rfc2136_algorithm = HMAC-SHA256
 +EOF
 +</code>
 +
 +Avec des permissions restreintes :
 +
 +<code>sudo chmod 600 /etc/letsencrypt/nsupdate-credentials-${DOMAIN}.ini</code>
 +
 +Créer le script hook a exécuter après chaque renouvellement de certificat :
 +
 +<code>
 +cat << EOF | sudo tee /etc/letsencrypt/renewal-hooks/post/reload-services.sh
 +#! /bin/sh
 +systemctl reload nginx
 +EOF
 +</code>
 +
 +Et le rendre exécutable :
 +
 +<code>sudo chmod u+x /etc/letsencrypt/renewal-hooks/post/reload-services.sh</code>
 +
 +Puis lancer certbot certonly :
 +
 +<code>
 +certbot certonly \
 + --expand --non-interactive --agree-tos --email root@${DOMAIN} \
 + --dns-rfc2136 \
 + --dns-rfc2136-propagation-seconds 3 \
 + --dns-rfc2136-credentials /etc/letsencrypt/nsupdate-credentials-${DOMAIN}.ini \
 + -d "*.${DOMAIN}" -d ${DOMAIN} \
 + --post-hook /etc/letsencrypt/renewal-hooks/post/reload-services.sh
 +</code>