From 87771e6c1dd641e01b3f46a98ac78a81c5f09069 Mon Sep 17 00:00:00 2001 From: Fabio Boi Date: Sun, 4 Oct 2026 14:41:38 +0200 Subject: [PATCH] Repair official CISA fallback and preserve scan recovery in the installer --- docker-security.sh | 64 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/docker-security.sh b/docker-security.sh index f5cce26..818d27d 100644 --- a/docker-security.sh +++ b/docker-security.sh @@ -164,7 +164,7 @@ if [[ $MODE == host ]]; then fi fi if [[ $MODE == manager ]]; then - ask 'Auf diesem Manager werden die JSON-Regeln 110800-110805 ergaenzt und Wazuh neu gestartet. Bestehende Regeln/Discord-Einstellungen bleiben erhalten. Bitte INSTALL eingeben:' + ask 'Auf diesem Manager werden die JSON-Regeln 110800-110806 ergaenzt und Wazuh neu gestartet. Bestehende Regeln/Discord-Einstellungen bleiben erhalten. Bitte INSTALL eingeben:' else ask "Auf diesem Docker-Host einrichten: Listener=$(yesno_label "$LISTENER"), Image-Scans=$(yesno_label "$SCANS"), Abstand=${HOURS}h, Umfang=$SCOPE. Der Wazuh-Agent wird neu gestartet; Docker-Container bleiben in Betrieb. Bitte INSTALL eingeben:" fi @@ -301,6 +301,29 @@ CONFIG = Path('/etc/wazuh-docker-security/config.json') LOG = Path('/var/log/wazuh-docker-security/events.jsonl') SOCKET = 'unix:///var/run/docker.sock' KEV_URL = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json' +KEV_URLS = (KEV_URL, 'https://raw.githubusercontent.com/cisagov/kev-data/main/known_exploited_vulnerabilities.json') + + +def fetch_kev(url, now): + """Only publisher-controlled sources; reject invalid/old mirror snapshots.""" + request = urllib.request.Request(url, headers={'User-Agent': 'wazuh-docker-security/1'}) + with urllib.request.urlopen(request, timeout=15) as response: + raw = response.read(12 * 1024 * 1024 + 1) + if len(raw) > 12 * 1024 * 1024: + raise ValueError('KEV response too large') + document = json.loads(raw) + published = dt.datetime.fromisoformat(document['dateReleased'].replace('Z', '+00:00')).timestamp() + if not -86400 <= now - published <= 14 * 86400: + raise ValueError('KEV publication timestamp outside freshness window') + entries = document['vulnerabilities'] + if document.get('count') != len(entries): + raise ValueError('incomplete KEV catalog') + ids = sorted({v['cveID'] for v in entries + if re.fullmatch(r'CVE-\d{4}-\d{4,}', v.get('cveID', ''))}) + if not ids or len(ids) != len(entries): + raise ValueError('empty or invalid KEV catalog') + return {'fetched_at': now, 'published_at': published, 'ids': ids, + 'catalog_version': document.get('catalogVersion'), 'source': url} def atomic_json(path, data): @@ -369,21 +392,16 @@ def kev_catalog(): now = time.time() error = None if now - cached.get('fetched_at', 0) >= 86400: - try: - request = urllib.request.Request(KEV_URL, headers={'User-Agent': 'wazuh-docker-security/1'}) - with urllib.request.urlopen(request, timeout=30) as response: - raw = response.read(12 * 1024 * 1024 + 1) - if len(raw) > 12 * 1024 * 1024: - raise ValueError('KEV response too large') - document = json.loads(raw) - ids = sorted({v['cveID'] for v in document['vulnerabilities'] - if re.fullmatch(r'CVE-\d{4}-\d{4,}', v.get('cveID', ''))}) - if not ids: - raise ValueError('empty KEV catalog') - cached = {'fetched_at': now, 'ids': ids} - atomic_json(path, cached) - except Exception: - error = 'KEV refresh failed; cached information may be incomplete' + for url in KEV_URLS: + try: + updated = fetch_kev(url, now) + atomic_json(path, updated) + cached = updated + break + except Exception: + continue + else: + error = 'KEV refresh failed on both official sources; cached information may be incomplete' fresh = now - cached.get('fetched_at', 0) <= 7 * 86400 if not fresh: error = 'KEV catalog unavailable or older than seven days' @@ -518,6 +536,8 @@ def main(): for key in state['health']: if key not in errors: events.append({'event': 'scan_recovered', 'component': key}) + events.append({'event': 'scan_status', 'complete': 'yes' if inventory_ok and not any(k.startswith('image:') for k in errors) else 'no', + 'kev_fresh': 'yes' if fresh else 'no', 'problem_count': len(errors)}) append_events(events) atomic_json(state_path, {'findings': merged, 'health': errors, 'last_scan': time.time()}) atomic_json(BASE / 'inventory.json', {'updated_at': time.time(), 'scope': scope, @@ -535,7 +555,7 @@ if __name__ == '__main__': raise SystemExit(main()) SCAN_PY cat >"$TMP/rules.xml" <<'RULES_XML' - + json @@ -562,16 +582,22 @@ cat >"$TMP/rules.xml" <<'RULES_XML' Docker image scanner needs attention: $(component): $(message). docker_security_health, - + 110800 ^finding_(cleared|out_of_scope)$ Image finding cleared or no longer in scan scope. - + 110800 ^scan_recovered$ Scanner component recovered. + + 110800 + ^scan_status$ + Image scanner status: complete=$(complete), fresh KEV=$(kev_fresh), problems=$(problem_count). + docker_security_status, + RULES_XML BACKUP=/var/backups/wazuh-docker-security/$(date +%Y%m%d-%H%M%S)-$$