Monitoraggio scadenze

Complessità: Bassa-Media
Durata: 1-2 ore per il setup
Obiettivo: Nessun certificato deve scadere inosservato

Sorveglianza delle date di scadenza dei certificati con diversi strumenti e strategie di alerting.


Architettura

flowchart LR subgraph SOURCES["📁 FONTI"] F[Filesystem] K[Kubernetes Secrets] R[Remote TLS] S[Archivi certificati] end subgraph COLLECTOR["📊 COLLECTOR"] E[cert-exporter] N[node_exporter] B[blackbox_exporter] end subgraph STORAGE["💾 PROMETHEUS"] P[(Prometheus)] end subgraph ALERT["🚨 ALERT"] A[Alertmanager] M[E-Mail/Slack/Teams] end F --> E --> P --> A --> M K --> E R --> B --> P S --> N --> P style A fill:#ffebee style P fill:#e3f2fd


Opzione 1: Prometheus + cert-exporter

Installazione

# Scaricare cert-exporter
wget https://github.com/enix/x509-certificate-exporter/releases/download/v3.12.0/x509-certificate-exporter_3.12.0_linux_amd64.tar.gz
tar xzf x509-certificate-exporter_*.tar.gz
sudo mv x509-certificate-exporter /usr/local/bin/
 
# Creare servizio Systemd
cat << 'EOF' | sudo tee /etc/systemd/system/cert-exporter.service
[Unit]
Description=X509 Certificate Exporter
After=network.target
 
[Service]
Type=simple
ExecStart=/usr/local/bin/x509-certificate-exporter \
    --watch-file=/etc/ssl/certs/*.pem \
    --watch-file=/etc/nginx/ssl/*.crt \
    --watch-kubeconf=false
Restart=always
 
[Install]
WantedBy=multi-user.target
EOF
 
sudo systemctl daemon-reload
sudo systemctl enable --now cert-exporter

Configurazione Prometheus

# /etc/prometheus/prometheus.yml
scrape_configs:
  - job_name: 'cert-exporter'
    static_configs:
      - targets: ['localhost:9793']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        replacement: 'pki-server'
 
  # Verificare endpoint TLS remoti
  - job_name: 'blackbox-tls'
    metrics_path: /probe
    params:
      module: [tls_connect]
    static_configs:
      - targets:
          - https://api.example.com
          - https://web.example.com
          - https://mail.example.com:465
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115

Alert Rules

# /etc/prometheus/rules/cert-alerts.yml
groups:
  - name: certificate-alerts
    rules:
      - alert: CertificateExpiringSoon
        expr: x509_cert_not_after - time() < 30 * 24 * 3600
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "Certificato {{ $labels.filepath }} scade tra < 30 giorni"
          description: "Tempo rimanente: {{ $value | humanizeDuration }}"

      - alert: CertificateExpireCritical
        expr: x509_cert_not_after - time() < 7 * 24 * 3600
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "CRITICO: Certificato {{ $labels.filepath }} scade tra < 7 giorni"
          description: "Data scadenza: {{ $value | humanizeTimestamp }}"

      - alert: CertificateExpired
        expr: x509_cert_not_after - time() < 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "Certificato {{ $labels.filepath }} è SCADUTO"

      - alert: CAExpiringSoon
        expr: x509_cert_not_after{is_ca="true"} - time() < 365 * 24 * 3600
        for: 1d
        labels:
          severity: warning
        annotations:
          summary: "Certificato CA {{ $labels.subject }} scade tra < 1 anno"

Opzione 2: Dashboard Grafana

{
  "dashboard": {
    "title": "Certificate Expiry Dashboard",
    "panels": [
      {
        "title": "Certificati in scadenza (< 30 giorni)",
        "type": "table",
        "targets": [
          {
            "expr": "sort_desc((x509_cert_not_after - time()) / 86400)",
            "format": "table"
          }
        ],
        "fieldConfig": {
          "overrides": [
            {
              "matcher": { "id": "byName", "options": "Value" },
              "properties": [
                {
                  "id": "custom.displayMode",
                  "value": "color-background"
                },
                {
                  "id": "thresholds",
                  "value": {
                    "steps": [
                      { "color": "red", "value": 0 },
                      { "color": "orange", "value": 7 },
                      { "color": "yellow", "value": 30 },
                      { "color": "green", "value": 90 }
                    ]
                  }
                }
              ]
            }
          ]
        }
      },
      {
        "title": "Certificati per tempo alla scadenza",
        "type": "stat",
        "targets": [
          {
            "expr": "count(x509_cert_not_after - time() < 7 * 86400)",
            "legendFormat": "< 7 giorni"
          },
          {
            "expr": "count(x509_cert_not_after - time() < 30 * 86400)",
            "legendFormat": "< 30 giorni"
          }
        ]
      }
    ]
  }
}

Opzione 3: Script semplice (senza Prometheus)

#!/bin/bash
# /usr/local/bin/cert-check-notify.sh
 
WARN_DAYS=30
CRIT_DAYS=7
MAIL_TO="pki-team@example.com"
WEBHOOK_URL="https://teams.example.com/webhook/..."
 
check_cert() {
    local cert="$1"
    local days_left=$(( ($(openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2 | date -f - +%s) - $(date +%s)) / 86400 ))
    local subject=$(openssl x509 -subject -noout -in "$cert" 2>/dev/null | sed 's/subject=//')
 
    if [ "$days_left" -lt 0 ]; then
        echo "EXPIRED|$cert|$subject|$days_left"
    elif [ "$days_left" -lt "$CRIT_DAYS" ]; then
        echo "CRITICAL|$cert|$subject|$days_left"
    elif [ "$days_left" -lt "$WARN_DAYS" ]; then
        echo "WARNING|$cert|$subject|$days_left"
    fi
}
 
# Verificare tutti i certificati
results=""
for cert in /etc/ssl/certs/*.pem /etc/nginx/ssl/*.crt; do
    [ -f "$cert" ] || continue
    result=$(check_cert "$cert")
    [ -n "$result" ] && results+="$result\n"
done
 
# Inviare notifica se necessario
if [ -n "$results" ]; then
    # E-Mail
    echo -e "Problemi certificati trovati:\n\n$results" | \
        mail -s "PKI Alert: Certificati" "$MAIL_TO"
 
    # Teams/Slack Webhook
    curl -s -X POST "$WEBHOOK_URL" \
        -H "Content-Type: application/json" \
        -d "{\"text\": \"PKI Alert: Certificati\n\n$(echo -e "$results" | sed 's/|/ | /g')\"}"
fi
# Cron: Ogni giorno alle 08:00
echo "0 8 * * * root /usr/local/bin/cert-check-notify.sh" > /etc/cron.d/cert-check

Opzione 4: PowerShell (Windows)

# Check-CertificateExpiry.ps1
 
param(
    [int]$WarnDays = 30,
    [int]$CritDays = 7,
    [string]$SmtpServer = "smtp.example.com",
    [string]$MailTo = "pki-team@example.com"
)
 
$results = @()
 
# Certificati locali
Get-ChildItem Cert:\LocalMachine\My | ForEach-Object {
    $daysLeft = ($_.NotAfter - (Get-Date)).Days
    $severity = if ($daysLeft -lt 0) { "EXPIRED" }
                elseif ($daysLeft -lt $CritDays) { "CRITICAL" }
                elseif ($daysLeft -lt $WarnDays) { "WARNING" }
                else { $null }
 
    if ($severity) {
        $results += [PSCustomObject]@{
            Severity = $severity
            Subject = $_.Subject
            Thumbprint = $_.Thumbprint
            DaysLeft = $daysLeft
            NotAfter = $_.NotAfter
        }
    }
}
 
# Endpoint TLS remoti
$endpoints = @(
    "api.example.com:443",
    "web.example.com:443"
)
 
foreach ($endpoint in $endpoints) {
    try {
        $host, $port = $endpoint -split ':'
        $cert = (New-Object System.Net.Sockets.TcpClient($host, [int]$port)).GetStream() |
            ForEach-Object { (New-Object System.Net.Security.SslStream($_)).AuthenticateAsClient($host); $_.RemoteCertificate }
 
        $daysLeft = ($cert.NotAfter - (Get-Date)).Days
        # ... analogo a sopra
    }
    catch {
        $results += [PSCustomObject]@{
            Severity = "ERROR"
            Subject = $endpoint
            Thumbprint = "N/A"
            DaysLeft = -1
            NotAfter = "Connessione fallita: $_"
        }
    }
}
 
# Inviare report
if ($results.Count -gt 0) {
    $body = $results | Format-Table -AutoSize | Out-String
 
    Send-MailMessage -To $MailTo -From "pki@example.com" `
        -Subject "PKI Alert: $($results.Count) certificato/i richiedono attenzione" `
        -Body $body `
        -SmtpServer $SmtpServer
}
 
# Output per logging
$results | Format-Table

Kubernetes: Metriche cert-manager

# ServiceMonitor per cert-manager
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: cert-manager
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: cert-manager
  namespaceSelector:
    matchNames:
      - cert-manager
  endpoints:
    - port: tcp-prometheus-servicemonitor
      interval: 60s

Metriche importanti:

Metrica Descrizione
———————-
certmanager_certificate_expiration_timestamp_seconds Timestamp scadenza
certmanager_certificate_ready_status Stato Ready (1=OK)
certmanager_certificate_renewal_timestamp_seconds Ultimo rinnovo

Best Practice

Raccomandazione Motivazione
—————–————-
30/14/7/1 giorni Alert Escalation graduale
Certificati CA separati Preavviso più lungo (1 anno)
Remoto + Locale verificare Fonti di errore diverse
Deduplicazione Non 100 alert per 1 certificato
Link al runbook nell'alert Istruzioni operative dirette

Checklist

# Punto di verifica
——————-
1 Exporter installato
2 Prometheus Scrape configurato
3 Alert Rules definite
4 Alertmanager Routing
5 Dashboard Grafana
6 Alert di test inviato

Documentazione correlata


« ← Monitoring | → Verifica revoca »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: il 30/01/2026 alle 01:42