Automatisation: Gérer vos certificat SSL (2/4)
Maintenant que nous avons posé notre première brique, nous allons regarder comment surveiller nos certificats afin de ne pas se retrouver par inadvertance avec un certificat expiré.
Pour commencer nous allons voir ce que nous avons comme information sur le certificat du sous-domaine mumu.fast-reload.com générer dans l'article précédent.
openssl x509 -text -noout -in /etc/letsencrypt/live/mumu.fast-reload.com/cert.pem
Et voici le résultat (je n'affiche que le début, la suite ne nous intéresse pas):
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
03:11:d4:f2:ca:05:0a:5b:dc:37:ee:d9:4c:3b:3c:ff:11:34
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Let's Encrypt, CN = R3
Validity
Not Before: Feb 26 10:15:01 2024 GMT
Not After : May 26 10:15:00 2024 GMT
Subject: CN = mumu.fast-reload.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:08:fe:db:64:c4:e2:19:34:03:2a:ce:e9:e6:48:
a1:63:52:a4:34:ef:b7:08:8b:81:3a:bb:06:95:e2:
da:6f:f7:54:0b:24:d8:a7:12:96:1a:1c:4a:fb:ef:
etc ...
On ne va pas décortiquer cela, ce n'est pas l'objectif de l'article, mais ce qui nous intéresse se trouve à la ligne 10, la date d'expiration.
À l'aide de la commande suivante, réduisons le champ de la recherche.
openssl x509 -enddate -noout -in /etc/letsencrypt/live/mumu.fast-reload.com/cert.pem | sed 's/notAfter=//'
Pour étayer mes dires du premier article, j'ai également généré deux certificats via OpenSSL.
# Le premier d'un durée de 20 jours
openssl req -x509 -nodes -days 20 -newkey rsa:4096 -keyout key-for-example.com.pem -out cert-for-example.com.pem -subj "/C=FR/ST=Paris/L=Paris/O=fast-reload/OU=reg
ister/CN=example.com"
# Le second d'une durée de 365 jours
openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout key-for-toto.com.pem -out cert-for-toto.com.pem -subj "/C=FR/ST=Paris/L=Paris/O=fast-reload/OU=register
/CN=toto.com"
Bien entendu, la commande pour vérifier l'expiration fonctionne tout aussi bien.
Pour la suite et la démonstration, je vais les renommer et les déplacer vers le répertoire/etc/letsencrypt/live/toto.com et /etc/letsencrypt/live/example.com
afin qu'ils rentrent dans le rang de la vérification global des certificats et ne pas être obligé d'écrire du code inutile lié à leurs emplacements.
Et pour aller au bout des choses, bien que je ne l'ai pas fait, il pourrait être judicieux de les renommer en fullchain1.pem et privkey1.pem, mais nous verrons cela plus tard.
Maintenant travaillons pour arriver à ce résultat.
Partez du principe que mon serveur Alpine Linux peut envoyer des mails, via postfix, ssmtp, avec mail, mailx ou encore mutt.
Bref,
Vous voyez où je veux en venir dans la gestion des certificats ?
Dans l'administration ?
Manuelle ou automatisée ?
Maintenant en début de script, un peu de couleurs une adresse mail valide (pensez à la modifier)
et le path ou se trouve les répertoires des domaines à checker.
Pour ne pas avoir à ajouter un read -p et entrer manuellement le nom de domaine ou du sous-domaine à vérifier, on va chercher tous les certificats.
Ensuite, on récupère la date d'expiration.
Pour effectuer un calcul savant on converti cette date d'expiration en timestamp et un petit contrôle si une erreur se présente
Pour continuer, on calcule la date du jour en timestamp, pour calculer la différence entre les deux dates.
Une petite conversion pour connaître le nombre de jours restant, c'est plus parlant que d'afficher une date, mais on pourrait très bien afficher aussi la variable $DAYS_LEFT en sortie console si on le souhaite.
Enfin, une condition qui regarde s'il reste moins de 30 jours aux certificats.
Si la condition est vrai alors on affiche un texte avec du rouge et on envoie un mail par domaine, car chaque domaine peut avoir des dates d'expiration différentes.
Et si la condition est fausse alors on affiche toujours un texte, mais en vert.
#!/bin/ash
source colors
RECIPIENT_EMAIL="monsuper@email.com"
BASE="/etc/letsencrypt/live/"
# Rechercher les fichiers de certificats dans /etc/letsencrypt/live/
CERT_PATHS=$(find $BASE -mindepth 1 -maxdepth 1 -type d -exec echo {}/fullchain.pem \;)
for CERT_PATH in $CERT_PATHS; do
# Extraire le nom de domaine du chemin du certificat
DOMAIN=$(basename $(dirname "$CERT_PATH"))
# Récupérer la date d'expiration au format "Fev 26 16:24:29 2024 GMT"
DATE=$(openssl x509 -enddate -noout -in "$CERT_PATH" | sed 's/notAfter=//')
# Convertir la date d'expiration en timestamp
EXPIRATION_TIMESTAMP=$(date -D "%b %e %H:%M:%S %Y GMT" -d "$DATE" "+%s" 2>/dev/null)
if [ -z "$EXPIRATION_TIMESTAMP" ]; then
echo "Erreur lors de la conversion de la date pour le certificat $DOMAIN."
continue
fi
# Calculer la date d'aujourd'hui en timestamp
CURRENT_TIMESTAMP=$(date "+%s")
# Calculer la différence entre les deux dates
DIFF_SECONDS=$((EXPIRATION_TIMESTAMP - CURRENT_TIMESTAMP))
# Calculer le nombre de jours restants
DAYS_LEFT=$((DIFF_SECONDS / (24 * 60 * 60)))
# Vérifier si moins de 30 jours restants
if [ "$DAYS_LEFT" -lt 30 ]; then
echo ""
echo -e "Attention : Il reste exactement ${RED}${DAYS_LEFT}${NC} jours pour le certificat $DOMAIN."
# Envoyer un e-mail
SUBJECT="[warn] Expiration du certificat $DOMAIN"
BODY="Il reste exactement $DAYS_LEFT jours pour le certificat $DOMAIN."
# Open port for send mail
iptables -I INPUT -p tcp --dport 587 -j ACCEPT && iptables -I OUTPUT -p tcp --dport 587 -j ACCEPT
# Send mail
echo "$BODY" | mutt -s "$SUBJECT" "$RECIPIENT_EMAIL"
# Close port send mail
iptables -D INPUT -p tcp --dport 587 -j ACCEPT && iptables -D OUTPUT -p tcp --dport 587 -j ACCEPT
else
echo ""
echo -e "Il reste encore ${GREEN}${DAYS_LEFT}${NC} jours pour le certificat $DOMAIN."
fi
done
Ce script va nous permettre de vérifier domaine par domaine, sous-domaine par sous-domaine de manière indépendante et de recevoir un mail personnalisé (à condition de le vouloir, je suis resté plus générique en modifiant seulement le hostname dans l'objet) pour chacun d'entre eux.
Vous noterez que là aussi, on gère les règles iptables pour envoyer un mail, port 587 pour moi.
J'ai chois de l'implémenter comme ceci, ouvrir et fermer uniquement si nécessaire.
On pourrait très bien ouvrir en début de script, traité les instructions du script et fermer à la fin du script, mais cela ne me plaisait pas.
Ce qui donne, pour ce script que j'ai nommé tmp2:
J'ai bien le résultat attendu sans avoir eu a rentrer 4, 5, 10 ou je ne sais combien de noms de domaine ou sous-domaine à la main.
Comme ils sont tous au même endroit, c'est facile, mais ce script peut très bien évoluer pour plusieurs path, par exemple si on veut dissocier ceux de Let's Encrypt et ceux auto-signés par OpenSSL
Mais ai-je bien reçu le mail concernant le domaine example.com ?
Et bien oui,
Je ne vais bien évidemment pas tout vous montrer de mon Thunderbird, mais voici ce qui nous intéresse.
Avant de passer à l'automatisation complète, en gardant une intervention humaine, pour ma part, c'est ce que je préfère il nous manque un script et en avant-première, je vous le donne dans le mille, nous le nommerons tmp3,
Et nous ferons un script complet pour administrer nos certificats avant de tout automatiser (ou presque, selon les goûts).