2026-08-01 13:42:07 +02:00
|
|
|
#!/bin/sh
|
2026-08-21 12:00:00 +00:00
|
|
|
# Trivy-Scan-Schleife (gitops#47, seit #0106 mit abgeleiteter Zielmenge).
|
|
|
|
|
#
|
|
|
|
|
# Die Ziele standen bis 2026-08-21 in cve/images.txt und wurden von Hand
|
|
|
|
|
# nachgezogen. Gemessen an dem Tag: 27 von 51 laufenden Images, also 52 % -
|
|
|
|
|
# nicht gescannt wurden unter anderem der Web-Client, den jeder Nutzer laedt,
|
|
|
|
|
# beide Traefik-Schichten und die Registry selbst. Gleichzeitig lief ein Scan
|
|
|
|
|
# gegen threadnet-web:v0.3.0, ein Image, das nirgends mehr laeuft.
|
|
|
|
|
#
|
|
|
|
|
# Seitdem schreibt der cve-exporter die Soll-Menge nach /targets/targets.txt
|
|
|
|
|
# (Quellen: Cluster-Metriken, cAdvisor des Betriebs-Hosts, Registry). Diese
|
|
|
|
|
# Datei wird NICHT von Hand gepflegt - siehe ADR-0026.
|
|
|
|
|
#
|
|
|
|
|
# Kein Abbruch bei Einzelfehlern: Ein nicht mehr pullbares Image darf den Rest
|
|
|
|
|
# nicht verhindern; der Exporter alarmiert ueber trivy_last_scan_timestamp,
|
|
|
|
|
# wenn ein Report veraltet.
|
2026-08-01 13:42:07 +02:00
|
|
|
set -u
|
2026-08-21 12:00:00 +00:00
|
|
|
# Ueber die Umgebung ueberschreibbar - der Exporter fuehrt RESULTS_DIR
|
|
|
|
|
# bereits so, und test_scan_loop.sh laedt diese Datei damit.
|
|
|
|
|
RESULTS="${RESULTS_DIR:-/results}"
|
2026-08-21 12:00:00 +00:00
|
|
|
TARGETS="${TARGETS_FILE:-/targets/targets.txt}"
|
2026-08-01 13:42:07 +02:00
|
|
|
INTERVAL="${SCAN_INTERVAL_SECONDS:-86400}"
|
2026-08-21 12:00:00 +00:00
|
|
|
# ⚠️ Kurze Wartezeit, wenn die Zielliste (noch) fehlt: Beim ersten Start nach
|
|
|
|
|
# einem Ausrollen schreibt der Exporter sie erst beim naechsten Scrape. Mit dem
|
|
|
|
|
# vollen 24h-Intervall stuende der Scanner deswegen einen Tag still.
|
|
|
|
|
WARTEZEIT="${WARTE_INTERVAL_SECONDS:-60}"
|
2026-08-21 12:00:00 +00:00
|
|
|
# Trivy ist im Test durch eine Attrappe ersetzbar; hier der echte Aufruf.
|
|
|
|
|
scanne() { trivy image --scanners vuln --format json --output "$2" "$1"; }
|
2026-08-01 13:42:07 +02:00
|
|
|
|
2026-08-21 12:00:00 +00:00
|
|
|
runde() {
|
2026-08-21 12:00:00 +00:00
|
|
|
# ⚠️ Ohne brauchbare Zielliste wird NICHTS getan. Der Exporter schreibt sie
|
|
|
|
|
# nur, wenn mindestens eine Quelle geliefert hat - faellt die Herleitung
|
|
|
|
|
# ganz aus, bleibt die alte Datei stehen und diese Schleife arbeitet
|
|
|
|
|
# weiter mit dem letzten bekannten Stand, statt ins Leere zu laufen.
|
|
|
|
|
if [ ! -s "$TARGETS" ]; then
|
|
|
|
|
echo "warte: $TARGETS fehlt oder ist leer - keine Runde"
|
2026-08-21 12:00:00 +00:00
|
|
|
return 1
|
2026-08-21 12:00:00 +00:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
anzahl=0
|
2026-08-21 12:00:00 +00:00
|
|
|
erwartet=$(mktemp)
|
2026-08-01 13:42:07 +02:00
|
|
|
while IFS= read -r img; do
|
|
|
|
|
case "$img" in ""|\#*) continue;; esac
|
2026-08-21 12:00:00 +00:00
|
|
|
anzahl=$((anzahl + 1))
|
2026-08-01 13:42:07 +02:00
|
|
|
safe=$(echo "$img" | tr '/:@' '___')
|
2026-08-21 12:00:00 +00:00
|
|
|
echo "$safe" >> "$erwartet"
|
2026-08-01 13:42:07 +02:00
|
|
|
echo "scan: $img"
|
2026-08-21 12:00:00 +00:00
|
|
|
scanne "$img" "$RESULTS/$safe.json.tmp" \
|
2026-08-01 13:42:07 +02:00
|
|
|
&& mv "$RESULTS/$safe.json.tmp" "$RESULTS/$safe.json" \
|
|
|
|
|
|| echo "FEHLER bei $img (Report bleibt auf altem Stand)"
|
2026-08-21 12:00:00 +00:00
|
|
|
done < "$TARGETS"
|
|
|
|
|
|
2026-08-21 12:00:00 +00:00
|
|
|
# Berichte ohne Ziel entfernen (#0106, Kriterium 3).
|
|
|
|
|
#
|
|
|
|
|
# ⚠️ WARUM DAS NOETIG IST: Der Exporter liest JEDE *.json in /results und
|
|
|
|
|
# nimmt das Ziel aus Trivys ArtifactName. Ein Bericht, dessen Image aus der
|
|
|
|
|
# Soll-Menge gefallen ist, meldet sonst weiter - genau der Zustand vom
|
|
|
|
|
# 2026-08-20, als der Security-Raum HIGH-CVEs zu threadnet-web:v0.3.0 meldete,
|
|
|
|
|
# das nirgends mehr lief. Rauschen, das wie Signal aussieht.
|
|
|
|
|
#
|
|
|
|
|
# ⚠️ WARUM ES HIER STEHT UND NICHT WEITER OBEN: Die Wache am Schleifenanfang
|
|
|
|
|
# hat bereits sichergestellt, dass eine nicht-leere Zielliste vorliegt. Ohne
|
|
|
|
|
# sie wird die Runde ausgesetzt - und damit auch nichts geloescht. Sonst
|
|
|
|
|
# raeumte ein Neustart waehrend eines Prometheus-Ausfalls den gesamten
|
|
|
|
|
# Bestand ab, und der Verlust faellt erst beim naechsten Ernstfall auf.
|
|
|
|
|
entfernt=0
|
|
|
|
|
for bericht in "$RESULTS"/*.json; do
|
|
|
|
|
[ -e "$bericht" ] || continue
|
|
|
|
|
name=$(basename "$bericht" .json)
|
|
|
|
|
# Host-rootfs-Scans (Ausbaustufe, Dateiname traegt __host__) stammen nicht
|
|
|
|
|
# aus dieser Zielliste und werden deshalb nicht danach beurteilt.
|
|
|
|
|
case "$name" in *__host__*) continue;; esac
|
|
|
|
|
if ! grep -qxF "$name" "$erwartet"; then
|
|
|
|
|
echo "verwaist, entfernt: $name"
|
|
|
|
|
rm -f "$bericht"
|
|
|
|
|
entfernt=$((entfernt + 1))
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
rm -f "$erwartet"
|
|
|
|
|
|
|
|
|
|
echo "runde fertig: ${anzahl} Ziele, ${entfernt} verwaiste Berichte entfernt"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ⚠️ Ladewache: test_scan_loop.sh laedt diese Datei, um die ECHTE runde() zu
|
|
|
|
|
# pruefen statt eine nachgebaute. Ohne die Wache liefe beim Laden die
|
|
|
|
|
# Endlosschleife an, und der Test haette einen Nachbau geprueft - also den
|
|
|
|
|
# Nachbau, nicht die Sache.
|
|
|
|
|
[ "${SCAN_LOOP_NUR_LADEN:-0}" = "1" ] && return 0
|
|
|
|
|
|
|
|
|
|
while true; do
|
|
|
|
|
# Ausdruecklich als if, nicht als `runde || sleep A && sleep B`: Das gruppiert
|
|
|
|
|
# links-assoziativ zu `(runde || sleep A) && sleep B` und schliefe bei
|
|
|
|
|
# fehlender Zielliste erst die Wartezeit UND danach das volle Intervall -
|
|
|
|
|
# also doch einen Tag, genau das, was die Wartezeit verhindern soll.
|
|
|
|
|
if runde; then
|
|
|
|
|
sleep "$INTERVAL"
|
|
|
|
|
else
|
|
|
|
|
sleep "$WARTEZEIT"
|
|
|
|
|
fi
|
2026-08-01 13:42:07 +02:00
|
|
|
done
|