Ablauf-Monitoring

Komplexität: Niedrig-Mittel
Dauer: 1-2 Stunden Setup
Ziel: Keine Zertifikate unbemerkt ablaufen lassen

Überwachung von Zertifikatsablaufdaten mit verschiedenen Tools und Alerting-Strategien.


Architektur

flowchart LR subgraph SOURCES["📁 QUELLEN"] F[Dateisystem] K[Kubernetes Secrets] R[Remote TLS] S[Cert Stores] 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


Option 1: Prometheus + cert-exporter

Installation

# cert-exporter herunterladen
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/
 
# Systemd Service erstellen
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

Prometheus Konfiguration

# /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'
 
  # Remote TLS Endpoints prüfen
  - 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: "Zertifikat {{ $labels.filepath }} läuft in < 30 Tagen ab"
          description: "Verbleibende Zeit: {{ $value | humanizeDuration }}"

      - alert: CertificateExpireCritical
        expr: x509_cert_not_after - time() < 7 * 24 * 3600
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "KRITISCH: Zertifikat {{ $labels.filepath }} läuft in < 7 Tagen ab"
          description: "Ablaufdatum: {{ $value | humanizeTimestamp }}"

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

      - alert: CAExpiringSoon
        expr: x509_cert_not_after{is_ca="true"} - time() < 365 * 24 * 3600
        for: 1d
        labels:
          severity: warning
        annotations:
          summary: "CA-Zertifikat {{ $labels.subject }} läuft in < 1 Jahr ab"

Option 2: Grafana Dashboard

{
  "dashboard": {
    "title": "Certificate Expiry Dashboard",
    "panels": [
      {
        "title": "Ablaufende Zertifikate (< 30 Tage)",
        "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": "Zertifikate nach Ablaufzeit",
        "type": "stat",
        "targets": [
          {
            "expr": "count(x509_cert_not_after - time() < 7 * 86400)",
            "legendFormat": "< 7 Tage"
          },
          {
            "expr": "count(x509_cert_not_after - time() < 30 * 86400)",
            "legendFormat": "< 30 Tage"
          }
        ]
      }
    ]
  }
}

Option 3: Einfaches Script (ohne 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
}
 
# Alle Zertifikate prüfen
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
 
# Benachrichtigung senden wenn nötig
if [ -n "$results" ]; then
    # E-Mail
    echo -e "Zertifikats-Probleme gefunden:\n\n$results" | \
        mail -s "PKI Alert: Zertifikate" "$MAIL_TO"
 
    # Teams/Slack Webhook
    curl -s -X POST "$WEBHOOK_URL" \
        -H "Content-Type: application/json" \
        -d "{\"text\": \"PKI Alert: Zertifikate\n\n$(echo -e "$results" | sed 's/|/ | /g')\"}"
fi
# Cron: Täglich um 08:00
echo "0 8 * * * root /usr/local/bin/cert-check-notify.sh" > /etc/cron.d/cert-check

Option 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 = @()
 
# Lokale Zertifikate
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
        }
    }
}
 
# Remote TLS Endpoints
$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
        # ... analog wie oben
    }
    catch {
        $results += [PSCustomObject]@{
            Severity = "ERROR"
            Subject = $endpoint
            Thumbprint = "N/A"
            DaysLeft = -1
            NotAfter = "Connection failed: $_"
        }
    }
}
 
# Report senden
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) Zertifikat(e) erfordern Aufmerksamkeit" `
        -Body $body `
        -SmtpServer $SmtpServer
}
 
# Output für Logging
$results | Format-Table

Kubernetes: cert-manager Metrics

# ServiceMonitor für 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

Wichtige Metriken:

Metrik Beschreibung
——–————–
certmanager_certificate_expiration_timestamp_seconds Ablaufzeitpunkt
certmanager_certificate_ready_status Ready-Status (1=OK)
certmanager_certificate_renewal_timestamp_seconds Letzte Erneuerung

Best Practices

Empfehlung Begründung
————————
30/14/7/1 Tage Alerts Gestaffelte Eskalation
CA-Zertifikate separat Längere Vorwarnzeit (1 Jahr)
Remote + Lokal prüfen Unterschiedliche Fehlerquellen
Deduplizierung Nicht 100 Alerts für 1 Zertifikat
Runbook-Link in Alert Direkte Handlungsanweisung

Checkliste

# Prüfpunkt
———–
1 Exporter installiert
2 Prometheus Scrape konfiguriert
3 Alert Rules definiert
4 Alertmanager Routing
5 Grafana Dashboard
6 Test-Alert gesendet

Verwandte Dokumentation


« ← Monitoring | → Revocation-Check »


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

Zuletzt geändert: den 29.01.2026 um 15:13