Tester: a CVE newly created by a sync appeared in the vuln list but left
NO initial audit entry ("new CVE detected on asset X at ...") — the audit
trail only began with the first status change (open -> patched etc.).
Confirmed: not intentional, simply never built; fails the revisionssicher
requirement.
- Migration 031: ALTER TYPE auditeventtype ADD VALUE
'VULNERABILITY_DETECTED' (idempotent, autocommit block).
- New app/services/audit_events.py: audit_new_vulnerabilities() writes one
System/Auto event per newly created finding:
"New finding detected: CVE-X on <hostname> (severity=..., source=...)".
- Wired into every creation path:
* Wazuh full sync (run_wazuh_vulnerability_sync) -> source=wazuh
* Wazuh per-agent sync (sync_agent_vulnerabilities, covers the
scheduler loop + per-asset rescan) -> source=wazuh
* Nessus sync (incl. Nessus EOL pseudo-vulns via
newly_created_vuln_ids) -> source=nessus
* endoflife.date upsert -> source=eol_check
* M365 Apps upsert -> source=m365_check
Full-sync and per-agent paths are independent (no double events).
- Best-effort: audit failure never breaks a sync.
Migration 031 required: alembic upgrade head.
61 lines
1.9 KiB
Python
61 lines
1.9 KiB
Python
"""
|
|
Shared audit-event writers for sync-driven changes.
|
|
|
|
Tester requirement (revisionssicher): a finding newly created by a
|
|
Wazuh/Nessus sync must leave an initial "VULNERABILITY_DETECTED" trail —
|
|
previously the audit history only began with the first status change.
|
|
"""
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Iterable
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models.audit_log import AuditLog, AuditEventType
|
|
from app.models.vulnerability import Vulnerability
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def audit_new_vulnerabilities(
|
|
db: Session,
|
|
vuln_ids: Iterable[int],
|
|
*,
|
|
source: str,
|
|
) -> int:
|
|
"""Write one VULNERABILITY_DETECTED audit row per newly created finding.
|
|
|
|
`source` names the detector ("wazuh", "nessus", "eol_check", ...).
|
|
user_id stays NULL → the audit UI renders it as System/Auto, matching
|
|
the asset-lifecycle events. Caller commits. Returns rows written.
|
|
"""
|
|
ids = [i for i in vuln_ids if i]
|
|
if not ids:
|
|
return 0
|
|
rows = (
|
|
db.query(Vulnerability)
|
|
.filter(Vulnerability.id.in_(ids))
|
|
.all()
|
|
)
|
|
written = 0
|
|
now = datetime.now()
|
|
for v in rows:
|
|
hostname = v.asset.hostname if v.asset else f"asset #{v.asset_id}"
|
|
desc = (
|
|
f"New finding detected: {v.cve_id} on {hostname} "
|
|
f"(severity={v.severity.value if hasattr(v.severity, 'value') else v.severity}, "
|
|
f"source={source})"
|
|
)
|
|
db.add(AuditLog(
|
|
user_id=None, # System/Auto
|
|
event_type=AuditEventType.VULNERABILITY_DETECTED,
|
|
event_description=desc[:500],
|
|
resource_type="vulnerability",
|
|
resource_id=str(v.id),
|
|
timestamp=now,
|
|
))
|
|
written += 1
|
|
if written:
|
|
logger.info("audit: %d VULNERABILITY_DETECTED events (%s)", written, source)
|
|
return written
|