feat(scans): nessus sync now writes per-host scan history rows

Tester noticed that scheduled + manual Nessus syncs left no trace in
the Scan-Jobs page — only Wazuh autoscan was visible there. Made
it impossible to verify Nessus scheduler runs after the fact.

Changes:

- New enum value ScanType.NESSUS + idempotent migration 014.

- run_nessus_sync (services/nessus_sync.py) now opens a Scan row
  per host at the top of the per-host loop:
      scan_type   = NESSUS
      status      = RUNNING → COMPLETED / FAILED
      started_at  = now
      asset_id    = matched VulnCheck asset
  and closes it after the backfill, setting
      vulnerabilities_found = len(seen_cves_for_asset)
      completed_at = now
  The skip-backfill defensive branch (host returned 0 findings)
  marks the row COMPLETED with an explanatory error_message so the
  operator sees the run happened but produced nothing.

- Scheduler path inherits this for free — app/scheduler.py already
  calls run_nessus_sync for scanner_type='nessus' schedules.

- Existing /scans/summary endpoint groups consecutive Scan rows of
  the same type into a "run", so the UI shows one entry per Nessus
  sync (covering all hosts).

Migration required on deploy:
    docker compose exec backend alembic upgrade head    # 013 → 014
This commit is contained in:
2026-05-18 11:43:01 +02:00
parent ec752b4a4a
commit 4bca41e63e
3 changed files with 57 additions and 0 deletions
@@ -0,0 +1,34 @@
"""Add NESSUS value to scans.scan_type enum
Revision ID: 014
Revises: 013
Create Date: 2026-05-18 10:00:00.000000
run_nessus_sync now writes one Scan row per host so Nessus syncs
appear in the Scan-Jobs history (previously only Wazuh did). Needs
a new enum value 'nessus' on scans.scan_type.
Idempotent — the ADD VALUE IF NOT EXISTS guard means re-running this
migration on a schema where the value already lives is a no-op.
"""
from alembic import op
revision = '014'
down_revision = '013'
branch_labels = None
depends_on = None
def upgrade() -> None:
# Postgres enum ALTERs must run outside a transaction in older
# versions. ADD VALUE IF NOT EXISTS is supported on 9.6+ and is
# transaction-safe on 12+ which is our minimum target.
op.execute("ALTER TYPE scantype ADD VALUE IF NOT EXISTS 'nessus'")
def downgrade() -> None:
# Postgres has no DROP VALUE for enums. The downgrade is a no-op;
# an existing 'nessus' value is harmless even after the model
# stops emitting it.
pass
+1
View File
@@ -12,6 +12,7 @@ class ScanType(str, Enum):
FULL = "full"
SYSCOLLECTOR = "syscollector"
MANUAL = "manual"
NESSUS = "nessus"
class ScanStatus(str, Enum):
+22
View File
@@ -30,6 +30,7 @@ from sqlalchemy.orm import Session
from app.integrations.nessus_client import NessusClient
from app.models.asset import Asset, AssetSource, AssetStatus
from app.models.scan import Scan, ScanStatus, ScanType
from app.models.setting import Setting
from app.models.vulnerability import (
Vulnerability,
@@ -231,6 +232,19 @@ def run_nessus_sync(
continue
stats["hosts_synced"] += 1
# Record one Scan row per host so Nessus runs appear in
# the Scan-Jobs history (previously only Wazuh autoscan
# populated this table). Status updated to COMPLETED /
# FAILED at the end of this host's loop iteration.
scan_row = Scan(
asset_id=asset.id,
scan_type=ScanType.NESSUS,
status=ScanStatus.RUNNING,
started_at=datetime.now(),
)
db.add(scan_row)
host_vulns_found = 0
# Track cves we observe on THIS asset in THIS sync run.
# Used for backfill (drop nessus source on disappeared findings).
seen_cves_for_asset: set = set()
@@ -469,6 +483,10 @@ def run_nessus_sync(
asset.hostname, scan_id,
)
asset.last_scan = datetime.now()
scan_row.status = ScanStatus.COMPLETED
scan_row.completed_at = datetime.now()
scan_row.vulnerabilities_found = 0
scan_row.error_message = "Host returned 0 findings (skipped backfill)"
continue
stale_nessus_vulns = (
db.query(Vulnerability)
@@ -489,6 +507,10 @@ def run_nessus_sync(
stats["vulns_marked_patched"] += 1
asset.last_scan = datetime.now()
# Close out the per-host scan row.
scan_row.status = ScanStatus.COMPLETED
scan_row.completed_at = datetime.now()
scan_row.vulnerabilities_found = len(seen_cves_for_asset)
db.commit()