10 jours plus tard

laster13

Pour mondedie.fr, j'ai organisé le renouvellement de la manière suivante (à noter que dans notre cas, le serveur web et le serveur mail ne sont pas hébergés sur la même machine, donc j'ai du utiliser rsync et ssh) :

acme.sh \
 --issue \
 -w /mnt/docker/nginx/www/acme \
 -k 4096 \
 -d mondedie.fr \
 -d www.mondedie.fr \
 ...

acme.sh \
 --install-cert   -d mondedie.fr \
 --cert-file      /mnt/docker/nginx/ssl/live/mondedie.fr/cert.pem \
 --key-file       /mnt/docker/nginx/ssl/live/mondedie.fr/privkey.pem \
 --ca-file        /mnt/docker/nginx/ssl/live/mondedie.fr/chain.pem \
 --fullchain-file /mnt/docker/nginx/ssl/live/mondedie.fr/fullchain.pem \
 --reloadcmd      "docker-compose -f /docker/docker-compose.yml restart nginx && /usr/local/bin/update-mail-tlsa"
#!/bin/bash
# /usr/local/bin/update-mail-tlsa sur mondedie-web

DOMAIN="mondedie.fr"
LETS_ENCRYPT_LIVE_PATH="/mnt/docker/nginx/ssl/live/${DOMAIN}"
SLACK_HOOK=https://hooks.slack.com/services/xxxxxxxx/xxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxx

# Empreinte du certificat récupérée lors du renouvellement
# par acme.sh à partir de mondedie-web
fingerprint=$(openssl x509 -noout -in "${LETS_ENCRYPT_LIVE_PATH}/cert.pem" -fingerprint -sha256 | cut -c 20- | sed s/://g)

# Mise à jour des certificats du serveur mail
# Seul l'@IP de mondedie-web est autorisée à se connecter avec une clé SSH via l'utilisateur root sur mondedie-mail
rsync -Lvr "${LETS_ENCRYPT_LIVE_PATH}/" root@mondedie-mail.lan:"/mnt/docker/ssl/letsencrypt/live/mail.${DOMAIN}"

# Redémarrage du serveur mail suite au renouvellement des certificats
ssh root@mondedie-mail.lan docker-compose -f /docker/docker-compose.yml restart mailserver

# Mise à jour de l'enregistrement TLSA
ssh root@mondedie-mail.lan update-tlsa ${fingerprint,,}

curl --silent -X POST --data-urlencode 'payload={"channel":"#mondedie-flarum", "username":"acme-bot","text":"Le certificat SSL a été renouvelé sur mondedie-web et mondedie-mail. Les conteneurs nginx et mailserver ont été redémarrés automatiquement.","icon_emoji":":infomation_source:"}' $SLACK_HOOK >/dev/null 2>&1
#!/bin/bash
# /usr/local/bin/update-tlsa sur mondedie-mail

SLACK_HOOK=https://hooks.slack.com/services/xxxxxxxx/xxxxxxxxxxxx/xxxxxxxxxxxxxxxxxxxxx

# Empreinte du certificat récupérée lors du renouvellement
# par acme.sh à partir de mondedie-web
fingerprint=$1

domain="mondedie.fr"
zonename="db.${domain}"
zonefile="/mnt/docker/nsd/zones/${zonename}"
serial=$(date -d "+1 day" +'%Y%m%d%H')
tlsa_line_number=$(grep -n TLSA $zonefile | cut -d : -f 1)
tlsa_dns_record="_dane IN TLSA 3 0 1 ${fingerprint}"
expiration_date=$(date -d "+6 months" +'%Y%m%d%H%M%S')

# Mise à jour de la zone DNS avec un nouveau
# serial et un nouvel enregistrement TLSA
sed -i -e "s/20[0-9][0-9]\{7\} ; Serial/${serial} ; Serial/g" \
       -e "${tlsa_line_number}s/.*/${tlsa_dns_record}/" $zonefile

# On vérifie que la zone est valide avant de la signer
if docker exec nsd nsd-checkzone "$domain" /zones/"$zonename" | grep -q 'zone mondedie.fr is ok'; then
  echo "Execution de la commande : docker exec nsd signzone ${domain} ${expiration_date}"
  docker exec nsd signzone "$domain" "$expiration_date"
  curl --silent -X POST --data-urlencode 'payload={"channel":"#mondedie-flarum", "username":"acme-bot","text":"La zone DNS a été mise à jour et signée avec DNSSEC. Merci de vérifier la conformité 10 minutes après la notification avec : http://dnsviz.net/d/mondedie.fr/analyze/ et https://dnssec-debugger.verisignlabs.com/mondedie.fr","icon_emoji":":infomation_source:"}' $SLACK_HOOK >/dev/null 2>&1
  curl --silent -X POST --data-urlencode 'payload={"channel":"#mondedie-flarum", "username":"acme-bot","text":"'"zonefile=$zonefile, expiration_date=$expiration_date, serial=$serial, tlsa_dns_record=$tlsa_dns_record, tlsa_line_number=$tlsa_line_number"'","icon_emoji":":infomation_source:"}' $SLACK_HOOK >/dev/null 2>&1
else
  echo "La zone ${zonefile} n'est pas valide !"
  curl --silent -X POST --data-urlencode 'payload={"channel":"#mondedie-flarum", "username":"acme-bot","text":"Une erreur est survenue pendant la mise à jour de la zone DNS. Merci de vérifier la conformité avec la commande suivante : docker exec nsd nsd-checkzone '"$domain"' /zones/'"$zonename"'","icon_emoji":":rotating_light:"}' $SLACK_HOOK >/dev/null 2>&1
fi

Et ne pas oublier de mettre ceci dans chaque vhost nginx ayant un sous-domaine renouvelé automatiquement :

location ~ /\.well-known/acme-challenge {
  root /nginx/www/acme; # à modifier selon votre configuration
  allow all;
}

Je l'avais pas posté avant sur le forum parce que je l'avais pas suffisamment testé, j'étais sûr de moi à 95% 😃 Mais aujourd'hui le renouvellement a eu lieu et il n'y a eu aucun problème.

    A noter que j'ai mis le serial de la zone DNS à J+1 serial=$(date -d "+1 day" +'%Y%m%d%H') lors du renouvellement pour éviter un décrémentation invonlontaire si quelqu'un modifie la zone au même moment. Normalement ça devrait pas arriver parce que j'ai mis l'heure au format 24 heures dans le serial, mais bon, on est jamais trop prudent 😃

    laster13 tient moi au courant 😉

      Hardware Je te tiens au courant, par contre je vois que tu utilises du SAN plutôt que du Wildcard. Une raison particulière pour ce choix ?

      Je m'étais pas penché sur la méthode de renouvellement d'un certificat wildcard avec mon conteneur NSD, donc c'est pour ça que je suis resté sur un SAN. Ensuite j'ai vu ton tuto ^^

      Pour le moment, je laisse comme ça, j'ai pas vraiment d'utilité à mettre un wildcard sur mondedie.fr.

      Pas mal le script.
      Moi j'utilisais ça, mais pas avec acme.sh :

      f_check_certs() {
          cd /etc/letsencrypt/live
      
          for domain in `find * -type d`; do
              FULLCHAINFILE=/etc/letsencrypt/live/${domain}/fullchain.pem
              openssl x509 -checkend 864000 -noout -in "${FULLCHAINFILE}" &>/dev/null
      
              if [ $? == 0 ]; then
                  f_log INF "Certificate for ${domain} is good for another 10 days!"
              else
                  f_log INF "Time to renew ${domain}!"
      
                  f_log INF "Downloading cerbot image..."
                  docker pull certbot/certbot &>/dev/null
      
                  f_log INF "Stopping nginx..."
                  service nginx stop &>/dev/null
      
                  f_log INF "Renewing ${domain}..."
                  old_fingerprint=$(openssl x509 -noout -in "${FULLCHAINFILE}" -fingerprint -sha256 | cut -c 20- | sed s/://g | awk '{print tolower($0)}')
      
                  cd ${domain}
                  cp cert.pem old_cert.pem && cp chain.pem old_chain.pem && cp fullchain.pem old_fullchain.pem
      
                  docker run -ti --rm -p 80:80 -p 443:443 -v /etc/letsencrypt:/etc/letsencrypt certbot/certbot \
                      certonly --standalone --agree-tos -m wonderfall@targaryen.house \
                      --csr /etc/letsencrypt/live/${domain}/csr.der \
                      --cert-path /etc/letsencrypt/live/${domain}/cert.pem \
                      --chain-path /etc/letsencrypt/live/${domain}/chain.pem \
                      --fullchain-path /etc/letsencrypt/live/${domain}/fullchain.pem
      
                  new_fingerprint=$(openssl x509 -noout -in "${FULLCHAINFILE}" -fingerprint -sha256 | cut -c 20- | sed s/://g | awk '{print tolower($0)}')
      
                  f_log INF "Certificate renewed for ${domain}."
      
                  f_log INF "Starting nginx..."
                  service nginx start &>/dev/null
      
                  f_log INF "Updating TLSA records..."
                  sed -i "s/${old_fingerprint}/${new_fingerprint}/g" /home/docker/nsd/zones/db.${domain}
                  dns_update.sh ${domain}
      
                  f_log INF "Done!"
              fi
          done
      }
      domain=$1
      f_log INF "Domain set to ${domain}."
      old_serial=$(grep "Serial" /home/docker/nsd/zones/db.${domain} | grep -o '[0-9]\+')
      new_serial=${old_serial} && let new_serial++
      sed -i -e "s/${old_serial}/${new_serial}/g" /home/docker/nsd/zones/db.${domain}
      f_log INF "Incremented serial."
      f_log INF "Signing DNS zone..."
      docker exec nsd signzone ${domain} &>/dev/null
      f_log INF "Done."

      C'est moins propre mais ça faisait le boulot. Si tu fais ton truc pars plutôt ce qu'a fait Hardware je pense. Perso faut je revois ça pour traefik et comment dump les certs de acme.json d'abord...

      Au passage, je ne suis pas fan du wildcard, pourquoi ? C'est bien si l'utilises sur un seul serveur, mais sinon c'est une mauvaise pratique que d'éparpiller des clés privées à d'autres serveurs dont la compromission de l'un peut mener à la compromission de l'ensemble du domaine. Tous les sous-domaines sont à risque quand tu fais un wildcard (SPOF). Je ne dis pas qu'il faut éviter, je dis juste qu'il faut être prudent...

        Wonderfall Au passage, je ne suis pas fan du wildcard, pourquoi ? C'est bien si l'utilises sur un seul serveur, mais sinon c'est une mauvaise pratique que d'éparpiller des clés privées à d'autres serveurs dont la compromission de l'un peut mener à la compromission de l'ensemble du domaine. Tous les sous-domaines sont à risque quand tu fais un wildcard (SPOF). Je ne dis pas qu'il faut éviter, je dis juste qu'il faut être prudent...

        Je ploussoie la remarque sur le wildcard.

        Je viens de tester tes scripts pour le renouvellement des enregistrements TLSA et ca fonctionne nickel. C'est plus léger que le script que j'ai proposé dans le tuto, l'auteur inscrit dans la zone l'ancien TLSA en "old-dane-hash" que son script supprime après 24 heures. Il considère que ce délai est nécessaire pour la propagation des enregistrements.

        L'idée est qu'il n'y ait pas de "trou" dans le relais des 2 enregistrements TLSA, d’où ma question, est que le nouvel enregistrement est pris en compte immédiatement au niveau DNS ?

        Edit : c'est peu etre un bug mais du coup le plugin HTTPS+ Checker de firefox montre que dnssec et dane ne sont pas actif sur mondedie.

        A priori la valeur de la variable "$expiration_date" est trop grande, un warning sur zonemaster

        DNSSEC 	WARNING 	La signature (RRSIG) avec la clé de tag 27616 et couvrant les enregistrements de type(s) "DNSKEY" a une durée de validitée restante de 15770474 secondes, ce qui est trop long. 

          Cet addon semble buggé, il vaut mieux utiliser ça : https://www.dnssec-validator.cz/

          laster13 A priori la valeur de la variable "$expiration_date" est trop grande, un warning sur zonemaster

          Oui j'avais déjà vu ça, 6 mois c'est trop pour zonemaster.

          A la question de savoir si les enregistrement TLSA étaient pris en compte immédiatement, le site https://www.huque.com/bin/danecheck me confirme que oui.

          Du coup, le renew des certificats génére une signature DNSSEC pour 6 mois ce qui fait qu'on a plus besoin de se préoccuper de la signature de zone. Par ailleurs je ne connaissais pas ce système de notifications slack intégrable dans les scripts, je trouve ça génial, je vais mettre à jour tous mes scripts.

          J'ai testé ton script pour le wildcard en rajoutant les options suivantes :

          certbot certonly --rsa-key-size 4096 --manual --preferred-challenges=dns --manual-auth-hook /mnt/docker/nsd/leadd.sh --manual-cleanup-hook /mnt/docker/nsd/ledel.sh --renew-hook "service nginx reload" --post-hook "update-mail-tlsa" ...

          Cela fonctionne très bien et avec au final les paramètres renew

          # Options used in the renewal process
          [renewalparams]
          installer = None
          manual_cleanup_hook = /mnt/docker/nsd/ledel.sh
          authenticator = manual
          manual_public_ip_logging_ok = True
          pref_challs = dns-01,
          account = fd7a3565aba701........
          server = https://acme-v02.api.letsencrypt.org/directory
          manual_auth_hook = /mnt/docker/nsd/leadd.sh
          rsa_key_size = 4096
          post_hook = update-mail-tlsa
          renew_hook = service nginx reload

          Au final ton script est vraiment super et je souhaiterais adapter mon tuto avec si tu es d'accord de manière à pouvoir le garder disponible avant qu'il se noie parmi les autres posts. Si tu es ok pour déverrouiller le tuto 😉

          Reste plus qu'à trouver un moyen de renouveler automatiquement les clés ZSK et KSK mais surtout de pouvoir incrémenter le fingerprint auprès de Gandi sans avoir a le faire manuellement .. peut être avec l'Api livedns de gandi, je vais creuser ..

          Plutôt que de rentrer le fingerprint dans gandi, ne peut on pas tout simplement mettre un enregistrement (je sais pas quoi) 😗 dans le fichier de zone qui ferait le boulot ?

            laster13 Au final ton script est vraiment super et je souhaiterais adapter mon tuto avec si tu es d'accord de manière à pouvoir le garder disponible avant qu'il se noie parmi les autres posts. Si tu es ok pour déverrouiller le tuto 😉

            Je l'ai déverrouillé. Donc au final, les topics de discussions ne servent à rien si on déverrouille les tutos @Magicalex @hydrog3n @BXT

              laster13 Reste plus qu'à trouver un moyen de renouveler automatiquement les clés ZSK et KSK mais surtout de pouvoir incrémenter le fingerprint auprès de Gandi sans avoir a le faire manuellement .. peut être avec l'Api livedns de gandi, je vais creuser ..

              ça semble possible : https://doc.rpc.gandi.net/domain/faq.html#dnssec

              laster13 Plutôt que de rentrer le fingerprint dans gandi, ne peut on pas tout simplement mettre un enregistrement (je sais pas quoi) 😗 dans le fichier de zone qui ferait le boulot ?

              Non c'est pas possible, il n'y a que le registrar qui peut modifier la zone parent, et heureusement c'est un peu le coeur du fonctionnement de DNSSEC, c'est une chaine de confiance.

              Hardware Merci, j'ai terminé 😉 j'aurai pu inclure la modif dans le fil de discussion, sur l'instant j'y ai pas pensé.

              Ne serait-il pas possible d'autoriser l’accès au tuto verrouillé qu'à l'auteur lui même de manière à faciliter les éventuelles mises à jours. Je reconnais que pour les admins ça peut vite devenir une usine à gaz si tout le monde vous sollicite pour des modifs.

              D'autant que si l'on déverrouille tous les tutos et que des posts viennent s'incrémenter derrière, il ne sera plus possible d'éditer

              5 jours plus tard

              Merci @Hardware. Juste pour signaler que dorénavant les tutos ne peuvent être modifiés que par les auteurs sans être obligé de solliciter les Admins, une souplesse très appréciable

              Hardware c'est pas parce que les topics sont déverrouillé que les membres peuvent poster à la suite des topics. Y a quelque mois j'ai changé les autorisations pour ne pas devoir verrouiller tous les tutos à chaque fois.
              Je sais pas si je suis clair

                10 jours plus tard

                J'ai une petite infra DNS (4 serveurs, primaire/esclave1 en Bind9 auto-hébergé et esclave2/3 en offre dns secondaire OVH/afraid) et j'utilise pour le moment Certbot en certonly et webroot.
                Je suis en train de voir pour déployer DNSSEC (avec openDNSSEC pour le renouvellement automatique des clés).
                Je me suis dit que ça pourrais aussi être intéressant de permettre d'utiliser le DNS pour les certificats de Let's Encrypt (notamment par ce que c'est le seul moyen viable actuellement pour les wildcard) du coup le plugin certbot-dns-rfc2136 (mise a jours via DNS Dynamique) + ACL Bind9 peut faire un combo intéressant (peut être un tuto à venir, intéressant pour ceux qui gèrent leur serveur(s) DNS).
                @Hardware si tu veut filer un coup de mains 😉

                  Répondre…