Tips & Tricks: Notifica della Scadenza dei Certificati TLS con OpenSSL

Tips & Tricks: Notifica della Scadenza dei Certificati TLS con OpenSSL

1024 683 NM Professionals

Tips & Tricks: Notifica della Scadenza dei Certificati TLS con OpenSSL

Continuando l’argomento OpenSSL, utilizzando quando già descritto nell’articolo Tips & Tricks: Verifica della Catena di Certificati SSL/TLS con OpenSSL descriveremo come automatizzare la verifica di validità dei certificati TLS applicata ai siti WEB.

Da chi possiede un singolo sito web fino alle aziende che ne gestiscono molti ricorre la difficoltà di tenere traccia della scadenza dei certificati TLS (https) per evitare che il sito web mostri all’utente la pagina di sito “non sicuro”, cosa che non giova alla credibilità di ciò che si vuole condividere.

Questo script, eseguito a mezzo crontab, verifica periodicamente che i certificati dei siti web, indicati negli array (chiamati GroupA, GroupB…GroupN) rientrino nel periodo di validità ed eventualmente notifichi a mezzo email quali si approssimino alla scadenza. Si dimostra essere utile quando ci si “dimentica” della data di scadenza del certificato oppure quando, per qualsiasi motivo la procedura di rinnovo “automatizzata” (che sarà descritta in un prossimo articolo)  fallisca il compito.

# ------------------------------------------------------------------------------
# - File Name     : check-web-certs-expiry.sh
# - Author        : Nicola Montemurro
# - Administrator : Nicola Montemurro  - Mobile: 335 -
# - Create        : 30/04/2026
# - Last Update   : 01/05/2026
# - Description   : script ...
# - Position      : /usr/local/scripts
# - Note          : NON modificare senza AUTORIZZAZIONE
#  -----------------------------------------------------------------------------

#!/usr/bin/env bash
set -euo pipefail

# Configuration
DAYBEFOREALERT=5

MAILTO="sysadmin@mydomain.it"              ### set to email address to send report (requires mailx)
SMTP="smtp.myotherdomain.com"                ### unused in this script; kept for compatibility

CONNECT_TIMEOUT=5      ### seconds for openssl TCP connect (uses timeout)
OPENSSL_TIMEOUT=5      ### seconds to limit openssl time (uses timeout)

TODAYEPOCH=$(date -u +%s)

### Hosts groups
declare -a groupA=("www.solutiontech.it")
declare -a groupB=("solutioncafe.it")
declare -a groupC=("www.nicolamontemurro.it" "blog.nicolamontemurro.it")
declare -a groupD=("www.nicolamontemurro.com" "blog.nicolamontemurro.com")

declare -a groups=("groupA" "groupB" "groupC" "groupD")

ALERTS=()
WARNS=()

### helper: fetch certificate once, return text or empty on failure
fetch_cert_text() {
   local host=$1
   ### Use timeout to avoid hangs; capture only the leaf certificate text via openssl x509
   ### We start a TCP connection and pipe into openssl x509 to get parsed cert.
   if CERT_TEXT=$(timeout "${CONNECT_TIMEOUT}" openssl s_client -connect "${host}:443" -servername "${host}" </dev/null 2>/dev/null | timeout "${OPENSSL_TIMEOUT}" openssl x509 -noout -text 2>/dev/null)
   then
        printf '%s' "$CERT_TEXT"
        return 0
   else
        return 1
   fi
}

for group in "${groups[@]}"
do
        lst="${group}[@]"
        for host in "${!lst}"
        do
                CERT_TEXT=""
                if ! CERT_TEXT=$(fetch_cert_text "$host")
                then
                        WARNS+=("WARN: cannot retrieve certificate for ${host}")
                        continue
                fi

                ### Issuer: prefer openssl -noout -ISSUER but we already have parsed text
                ISSUER=$(printf '%s\n' "$CERT_TEXT" | awk -F'ISSUER=' '/ISSUER=/{print $2; exit}' || true)
                ISSUER=${ISSUER:-unknown}

                ### Not After: try to extract 'Not After' line from parsed cert text
                NOTAFTER_RAW=$(printf '%s\n' "$CERT_TEXT" | awk -F'Not After :' '/Not After/{gsub(/^[ \t]+/,""); print $2; exit}' || true)

                ### Fallback to enddate format
                if [[ -z "$NOTAFTER_RAW" ]]
                then
                        NOTAFTER_RAW=$(openssl x509 -noout -enddate <<<"$CERT_TEXT" 2>/dev/null | sed 's/notAfter=//g' || true)
                fi

                if [[ -z "$NOTAFTER_RAW" ]]
                then
                        WARNS+=("WARN: cannot extract NotAfter for ${host}")
                        continue
                fi

                echo $NOTAFTER_RAW $host

                ### SAN extraction: collect DNS entries into a comma-separated list
                ALTNAME=$(printf '%s\n' "$CERT_TEXT" | awk '/Subject Alternative Name:/ {flag=1; next} /^X509v3/ { if(flag){flag=0} } flag {print}' | tr -d ' ' | sed 's/DNS:/\nDNS:/g' | sed -n 's/^DNS://p' | paste -sd, -)
                ALTNAME=${ALTNAME:-unknown}

                ### Convert NotAfter to epoch (GNU date required)
                if ! NOTAFTER_EPOCH=$(date -u -d "$NOTAFTER_RAW" +%s 2>/dev/null)
                then
                        WARNS+=("WARN: cannot parse NotAfter date for ${host}: '${NOTAFTER_RAW}'")
                        continue
                fi

                DELTA=$(( NOTAFTER_EPOCH - TODAYEPOCH ))
                DAYLEFT=$(( DELTA / 86400 ))

                if (( DAYLEFT < 0 ))
                then
                        ALERTS+=("EXPIRED: ${host} expired $((-DAYLEFT)) day(s) ago on ${NOTAFTER_RAW} (Issuer: ${ISSUER}, SAN: ${ALTNAME})")
                elif (( DAYLEFT <= DAYBEFOREALERT ))
                then
                        ALERTS+=("ALERT: ${host} expires in ${DAYLEFT} day(s) on ${NOTAFTER_RAW} (Issuer: ${ISSUER}, SAN: ${ALTNAME})")
                fi
        done
done

### Output print warnings and alerts 
if ((${#WARNS[@]}))
then
        printf '%s\n' "${WARNS[@]}" >&2
fi

### Send warning email 
if ((${#WARNS[@]}))
then
        report=$(printf '%s\n' "${WARNS[@]}")
        printf '%s\n' "$report"

        ### Send email if configured and mailx is available
        if [[ -n "$MAILTO" ]]
        then
                if command -v mailx >/dev/null 2>&1
                then
                        printf '%s\n' "$report" | mailx -s "Certificate WARNS" "$MAILTO"
                else
                        printf 'WARN: MAILTO set but mailx not found; skipping email\n' >&2
                fi
        fi
fi

### send alert email
if ((${#ALERTS[@]}))
then
        report=$(printf '%s\n' "${ALERTS[@]}")
        printf '%s\n' "$report"

        ### Send email if configured and mailx is available
        if [[ -n "$MAILTO" ]]
        then
                if command -v mailx >/dev/null 2>&1
                then
                        printf '%s\n' "$report" | mailx -s "Certificate ALERTS" "$MAILTO"
                else
                        printf 'WARN: MAILTO set but mailx not found; skipping email\n' >&2
                fi
        fi
        exit 2
else
        printf 'OK: no certificates expiring within %s days\n' "$DAYBEFOREALERT"
        exit 0
fi

    Preferenze Privacy

    Quando visiti il nostro sito web, possono essere memorizzate alcune informazioni, di servizi specifici, tramite il tuo browser, di solito sotto forma di cookie. Qui puoi modificare le tue preferenze sulla privacy. Il blocco di alcuni cookie può influire sulla tua esperienza sul nostro sito Web e sui servizi che offriamo.

    Questo sito web utilizza cookie, principalmente di terze parti. Personalizza le preferenze sulla privacy e/o acconsenti all'utilizzo dei cookie.