====== SSL Let's Encrypt automatisé (DNS-01 + API Gandi) — renouvellement sans intervention ====== by thenoiser / apo33 **Objectif** : remplacer le renouvellement manuel du certificat Let's Encrypt (challenge DNS-01 à copier-coller à la main tous les ~60-90 jours) par un système entièrement automatisé, piloté par l'API Gandi, avec un token valable jusqu'à 1 an. Ce tutoriel fait suite à/remplace la méthode manuelle décrite dans **"Comment faire sa web radio"** (renouvellement manuel via ''certbot certonly --manual --preferred-challenges dns''). Le principe reste le DNS-01 (pas besoin du port 80 ouvert, fonctionne même derrière un reverse proxy ou sur une machine sans exposition web directe), mais la pose/suppression du challenge TXT est déléguée à l'API Gandi. ===== 1. Créer un jeton d'accès personnel (PAT) Gandi ===== Interface Gandi → Paramètres → **Jetons d'accès personnels** → Créer. - **Nom** : un nom explicite (ex: ''certbot-dns-automation'') - **Organisation** : **vérifier qu'on est bien positionné sur l'organisation propriétaire du domaine** (peut différer du compte personnel connecté — se tromper d'organisation donne un token qui "marche" mais ne voit pas le bon domaine, erreur 403 silencieuse) - **Expire dans** : 1 an (maximum autorisé par Gandi) - **Ressources du jeton d'accès** : "Restreint aux produits sélectionnés" → cocher uniquement le domaine concerné - **Permissions**, section **Domaines** (bien descendre jusqu'à cette section précise — ne pas confondre avec la section "Organisation" tout en haut, dont les cases se ressemblent) : * ✅ **Voir et renouveler les domaines** (lecture, indispensable — sans elle, l'API renvoie une erreur "Unable to get base domain") * ✅ **Gérer la configuration technique des domaines** (écriture DNS, pour poser/retirer le challenge TXT) - Ne rien cocher d'autre (Facturation, Web Hosting, Cloud, Certificats SSL, Boîte Mail — aucun rapport, principe de moindre privilège) Copier le token immédiatement (affiché une seule fois). **Test de validation rapide**, avant même de toucher à certbot : curl -H "Authorization: Bearer VOTRE_TOKEN" \ https://api.gandi.net/v5/livedns/domains/votredomaine.org Doit renvoyer du JSON avec les infos du domaine. Une erreur ''403 Forbidden'' = mauvaise organisation ou permissions incomplètes — retourner corriger le token (Gandi ne permet pas d'éditer un token existant, il faut le supprimer et en recréer un). ===== 2. Installer certbot + plugin Gandi (via pipx, isolé) ===== Ne pas utiliser ''pip install --break-system-packages'' en direct : conflit quasi garanti avec les paquets système (''cffi'' notamment). Utiliser ''pipx'', qui isole proprement l'environnement. sudo apt install -y pipx sudo pipx install certbot sudo pipx inject certbot certbot-dns-gandi sudo pipx ensurepath Si un ''certbot'' système existe déjà (''/usr/bin/certbot'', installé via ''apt''), pipx ne le remplace **pas** automatiquement sur le PATH — il faut appeler explicitement le binaire pipx : which certbot # montre souvent encore /usr/bin/certbot sudo /root/.local/bin/certbot plugins # vérifie que dns-gandi apparaît bien ici **Toujours utiliser ''/root/.local/bin/certbot'' explicitement** dans la suite de ce tuto (obtention, renouvellement, timer) pour être sûr de viser la bonne installation. ===== 3. Fichier de credentials ===== sudo mkdir -p /etc/letsencrypt sudo nano /etc/letsencrypt/gandi.ini dns_gandi_token = VOTRE_TOKEN Le nom de la clé est **''dns_gandi_token''** (pas ''dns_gandi_api_token'', erreur courante qui donne "Missing property in credentials configuration file"). sudo chmod 600 /etc/letsencrypt/gandi.ini sudo chown root:root /etc/letsencrypt/gandi.ini ===== 4. Obtenir le certificat ===== sudo /root/.local/bin/certbot certonly \ --authenticator dns-gandi \ --dns-gandi-credentials /etc/letsencrypt/gandi.ini \ --key-type ecdsa \ --deploy-hook "systemctl reload nginx" \ -d sousdomaine.votredomaine.org Si l'erreur suivante apparaît : Incorrect TXT record "..." found at _acme-challenge.sousdomaine... Hint: ... try increasing --dns-gandi-propagation-seconds (currently 10 seconds) C'est un simple problème de timing de propagation DNS, pas un problème de credentials. Relancer avec un délai plus long : sudo /root/.local/bin/certbot certonly \ --authenticator dns-gandi \ --dns-gandi-credentials /etc/letsencrypt/gandi.ini \ --key-type ecdsa \ --dns-gandi-propagation-seconds 45 \ -d sousdomaine.votredomaine.org \ --force-renewal Une fois un essai réussi avec un délai donné, cette valeur est **automatiquement sauvegardée** dans ''/etc/letsencrypt/renewal/.conf'' (''dns_gandi_propagation_seconds = 45'') — les renouvellements futurs en bénéficient sans réglage supplémentaire. ===== 5. Automatiser le renouvellement (timer systemd dédié) ===== Ne pas se fier au timer ''certbot.timer''/''snap.certbot.renew.timer'' déjà présent sur le système : il utilise le **certbot système** (''/usr/bin/certbot''), qui ne connaît pas le plugin ''dns-gandi''. Créer un timer dédié qui pointe explicitement vers le certbot pipx. sudo nano /etc/systemd/system/certbot-renew.service [Unit] Description=Certbot renewal (pipx, dns-gandi) [Service] Type=oneshot ExecStart=/root/.local/bin/certbot renew --quiet sudo nano /etc/systemd/system/certbot-renew.timer [Unit] Description=Run certbot renewal twice daily [Timer] OnCalendar=*-*-* 03,15:00:00 RandomizedDelaySec=1800 Persistent=true [Install] WantedBy=timers.target sudo systemctl daemon-reload sudo systemctl enable --now certbot-renew.timer sudo systemctl list-timers | grep certbot-renew Désactiver l'ancien timer système pour éviter tout conflit/doublon : sudo systemctl disable --now certbot.timer 2>/dev/null sudo systemctl disable --now snap.certbot.renew.timer 2>/dev/null ===== 6. Valider ===== sudo /root/.local/bin/certbot renew --dry-run Doit afficher ''Congratulations, all simulated renewals succeeded''. ===== 7. Cas particulier : utiliser ce certificat sur un vieux serveur Apache (SME/CentOS) ===== **Section sensible.** Si le certificat doit être utilisé par un Apache ancien (ex: Apache 2.4.6 / OpenSSL 1.0.2k sur CentOS 7, cas du serveur SME), le fichier ''fullchain.pem'' moderne (certificat + tous les intermédiaires concaténés) peut faire planter ''mod_ssl'' au démarrage avec : AH01903: Failed to configure CA certificate chain! AH02312: Fatal error initialising mod_ssl, exiting. Ce n'est pas un problème de contenu invalide, mais de format non supporté par cette version d'OpenSSL — **et comme SME régénère toute la config Apache d'un coup à chaque événement, cette erreur peut interrompre TOUS les sites du serveur**, pas seulement celui concerné. ==== 7.1 Séparer le certificat de la chaîne ==== Ne **jamais découper ''fullchain.pem'' à la main avec ''awk''/''sed''** (source d'erreurs de parsing PEM très facile) — utiliser directement les fichiers déjà séparés par certbot : # Sur la machine où le cert a été obtenu : sudo cat /etc/letsencrypt/live/sousdomaine.votredomaine.org/cert.pem # certificat seul (feuille) sudo cat /etc/letsencrypt/live/sousdomaine.votredomaine.org/chain.pem # chaîne d'intermédiaires seule sudo cat /etc/letsencrypt/live/sousdomaine.votredomaine.org/privkey.pem # clé privée Copier ces trois fichiers tels quels vers le serveur cible (ex: via heredoc %%cat > fichier << 'EOF' ... EOF%%), dans un dossier dédié ne touchant à aucun certificat existant : mkdir -p /etc/pki/tls/certs/sousdomaine/ # coller cert.pem, chain.pem, privkey.pem chmod 600 /etc/pki/tls/certs/sousdomaine/privkey.pem chmod 644 /etc/pki/tls/certs/sousdomaine/cert.pem /etc/pki/tls/certs/sousdomaine/chain.pem chown root:root /etc/pki/tls/certs/sousdomaine/* Valider en pur ''openssl'', sans toucher à Apache, avant toute intégration : openssl x509 -in /etc/pki/tls/certs/sousdomaine/cert.pem -noout -subject -dates openssl crl2pkcs7 -nocrl -certfile /etc/pki/tls/certs/sousdomaine/chain.pem | openssl pkcs7 -print_certs -noout La deuxième commande doit lister 2-3 certificats intermédiaires proprement, sans erreur ''bad end line'' ni ''no start line''. ==== 7.2 Intégrer via un fragment templates-custom (SME) ==== Ne jamais éditer ''httpd.conf'' directement (régénéré automatiquement). Créer un fragment isolé, conditionné au domaine concerné uniquement : mkdir -p /etc/e-smith/templates-custom/etc/httpd/conf/httpd.conf/WebAppVirtualHost cat > /etc/e-smith/templates-custom/etc/httpd/conf/httpd.conf/WebAppVirtualHost/06SSLCertPersonnalise << 'EOF' { my $name = $domain->key; if ($name eq 'sousdomaine.votredomaine.org') { $OUT .= " SSLCertificateFile /etc/pki/tls/certs/sousdomaine/cert.pem\n"; $OUT .= " SSLCertificateKeyFile /etc/pki/tls/certs/sousdomaine/privkey.pem\n"; $OUT .= " SSLCertificateChainFile /etc/pki/tls/certs/sousdomaine/chain.pem\n"; } } EOF La condition %%if ($name eq '...')%% isole strictement ce fragment à un seul domaine — aucun risque pour les autres vhosts, actuels ou futurs, qui utilisent le même template ''WebAppVirtualHost''. ==== 7.3 Régénérer SANS redémarrer, puis tester en isolation ==== # Backup de sécurité avant toute manip cp /etc/httpd/conf/httpd.conf /root/httpd.conf.backup-$(date +%Y%m%d-%H%M%S) # Régénère le fichier SANS toucher au service actif /sbin/e-smith/expand-template /etc/httpd/conf/httpd.conf httpd -t **Test isolé obligatoire avant tout redémarrage réel** (voir détail complet dans le tuto PeerTube, section 7.5) : cp /etc/httpd/conf/httpd.conf /tmp/httpd-test.conf sed -i \ -e 's|^Listen 0\.0\.0\.0:80|Listen 0.0.0.0:8081|' \ -e 's|^Listen 0\.0\.0\.0:443|Listen 0.0.0.0:8444|' \ -e 's|^Listen \[::\]:80|Listen [::]:8081|' \ -e 's|^Listen \[::\]:443|Listen [::]:8444|' \ -e 's|^PidFile.*|PidFile /tmp/httpd-test.pid|' \ /tmp/httpd-test.conf /usr/sbin/httpd -f /tmp/httpd-test.conf -DFOREGROUND & sleep 3 jobs # "En cours d'exécution" = le certificat charge correctement tail -15 /var/log/httpd/error_log kill %1 Seulement si ce test réussit, appliquer pour de vrai : systemctl restart httpd-e-smith.service # nom exact du service à vérifier au préalable systemctl status httpd-e-smith.service --no-pager curl -Ik http://domaineprincipal.org # confirmer que le site principal répond toujours **En cas d'échec** (service qui refuse de démarrer) : supprimer immédiatement le fragment custom fautif, régénérer sans lui, relancer le service. Le site principal doit revenir en quelques secondes. rm /etc/e-smith/templates-custom/etc/httpd/conf/httpd.conf/WebAppVirtualHost/06SSLCertPersonnalise /sbin/e-smith/signal-event domain-modify sousdomaine.votredomaine.org systemctl restart httpd-e-smith.service ===== 8. Checklist de maintenance annuelle ===== - Le token Gandi expire au bout d'1 an max → prévoir son renouvellement avant expiration (Gandi ne prévient pas forcément), sinon le prochain cycle de renouvellement certbot échouera silencieusement au prochain passage du timer - Vérifier périodiquement ''sudo systemctl list-timers | grep certbot'' pour confirmer que le timer est toujours actif - Si un certificat est copié manuellement sur une autre machine (cas 7.), penser à resynchroniser ce fichier à chaque renouvellement (~tous les 90 jours) — ce n'est pas automatique dans ce montage