Repair official CISA fallback and preserve scan recovery in the installer
This commit is contained in:
+45
-19
@@ -164,7 +164,7 @@ if [[ $MODE == host ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [[ $MODE == manager ]]; then
|
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
|
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:"
|
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
|
fi
|
||||||
@@ -301,6 +301,29 @@ CONFIG = Path('/etc/wazuh-docker-security/config.json')
|
|||||||
LOG = Path('/var/log/wazuh-docker-security/events.jsonl')
|
LOG = Path('/var/log/wazuh-docker-security/events.jsonl')
|
||||||
SOCKET = 'unix:///var/run/docker.sock'
|
SOCKET = 'unix:///var/run/docker.sock'
|
||||||
KEV_URL = 'https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json'
|
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):
|
def atomic_json(path, data):
|
||||||
@@ -369,21 +392,16 @@ def kev_catalog():
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
error = None
|
error = None
|
||||||
if now - cached.get('fetched_at', 0) >= 86400:
|
if now - cached.get('fetched_at', 0) >= 86400:
|
||||||
try:
|
for url in KEV_URLS:
|
||||||
request = urllib.request.Request(KEV_URL, headers={'User-Agent': 'wazuh-docker-security/1'})
|
try:
|
||||||
with urllib.request.urlopen(request, timeout=30) as response:
|
updated = fetch_kev(url, now)
|
||||||
raw = response.read(12 * 1024 * 1024 + 1)
|
atomic_json(path, updated)
|
||||||
if len(raw) > 12 * 1024 * 1024:
|
cached = updated
|
||||||
raise ValueError('KEV response too large')
|
break
|
||||||
document = json.loads(raw)
|
except Exception:
|
||||||
ids = sorted({v['cveID'] for v in document['vulnerabilities']
|
continue
|
||||||
if re.fullmatch(r'CVE-\d{4}-\d{4,}', v.get('cveID', ''))})
|
else:
|
||||||
if not ids:
|
error = 'KEV refresh failed on both official sources; cached information may be incomplete'
|
||||||
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'
|
|
||||||
fresh = now - cached.get('fetched_at', 0) <= 7 * 86400
|
fresh = now - cached.get('fetched_at', 0) <= 7 * 86400
|
||||||
if not fresh:
|
if not fresh:
|
||||||
error = 'KEV catalog unavailable or older than seven days'
|
error = 'KEV catalog unavailable or older than seven days'
|
||||||
@@ -518,6 +536,8 @@ def main():
|
|||||||
for key in state['health']:
|
for key in state['health']:
|
||||||
if key not in errors:
|
if key not in errors:
|
||||||
events.append({'event': 'scan_recovered', 'component': key})
|
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)
|
append_events(events)
|
||||||
atomic_json(state_path, {'findings': merged, 'health': errors, 'last_scan': time.time()})
|
atomic_json(state_path, {'findings': merged, 'health': errors, 'last_scan': time.time()})
|
||||||
atomic_json(BASE / 'inventory.json', {'updated_at': time.time(), 'scope': scope,
|
atomic_json(BASE / 'inventory.json', {'updated_at': time.time(), 'scope': scope,
|
||||||
@@ -535,7 +555,7 @@ if __name__ == '__main__':
|
|||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
SCAN_PY
|
SCAN_PY
|
||||||
cat >"$TMP/rules.xml" <<'RULES_XML'
|
cat >"$TMP/rules.xml" <<'RULES_XML'
|
||||||
<!-- Managed by wazuh-docker-security; custom range 110800-110805. -->
|
<!-- Managed by wazuh-docker-security; custom range 110800-110806. -->
|
||||||
<group name="docker_security,trivy,">
|
<group name="docker_security,trivy,">
|
||||||
<rule id="110800" level="0">
|
<rule id="110800" level="0">
|
||||||
<decoded_as>json</decoded_as>
|
<decoded_as>json</decoded_as>
|
||||||
@@ -562,16 +582,22 @@ cat >"$TMP/rules.xml" <<'RULES_XML'
|
|||||||
<description>Docker image scanner needs attention: $(component): $(message).</description>
|
<description>Docker image scanner needs attention: $(component): $(message).</description>
|
||||||
<group>docker_security_health,</group>
|
<group>docker_security_health,</group>
|
||||||
</rule>
|
</rule>
|
||||||
<rule id="110804" level="0">
|
<rule id="110804" level="3">
|
||||||
<if_sid>110800</if_sid>
|
<if_sid>110800</if_sid>
|
||||||
<field name="event" type="pcre2">^finding_(cleared|out_of_scope)$</field>
|
<field name="event" type="pcre2">^finding_(cleared|out_of_scope)$</field>
|
||||||
<description>Image finding cleared or no longer in scan scope.</description>
|
<description>Image finding cleared or no longer in scan scope.</description>
|
||||||
</rule>
|
</rule>
|
||||||
<rule id="110805" level="0">
|
<rule id="110805" level="3">
|
||||||
<if_sid>110800</if_sid>
|
<if_sid>110800</if_sid>
|
||||||
<field name="event">^scan_recovered$</field>
|
<field name="event">^scan_recovered$</field>
|
||||||
<description>Scanner component recovered.</description>
|
<description>Scanner component recovered.</description>
|
||||||
</rule>
|
</rule>
|
||||||
|
<rule id="110806" level="3">
|
||||||
|
<if_sid>110800</if_sid>
|
||||||
|
<field name="event">^scan_status$</field>
|
||||||
|
<description>Image scanner status: complete=$(complete), fresh KEV=$(kev_fresh), problems=$(problem_count).</description>
|
||||||
|
<group>docker_security_status,</group>
|
||||||
|
</rule>
|
||||||
</group>
|
</group>
|
||||||
RULES_XML
|
RULES_XML
|
||||||
BACKUP=/var/backups/wazuh-docker-security/$(date +%Y%m%d-%H%M%S)-$$
|
BACKUP=/var/backups/wazuh-docker-security/$(date +%Y%m%d-%H%M%S)-$$
|
||||||
|
|||||||
Reference in New Issue
Block a user