Automatisation: Gérer vos certificat SSL (3/4)
Après avoir posé la deuxième brique, nous allons en faire un script d'administration, c'est-à-dire un script qui va nous permettre d'interagir avec nos certificats.
On va commencer par mettre un petit peu de couleur est créer le menu.
(je vous rappelle que ceci a été, au départ, pensé pour mon propre besoin, à vous de le faire évoluer selon vos besoins)
#!/bin/ash
source colors
BASE="/etc/letsencrypt/live/"
echo ""
echo " #######################################"
echo " ####### Que voulez-vous faire ? #######"
echo " #######################################"
echo ""
echo "1 - Vérifier l'enregistrement d'un domaine ou d'un sous domaine"
echo ""
echo "2 - Vérifier la date d'expiration des certificats"
echo ""
echo "3 - Générer un certificat Let's encrypt"
echo ""
echo "4 - Générer un certificat OpenSSL"
echo ""
echo "5 - Révoquer un certificat"
echo ""
echo "6 - Quitter ce script"
echo ""
read -p "entrez le numéro de l'action que vous souhaitez: " choice
echo ""
Comme vous pouvez le voir, il y a de quoi générer un certificat, mais aussi révoquer un certificat, parce qu'il est important de pouvoir le faire dans le cas d'une compromission par exemple, mais aussi, et cela sera le cas présenté, en générer un nouveau, portant le même nom tout le temps (fullchain & privkey) sinon le script et la suite dans une CI deviendrait plus contraignante à gérer.
Partez du principe qu'il n'y a pas qu'une seule et unique façon de faire les choses avec Linux en matière de Scripting.
Pour générer un certificat, via Let's Encrypt ou même OpenSSL, nous avons vu ce qu'il nous faut dans les deux articles précédents.
Concernant la révocation de certificat, n'ayant pas monté de PKI, les certificats OpenSSL ne sont pas concernés, ils seront purement et simplement supprimés.
On va ajouter nos 2 bouts de code des deux premiers articles en faisant en sorte que celui qui vérifie l'enregistrement DNS d'un domaine/sous-domaine n'est plus la possibilité de générer le certificat, on va laisser cela aux choix 3 et 4 du menu et celui qui vérifie le nombre de jours restant, nous lui retirons la possibilité d'envoyer des mails (nous remettrons cela dans un autre script en fin de cet article.).
On va également faire en sorte d'harmoniser les noms des certificats OpenSSL avec ceux de Let's Encrypt.
Voilà nous avons tout pour finaliser notre script et bien entendu, on va se laisser le choix de la durée de validité pour le certificat OpenSSL (10 jours, 1 an, 10 ans, etc ...), mettons cela en forme.
#!/bin/ash
source colors
BASE="/etc/letsencrypt/live/"
function cert {
# Demander à l'utilisateur le nom de domaine ou sous-domaine à vérifier
read -p "Entrez le ou les nom(s) de domaine(s) ou sous-domaine(s) à vérifier: " DOMAINS
for DOMAIN in $DOMAINS; do
# Vérifier l'existence de l'enregistrement A (IPv4)
if dig +short A "$DOMAIN" | grep -q .; then
# Afficher "OK" en vert si l'enregistrement A existe
echo ""
echo -e "${GREEN}OK${NC} - L'enregistrement A pour ${GREEN}$DOMAIN${NC} existe."
echo ""
else
# Afficher "NOK" en rouge si l'enregistrement A n'existe pas
echo -e "${RED}NOK${NC} - Aucun enregistrement A trouvé pour ${RED}$DOMAIN${NC}."
fi
done
}
function check_cert {
# Rechercher les fichiers de certificats dans /etc/letsencrypt/live/
CERT_PATHS=$(find $BASE -mindepth 1 -maxdepth 1 -type d -exec echo {}/cert.pem \;)
#CERT_PATHS=$(find /etc/letsencrypt/live/ -mindepth 1 -maxdepth 1 -type d -exec echo {}/cert.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 "Jan 19 07: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 -e "Attention : Il reste exactement ${RED}${DAYS_LEFT}${NC} jours pour le certificat $DOMAIN."
else
echo -e "Il reste encore ${GREEN}${DAYS_LEFT}${NC} jours pour le certificat $DOMAIN."
fi
done
}
echo ""
echo " #######################################"
echo " ####### Que voulez-vous faire ? #######"
echo " #######################################"
echo ""
echo "1 - Vérifier l'enregistrement d'un domaine ou d'un sous domaine"
echo ""
echo "2 - Vérifier la date d'expiration des certificats"
echo ""
echo "3 - Générer un certificat Let's encrypt"
echo ""
echo "4 - Générer un certificat OpenSSL"
echo ""
echo "5 - Révoquer un certificat"
echo ""
echo "6 - Supprimer un certificat OpenSSL"
echo ""
echo "7 - Quitter ce script"
echo ""
read -p "entrez le numéro de l'action que vous souhaitez: " choice
echo ""
case $choice in
1) cert ;;
2) check_cert ;;
3)
echo ""
echo -e " Vous allez ${GREEN}Générer${NC} un certificat"
echo "Voici la liste des certificat existant"
check_cert
echo ""
read -p "Indiquez le FQDN complet pour générer le certificat: " DOMAIN
# générer le certificat
iptables -I INPUT -p tcp --dport 80 -j ACCEPT && iptables -I INPUT -p tcp --dport 443 -j ACCEPT
iptables -I OUTPUT -p tcp --dport 80 -j ACCEPT && iptables -I OUTPUT -p tcp --dport 443 -j ACCEPT
certbot certonly --standalone -d "$DOMAIN" --agree-tos --non-interactive --email "$RECIPIENT_EMAIL" > /dev/null
iptables -D INPUT -p tcp --dport 80 -j ACCEPT && iptables -D INPUT -p tcp --dport 443 -j ACCEPT
iptables -D OUTPUT -p tcp --dport 80 -j ACCEPT && iptables -D OUTPUT -p tcp --dport 443 -j ACCEPT
echo -e "${GREEN}$DOMAIN${NC} généré"
echo ""
;;
4)
echo ""
echo -e " Vous allez ${GREEN}Générer${NC} un certificat OpenSSL"
echo "Voici la liste des certificat existant"
check_cert
echo ""
read -p "Indiquez le FQDN complet pour générer le certificat: " DOMAIN
read -p "Indiquez la durée de validité voulue pour le certificat: " TIME_LEFT
# générer le certificat
mkdir "$BASE/$DOMAIN"
openssl req -x509 -nodes -days $TIME_LEFT -newkey rsa:4096 -keyout "$BASE/$DOMAIN/privkey.pem" -out "$BASE/$DOMAIN/cert.pem" -subj "/C=FR/ST=Paris/L=Paris/O=standalone/OU=register/CN=$DOMAIN"
echo -e "${GREEN}$DOMAIN${NC} généré"
echo ""
;;
5)
echo ""
echo -e "${RED}Attention${NC}: Vous allez ${RED}Révoquer${NC} un certificat Let's Encrypt"
echo "Voici la liste des certificat existant"
check_cert
echo ""
read -p "Indiquez le FQDN complet du certificat à révoquer: " DOMAIN
# révoquer un certificat
iptables -I INPUT -p tcp --dport 80 -j ACCEPT && iptables -I INPUT -p tcp --dport 443 -j ACCEPT
iptables -I OUTPUT -p tcp --dport 80 -j ACCEPT && iptables -I OUTPUT -p tcp --dport 443 -j ACCEPT
certbot revoke --cert-name "$DOMAIN" --non-interactive > /dev/null
iptables -D INPUT -p tcp --dport 80 -j ACCEPT && iptables -D INPUT -p tcp --dport 443 -j ACCEPT
iptables -D OUTPUT -p tcp --dport 80 -j ACCEPT && iptables -D OUTPUT -p tcp --dport 443 -j ACCEPT
echo -e "${RED}$DOMAIN${NC} révoqué"
echo ""
;;
6)
echo ""
echo -e " Vous allez ${RED}supprimer${NC} un certificat OpenSSL"
echo "Voici la liste des certificat existant"
echo "Attention: il est possible de supprimer un certificat Let's encrypt également"
check_cert
echo ""
read -p "Indiquez le FQDN complet du certificat à supprimer: " DOMAIN
# Suppression du certificat
rm -rf $BASE/$DOMAIN
echo ""
echo -e "${GREEN}$DOMAIN${NC} supprimé"
echo ""
;;
7) echo "Bye bye!"; exit 0 ;;
*) echo "Choix invalide";;
esac
Le résultat en image (sur les screen ci-dessous il n'y avait pas encore le choix pour supprimer un certificat OpenSSL par exemple):
Maintenant avant de passer à de l'automatisation de tout ceci (et je garde toujours une intervention humaine dans mes tâches) utilisons notre deuxième script, modifions le légèrement et créons une tâche cron afin d'être averti lorsque qu'un certificat sera en fin de vie.
Réduisons-le en supprimant la couleur, les commentaires et les echo ...
Nous n'avons plus besoin de cela pour en faire une tâche cron
#!/bin/ash
RECIPIENT_EMAIL="monmail@mail.com"
BASE="/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"))
DATE=$(openssl x509 -enddate -noout -in "$CERT_PATH" | sed 's/notAfter=//')
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
CURRENT_TIMESTAMP=$(date "+%s")
DIFF_SECONDS=$((EXPIRATION_TIMESTAMP - CURRENT_TIMESTAMP))
DAYS_LEFT=$((DIFF_SECONDS / (24 * 60 * 60)))
if [ "$DAYS_LEFT" -lt 30 ]; then
SUBJECT="[warn] Expiration du certificat $DOMAIN"
BODY="Il reste exactement $DAYS_LEFT jours pour le certificat $DOMAIN."
iptables -I INPUT -p tcp --dport 587 -j ACCEPT && iptables -I OUTPUT -p tcp --dport 587 -j ACCEPT
echo "$BODY" | mutt -s "$SUBJECT" "$RECIPIENT_EMAIL"
iptables -D INPUT -p tcp --dport 587 -j ACCEPT && iptables -D OUTPUT -p tcp --dport 587 -j ACCEPT
else
continue
fi
done
Maintenant crontab -e et ajoutons une tache qui exécutera le script check-cert (ci-dessus) tous les 5 jours par exemple et nous serons averti par mail dès qu'un certificat sera en phase d'expiration ce qui nous permettra via le script d'administration de faire le nécessaire.
# do daily/weekly/monthly maintenance
# min hour day month weekday command
*/15 * * * * run-parts /etc/periodic/15min
0 * * * * run-parts /etc/periodic/hourly
0 2 * * * run-parts /etc/periodic/daily
0 3 * * 6 run-parts /etc/periodic/weekly
0 5 1 * * run-parts /etc/periodic/monthly
00 06 */5 * * /root/scripts/check-cert
Dans le dernier article, nous verrons comment tout ceci ou une partie (selon vos besoins) peut être automatiser via une CI Gitlab et donc distribuer vos certificats sur les serveurs concernés.
Rappelez-vous, ce serveur sous Alpine Linux n’héberge rien d'autre que les certificats, aucun autre service, donc en l'état les certificats ne servent à rien si on ne les propage pas vers les services concernés.