Rajouter une métrique perso à node-exporter : validité restante d'un certificat

node_exporter
est un exporteur de métriques au format prometheus, focalisé sur la
partie matériel/OS.

Mais il est possible de rajouter des métriques perso. Pour cela, Il faut
utiliser un textfile collector
:

  • ajouter au lancement de node_exporter l’option
    --collector.textfile.directory= pour positionner le répertoire qui
    va abriter les fichiers de métrique
  • créer par un script un fichier .prom contenant des métriques
    prometheus
  • assurer la mise à jour de ce fichier

Exemple avec la validité d’un certificat

Le serveur dns0.math.cnrs.fr utilise un certificat pour le chiffrement
SSL des communications avec les autres serveurs dns de notre
infrastructure.

Le script cert-validity donne pour un certificat le nombre de jours
avant son expiration, et avec l’option -m génère une métrique au
format prometheus.

cert-validity  /var/lib/acme/dns0.math.cnrs.fr/full.pem
34
cert-validity --metrics /var/lib/acme/dns0.math.cnrs.fr/full.pem
# HELP ssl_certificate_expiry_days Days until SSL certificate expires
# TYPE ssl_certificate_expiry_days gauge
ssl_certificate_expiry_days{cert_name="dns0.math.cnrs.fr"} 34

Dans la métrique, on rajoute le nom du certificat, et donc ici du
serveur.

# cert-validity cert-path
# gives validity in days
# -m/--metrics : outputs metric
#
# error 1 number of arguments different of 1
# error 2 if file missing or not found
# error 3 if openssl cannot

# Variables
METRICS_MODE=false
CERT_PATH=""

# Parser les arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        -m|--metrics)
            METRICS_MODE=true
            shift
            ;;
        -*)
            echo "Error: Unknown option $1" >&2
            exit 1
            ;;
        *)
            if [ -z "$1" ]; then
                echo "Error: Expected exactly 1 argument (certificate path)" >&2
                exit 1
            fi
            CERT_PATH="$1"
            shift
            ;;
    esac
done

# Vérifier si le fichier existe
if [ ! -f "$CERT_PATH" ]; then
    echo "Error: Certificate file not found: $CERT_PATH" >&2
    exit 2
fi

# Extraire la date d'expiration avec openssl
EXPIRY_DATE=$(openssl x509 -in "$CERT_PATH" -noout -enddate 2>/dev/null | cut -d= -f2)

# Vérifier si openssl a réussi
if [ $? -ne 0 ] || [ -z "$EXPIRY_DATE" ]; then
    echo "Error: Cannot parse certificate with openssl" >&2
    exit 3
fi

# Convertir en timestamp Unix
EXPIRY_TIMESTAMP=$(date -d "$EXPIRY_DATE" +%s 2>/dev/null)

# Vérifier si la conversion de date a réussi
if [ $? -ne 0 ]; then
    echo "Error: Cannot parse expiry date" >&2
    exit 3
fi

CURRENT_TIMESTAMP=$(date +%s)

# Calculer les jours restants
DAYS_REMAINING=$(( (EXPIRY_TIMESTAMP - CURRENT_TIMESTAMP) / 86400 ))

# Output according to mode
if [ "$METRICS_MODE" = true ]; then
    # Extract certificate name (Common Name)
    CERT_NAME=$(openssl x509 -in "$CERT_PATH" -noout -subject 2>/dev/null | sed -e 's/.*CN *= *\(.*\)/\1/')

    # If no CN found, use filename
    if [ -z "$CERT_NAME" ]; then
        CERT_NAME=$(basename "$CERT_PATH" .crt)
    fi

    # Generate Prometheus metric
    echo "# HELP ssl_certificate_expiry_days Days until SSL certificate expires"
    echo "# TYPE ssl_certificate_expiry_days gauge"
    echo "ssl_certificate_expiry_days{cert_name=\"$CERT_NAME\"} $DAYS_REMAINING"
else
    echo "$DAYS_REMAINING"
fi

Lancement de node_exporter

Je décide de stocker mes métriques perso dans
/var/lib/prometheus-node-exporter, je rajoute donc au lancement :

--collector.textfile.directory=/var/lib/prometheus-node-exporter

Pour cela, je modifie le service systemd.

Génération journalière de la métrique

Je crée un wrapper au dessus du script précédent :

#!/nix/store/5jw69mbaj5dg4l2bj58acg3gxywfszpj-bash-5.2p26/bin/bash
/nix/store/np29p1wj8q3xgq8ajf53yw6gsa5v4qz0-cert-validity/bin/cert-validity -m "/var/lib/acme/dns0.math.cnrs.fr/cert.pem" \
  > /var/lib/prometheus-node-exporter/cert.prom

Le service systemd suivant permet de lancer le wrapper :

[Unit]
Description=Daily cert-validity metrics generation

[Service]
Environment="LOCALE_ARCHIVE=/nix/store/9dij3pl4dkdxmxhasjw1pa9hzqv4rjlp-glibc-locales-2.39-52/lib/locale/locale-archive"
Environment="PATH=/nix/store/ysqx2xfzygv2rxl7nxnw48276z5ckppn-coreutils-9.5/bin:/nix/store/36rvynxwln7iz0qq3k1v3r1mna8bma8s-findutils-4.9.0/bin:/nix/store/d9xr7s3z0r8rf0ba22q6ilqv68agymdb-gnugrep-3.11/bin:/nix/store/7xwbkzfrs6flyvjyvd23m8r2mlnycinq-gnused-4.9/bin:/nix/store/d9ff8aqv537mlhpinncx6dwc7a5ky6gk-systemd-255.6/bin:/nix/store/ysqx2xfzygv2rxl7nxnw48276z5ckppn-coreutils-9.5/sbin:/nix/store/36rvynxwln7iz0qq3k1v3r1mna8bma8s-findutils-4.9.0/sbin:/nix/store/d9xr7s3z0r8rf0ba22q6ilqv68agymdb-gnugrep-3.11/sbin:/nix/store/7xwbkzfrs6flyvjyvd23m8r2mlnycinq-gnused-4.9/sbin:/nix/store/d9ff8aqv537mlhpinncx6dwc7a5ky6gk-systemd-255.6/sbin"
Environment="TZDIR=/nix/store/jyh52p2cxrjn8r4ywdv2am5pjkj1xcqa-tzdata-2024a/share/zoneinfo"
ExecStart=/nix/store/j3j50icl3jkmzxai5vm4m367wcd5x4fg-cert-metrics
Group=named
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadOnlyPaths=/var/lib/acme
ReadOnlyPaths=/var/lib/acme/dns0.math.cnrs.fr
ReadOnlyPaths=/var/lib/acme/dns0.math.cnrs.fr/cert.pem
ReadWritePaths=/var/lib/prometheus-node-exporter
Type=oneshot
User=node-exporter

Remarquez particulièrement la gestion des droits d’accès au fichier du
certificat :

  • l’accès au fichier de certificat est réalisé par ReadOnlyPaths
  • l’accès au répertoire pour le stockage de la métrique utilise
    ReadWritePaths
  • User et Group sont positionnés

Et enfin le timer systemd associé permet de régénérer la métrique tous
les jours :

[Unit]
Description=Run cert-validity metric daily

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1h


[Install]
WantedBy=timers.target

Test du bon fonctionnement

On force la génération de la métrique en démarrant manuellement le
service :

systemctl start cert-metrics

On peut ensuite vérifier que le fichier a bien été rempli :

cat /var/lib/prometheus-node-exporter/cert.prom
# HELP ssl_certificate_expiry_days Days until SSL certificate expires
# TYPE ssl_certificate_expiry_days gauge
ssl_certificate_expiry_days{cert_name="dns0.math.cnrs.fr"} 34

Et enfin interroger le prometheus local :

curl -s http://localhost:9100/metrics | grep ssl
# HELP ssl_certificate_expiry_days Days until SSL certificate expires
# TYPE ssl_certificate_expiry_days gauge
ssl_certificate_expiry_days{cert_name="dns0.math.cnrs.fr"} 34
2 « J'aime »