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

@@ -0,0 +1,58 @@
"""System-level endpoints (admin only).
Provides manual triggers for background operations such as the MITRE
ATT&CK synchronisation, and scheduler health introspection.
"""
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.dependencies.auth import require_role
from app.models.user import User
from app.services.mitre_sync_service import sync_mitre
from app.jobs.mitre_sync_job import scheduler
router = APIRouter(prefix="/system", tags=["system"])
@router.post("/sync-mitre")
def trigger_mitre_sync(
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Manually trigger a MITRE ATT&CK synchronisation.
**Requires** the ``admin`` role.
Returns a JSON object with the sync summary including the count of
new and updated techniques.
"""
summary = sync_mitre(db)
return {
"message": "MITRE sync completed",
"new": summary["created"],
"updated": summary["updated"],
}
@router.get("/scheduler-status")
def scheduler_status(
current_user: User = Depends(require_role("admin")),
):
"""Return the current state of the background scheduler.
**Requires** the ``admin`` role.
"""
jobs = scheduler.get_jobs()
return {
"running": scheduler.running,
"jobs": [
{
"id": job.id,
"name": job.name,
"next_run_time": str(job.next_run_time) if job.next_run_time else None,
}
for job in jobs
],
}