Automatisation: Gérer vos certificat SSL (3/4)

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.