Files
threadnet-operating/monitoring/prometheus/alerts_test.yml
T
Thore Cimbal 9a3b1d1dd5 cve: a successful query can still be worthless, and it cost 42 reports
Restarting k3s took kube-state-metrics and alloy's log tailers down with it, so
kube_pod_container_info went empty in prometheus. The derivation asked, got a
clean response with zero rows, and counted it as success. Cluster targets went
from 39 to none, the total from 54 to 12, and the scan loop deleted every report
whose target had vanished. Coverage then read 1.0.

None of the four rules fired, and each for a defensible reason: the set was not
empty because the operating host still answered, and every timestamp was fresh
because an empty success updates it. The gap sat exactly between them.

A source that has delivered before and now delivers nothing is treated as a
failure: its previous targets are kept, its timestamp ages, and the stale rule
takes over. A fifth rule watches the total for a drop of more than 40 percent,
and it deliberately also fires on a deliberate shrink — losing 40 percent of the
checked estate is worth a line either way.

The six rancher decisions are gone too. They described versions that no longer
run: the k3s patch took all fifteen of their criticals with it.
2026-08-21 12:00:00 +00:00

290 lines
11 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: []
# --- CveZielmengeEingebrochen (#0051, Vorfall 2026-08-23) ------------------
- interval: 1m
name: "Ein Einbruch der Zielmenge faellt auf"
input_series:
# Der echte Verlauf: 54 Ziele, dann faellt die Cluster-Quelle still aus.
- series: 'cve_targets_desired_total'
values: '54+0x120 12+0x120'
alert_rule_test:
- eval_time: 1h
alertname: CveZielmengeEingebrochen
exp_alerts: [] # noch bei 54
- eval_time: 2h5m
alertname: CveZielmengeEingebrochen
exp_alerts: [] # eingebrochen, aber noch in der Karenz
- eval_time: 2h30m
alertname: CveZielmengeEingebrochen
exp_alerts:
- exp_labels:
severity: warning
room: security
exp_annotations:
summary: "Die CVE-Zielmenge ist auf 12 Ziele eingebrochen — ueber 40 % weniger als in den letzten 24 h. Faellt eine Quelle still aus, sieht das Dashboard danach BESSER aus, nicht schlechter."
# ⚠️ Gegenprobe zum Zusammenspiel: die vier aelteren Regeln schweigen in
# genau dieser Lage. Deshalb brauchte es eine fuenfte.
- eval_time: 2h30m
alertname: CveTargetsNoneDesired
exp_alerts: []
- eval_time: 2h30m
alertname: CveTargetsMissing
exp_alerts: []
- interval: 1m
name: "Ein kleiner Rueckbau bleibt still"
input_series:
- series: 'cve_targets_desired_total'
values: '54+0x60 44+0x180' # -19 %, unter der Schwelle
alert_rule_test:
- eval_time: 3h
alertname: CveZielmengeEingebrochen
exp_alerts: []