Files
threadnet-operating/monitoring/prometheus/alerts_test.yml
T
Thore Cimbal 44cc030f0b cve: a critical with nothing left to do is allowed, but only on the record
Sixty-three criticals on running images have no fix to take. Writing them into
a trivy ignore file would have been the obvious move and the wrong one: trivy
drops ignored findings from its output, so afterwards 'zero because fixed' and
'zero because we looked away' render identically. Every finding stays in
trivy_vuln_info. The decisions sit beside them in entscheidungen.json and are
counted, not subtracted.

Each entry names its CVEs one by one. A blanket entry per image would also
swallow the next finding that shows up there, which is the finding you would
most want to see. The loader rejects an entry without ids, and rejects a review
date it cannot parse — rejecting the whole file, because a half-read decision
list is worse than none.

An unreadable file leaves everything counted as open. Getting that direction
backwards would mean a typo reads as 'all decided', and nobody would notice.
The end-to-end run caught the same mistake in the other half: when the derived
target set is empty the set is unknown, not empty, so the open count now falls
back to every report rather than to zero.

Three python services move off the debian base while we are here — 3.13-slim
carried four criticals with no fix, 3.13-alpine none. All three run on the
stdlib alone and TLS was checked inside the image before the switch.
2026-08-21 12:00:00 +00:00

249 lines
9.6 KiB
YAML

# Regeltests fuer die Deckungs-Alarme (#0106) und die Entscheidungs-Alarme (#0051).
# docker run --rm --entrypoint promtool -v "$PWD/prometheus:/p:ro" \
# prom/prometheus:v3.14.0 test rules /p/alerts_test.yml
#
# ⚠️ ZWECK: Eine Regel, die nur je gruen war, ist eine Vermutung. In diesem
# Projekt ist "meldet Erfolg, ist aber blind" die haeufigste Fehlerklasse
# (AAR 2026-08-01, Befund 1 und 3). Deshalb steht zu jeder Regel ein Fall, in
# dem sie feuern MUSS, und einer, in dem sie schweigen MUSS - sonst laesst sich
# eine Regel, die immer feuert, von einer richtigen nicht unterscheiden.
#
# Erste Regeltests in diesem Stack ueberhaupt; die uebrigen 16 Regeln sind
# bewusst nicht mit abgedeckt (Nicht-Ziel von #0106).
rule_files:
- alerts.yml
evaluation_interval: 1m
tests:
# --- CveTargetsMissing ----------------------------------------------------
- interval: 1m
name: "Luecke in der Deckung feuert nach 6h"
input_series:
- series: 'cve_targets_missing'
values: '24+0x480' # 8h lang 24 fehlende Images
alert_rule_test:
- eval_time: 5h
alertname: CveTargetsMissing
exp_alerts: [] # noch in der Karenz - eine volle Runde darf dauern
- eval_time: 7h
alertname: CveTargetsMissing
exp_alerts:
- exp_labels:
severity: warning
room: security
exp_annotations:
summary: "24 laufende Images werden NICHT auf CVEs geprueft — die Schwachstellen-Sicht hat ein Loch, das sie selbst nicht zeigt. Details: https://selendis.axion1337.de/d/cve-overview"
- interval: 1m
name: "Volle Deckung schweigt"
input_series:
- series: 'cve_targets_missing'
values: '0+0x480'
alert_rule_test:
- eval_time: 7h
alertname: CveTargetsMissing
exp_alerts: []
# --- CveTargetsOrphaned ---------------------------------------------------
- interval: 1m
name: "Berichte ohne laufendes Image feuern"
input_series:
- series: 'cve_targets_orphaned'
values: '2+0x480'
alert_rule_test:
- eval_time: 7h
alertname: CveTargetsOrphaned
exp_alerts:
- exp_labels:
severity: info
room: security
exp_annotations:
summary: "2 CVE-Bericht(e) betreffen Images, die nirgends laufen — Befunde, auf die niemand handeln kann (Rauschen, das wie Signal aussieht)"
# --- CveTargetSourceStale (die wichtigste) --------------------------------
- interval: 1m
name: "Eine eingefrorene Quelle faellt auf"
input_series:
# Die Registry-Quelle altert, die anderen beiden bleiben frisch.
- series: 'cve_target_source_stale{quelle="registry"}'
values: '0+60x400' # waechst um 60s je Minute -> nach 180 min > 10800
- series: 'cve_target_source_stale{quelle="cluster"}'
values: '30+0x400'
- series: 'cve_target_source_stale{quelle="operating"}'
values: '30+0x400'
alert_rule_test:
- eval_time: 2h
alertname: CveTargetSourceStale
exp_alerts: [] # 7200s < 10800s - noch in Ordnung
- eval_time: 5h
alertname: CveTargetSourceStale
exp_alerts:
- exp_labels:
severity: warning
room: security
quelle: registry # ⚠️ NUR die eingefrorene, nicht alle drei
exp_annotations:
summary: "Die Quelle 'registry' der CVE-Zielmenge ist seit 18000s nicht erneuert worden — die Zielmenge schrumpft still, und die Deckung sieht dadurch BESSER aus statt schlechter"
# --- CveTargetsNoneDesired ------------------------------------------------
# Der Fall, den die drei anderen NICHT sehen: alle Quellen antworten sauber,
# aber leer. missing = 0, stale frisch - und es wird nichts mehr geprueft.
- interval: 1m
name: "Leere Zielmenge trotz gesunder Quellen"
input_series:
- series: 'cve_targets_desired_total'
values: '0+0x120'
- series: 'cve_targets_missing'
values: '0+0x120'
- series: 'cve_target_source_stale{quelle="cluster"}'
values: '5+0x120'
alert_rule_test:
- eval_time: 20m
alertname: CveTargetsNoneDesired
exp_alerts: [] # Karenz 30m
- eval_time: 40m
alertname: CveTargetsNoneDesired
exp_alerts:
- exp_labels:
severity: critical
room: security
exp_annotations:
summary: "Die CVE-Zielmenge ist LEER — es wird nichts mehr geprueft, obwohl keine Quelle einen Fehler meldet"
# Gegenprobe im selben Lauf: die anderen drei schweigen hier, obwohl die
# Lage schlecht ist. Genau deshalb braucht es die vierte Regel.
- eval_time: 40m
alertname: CveTargetsMissing
exp_alerts: []
- eval_time: 40m
alertname: CveTargetSourceStale
exp_alerts: []
# --- Gegenprobe: gesunder Zustand loest KEINEN der vier Alarme aus --------
- interval: 1m
name: "Alles in Ordnung bleibt still"
input_series:
- series: 'cve_targets_missing'
values: '0+0x480'
- series: 'cve_targets_orphaned'
values: '0+0x480'
- series: 'cve_targets_desired_total'
values: '65+0x480'
- series: 'cve_target_source_stale{quelle="cluster"}'
values: '120+0x480'
- series: 'cve_target_source_stale{quelle="operating"}'
values: '120+0x480'
- series: 'cve_target_source_stale{quelle="registry"}'
values: '900+0x480'
alert_rule_test:
- eval_time: 7h
alertname: CveTargetsMissing
exp_alerts: []
- eval_time: 7h
alertname: CveTargetsOrphaned
exp_alerts: []
- eval_time: 7h
alertname: CveTargetsNoneDesired
exp_alerts: []
- eval_time: 7h
alertname: CveTargetSourceStale
exp_alerts: []
# --- CveCriticalOffen (#0051) ---------------------------------------------
- interval: 1m
name: "Ein CRITICAL ohne Entscheidung faellt nach 24h auf"
input_series:
- series: 'cve_critical_offen'
values: '3+0x1800'
alert_rule_test:
- eval_time: 20h
alertname: CveCriticalOffen
exp_alerts: [] # Karenz: ein frisch entdeckter Befund darf
# einen Arbeitstag lang unentschieden sein
- eval_time: 26h
alertname: CveCriticalOffen
exp_alerts:
- exp_labels:
severity: warning
room: security
exp_annotations:
summary: "3 CRITICAL auf laufenden Images ohne Entscheidung — weder behoben noch mit Begruendung und Pruefdatum in cve/entscheidungen.json. Details: https://selendis.axion1337.de/d/cve-overview"
# --- CveEntscheidungAbgelaufen --------------------------------------------
# ⚠️ Der eigentliche Zweck der Entscheidungsdatei. Ohne diese Regel wuerde
# aus "wir haben uns das angesehen und es ist vertretbar" nach ein paar
# Monaten still "das steht halt so in der Datei".
- interval: 1m
name: "Ein ueberschrittenes Pruefdatum meldet sich"
input_series:
- series: 'cve_entscheidungen_abgelaufen'
values: '0+0x60 1+0x600' # erst alles gueltig, dann laeuft eine ab
alert_rule_test:
- eval_time: 1h
alertname: CveEntscheidungAbgelaufen
exp_alerts: [] # noch nichts abgelaufen
- eval_time: 8h
alertname: CveEntscheidungAbgelaufen
exp_alerts:
- exp_labels:
severity: warning
room: security
exp_annotations:
summary: "1 CVE-Entscheidung(en) haben ihr Pruefdatum ueberschritten — die Begruendung von damals gilt bis zum Gegenbeweis nicht mehr"
# --- CveEntscheidungenUnlesbar --------------------------------------------
- interval: 1m
name: "Eine kaputte Entscheidungsdatei nennt sich selbst"
input_series:
- series: 'cve_entscheidungen_lesefehler'
values: '1+0x120'
# Gleichzeitig zaehlt alles als offen - so ist der Exporter gebaut.
- series: 'cve_critical_offen'
values: '63+0x120'
alert_rule_test:
- eval_time: 15m
alertname: CveEntscheidungenUnlesbar
exp_alerts: [] # Karenz 30m
- eval_time: 45m
alertname: CveEntscheidungenUnlesbar
exp_alerts:
- exp_labels:
severity: warning
room: security
exp_annotations:
summary: "cve/entscheidungen.json ist nicht lesbar — bis das behoben ist, zaehlt jeder CRITICAL als unentschieden"
# ⚠️ Gegenprobe zum Zusammenspiel: CveCriticalOffen schweigt hier noch
# (Karenz 24h). Ohne die Regel darueber bliebe der wahre Grund also
# einen ganzen Tag lang unsichtbar.
- eval_time: 45m
alertname: CveCriticalOffen
exp_alerts: []
# --- Gegenprobe: entschiedener Bestand bleibt still ------------------------
- interval: 1m
name: "Alles entschieden und gueltig bleibt still"
input_series:
- series: 'cve_critical_offen'
values: '0+0x1800'
- series: 'cve_critical_entschieden'
values: '63+0x1800'
- series: 'cve_entscheidungen_abgelaufen'
values: '0+0x1800'
- series: 'cve_entscheidungen_ohne_befund'
values: '0+0x1800'
- series: 'cve_entscheidungen_lesefehler'
values: '0+0x1800'
alert_rule_test:
- eval_time: 26h
alertname: CveCriticalOffen
exp_alerts: []
- eval_time: 26h
alertname: CveEntscheidungAbgelaufen
exp_alerts: []
- eval_time: 26h
alertname: CveEntscheidungOhneBefund
exp_alerts: []
- eval_time: 26h
alertname: CveEntscheidungenUnlesbar
exp_alerts: []