feat: Phase 4 - MITRE ATT&CK sync and scheduled job (T-018, T-019)

- Add MITRE sync service via TAXII 2.0 with GitHub fallback
- Upsert attack-pattern objects into techniques table (691 techniques)
- Detect name/description changes and flag review_required on re-sync
- Add APScheduler background job running every 24h
- Add POST /system/sync-mitre endpoint (admin only)
- Add GET /system/scheduler-status endpoint (admin only)
- Configure logging for scheduler and sync visibility
- Update README with new endpoints and project structure
This commit is contained in:
2026-02-06 15:28:53 +01:00
parent 4f6dd838fd
commit b11854fdab
6 changed files with 384 additions and 3 deletions

View File

View File

@@ -0,0 +1,53 @@
"""Scheduled job for periodic MITRE ATT&CK synchronisation.
Uses APScheduler's ``BackgroundScheduler`` to run :func:`sync_mitre` every
24 hours. The job manages its own database session (created on entry,
closed in ``finally``) so it is fully independent from FastAPI's
request-scoped sessions.
"""
import logging
from apscheduler.schedulers.background import BackgroundScheduler
from app.database import SessionLocal
from app.services.mitre_sync_service import sync_mitre
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Module-level scheduler instance
# ---------------------------------------------------------------------------
scheduler = BackgroundScheduler()
def _run_mitre_sync() -> None:
"""Execute a MITRE sync inside its own DB session."""
logger.info("Scheduled MITRE sync job starting...")
db = SessionLocal()
try:
summary = sync_mitre(db)
logger.info("Scheduled MITRE sync job finished — %s", summary)
except Exception:
logger.exception("Scheduled MITRE sync job failed")
finally:
db.close()
def start_scheduler() -> None:
"""Register the MITRE sync job and start the background scheduler.
The job runs every **24 hours**. It does **not** fire immediately on
startup — the first execution happens 24 h after the application boots.
"""
scheduler.add_job(
_run_mitre_sync,
trigger="interval",
hours=24,
id="mitre_sync",
name="MITRE ATT&CK sync (every 24h)",
replace_existing=True,
)
scheduler.start()
logger.info("MITRE sync scheduler started (interval=24h)")