feat(evaluations): ATT&CK Evaluations importer for CrowdStrike Falcon [FASE-6.1]
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- Migration b048: evaluation_imports table (adversary, round, status, tests_created) - EvaluationImport SQLAlchemy model - attck_evaluations_service: fetch rounds from evals.mitre.org API, import per-technique detection results (Technique/Tactic/Telemetry -> detected/partially/not_detected) - All imported tests land in in_review state with lab-environment disclaimer - Idempotency guard prevents duplicate round imports - 4 new endpoints: list rounds, import specific, import latest, check-new - Weekly APScheduler cron (Mon 06:00) auto-checks and imports new rounds - SystemPage UI: rounds table, import buttons, check-new, result feedback - Disclaimer callout reminding admins these are lab results not org coverage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -204,6 +204,83 @@ def _run_intel_scan() -> None:
|
||||
db.close()
|
||||
|
||||
|
||||
def _run_evaluation_round_check() -> None:
|
||||
"""Weekly job: check if a new ATT&CK Evaluation round is available.
|
||||
|
||||
If a new round is found it is imported automatically and an admin
|
||||
notification is created so the team knows new baseline data is available.
|
||||
"""
|
||||
logger.info("ATT&CK Evaluations new-round check starting...")
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from app.services.attck_evaluations_service import check_for_new_round, import_evaluation_round
|
||||
from app.models.user import User as UserModel
|
||||
|
||||
result = check_for_new_round(db)
|
||||
if result.get("error"):
|
||||
logger.warning("ATT&CK Evaluations check failed: %s", result["error"])
|
||||
return
|
||||
|
||||
if not result.get("new_round_available"):
|
||||
logger.info(
|
||||
"ATT&CK Evaluations check — latest round '%s' already imported",
|
||||
result.get("latest_round", {}).get("display_name", "?"),
|
||||
)
|
||||
return
|
||||
|
||||
latest = result["latest_round"]
|
||||
logger.info(
|
||||
"New ATT&CK Evaluation round detected: %s (round %d) — starting auto-import",
|
||||
latest["display_name"], latest["eval_round"],
|
||||
)
|
||||
|
||||
# Use the first admin user as the importer (system action)
|
||||
admin = db.query(UserModel).filter(UserModel.role == "admin").first()
|
||||
if not admin:
|
||||
logger.warning("ATT&CK Evaluations auto-import: no admin user found — skipping")
|
||||
return
|
||||
|
||||
summary = import_evaluation_round(
|
||||
db,
|
||||
latest["name"],
|
||||
latest["display_name"],
|
||||
latest["eval_round"],
|
||||
admin,
|
||||
)
|
||||
logger.info(
|
||||
"ATT&CK Evaluations auto-import complete — round %d (%s): %d tests created",
|
||||
latest["eval_round"], latest["display_name"], summary["created"],
|
||||
)
|
||||
|
||||
# Notify all admins
|
||||
try:
|
||||
from app.services.notification_service import create_notification
|
||||
admins = db.query(UserModel).filter(UserModel.role == "admin").all()
|
||||
for adm in admins:
|
||||
create_notification(
|
||||
db,
|
||||
user_id=adm.id,
|
||||
title="New ATT&CK Evaluation round imported",
|
||||
message=(
|
||||
f"Round {latest['eval_round']} — {latest['display_name']} — "
|
||||
f"has been automatically imported. "
|
||||
f"{summary['created']} tests created in In Review state. "
|
||||
f"Blue Leads must validate each result before it counts as coverage."
|
||||
),
|
||||
notification_type="eval_import",
|
||||
entity_type="evaluation",
|
||||
entity_id=None,
|
||||
)
|
||||
db.commit()
|
||||
except Exception:
|
||||
logger.warning("Failed to send eval import notifications", exc_info=True)
|
||||
|
||||
except Exception:
|
||||
logger.exception("ATT&CK Evaluations round check job failed")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _run_osint_enrichment() -> None:
|
||||
"""Execute weekly OSINT enrichment inside its own DB session."""
|
||||
logger.info("Scheduled OSINT enrichment job starting...")
|
||||
@@ -463,6 +540,16 @@ def start_scheduler() -> None:
|
||||
name="Operational alert evaluation (hourly)",
|
||||
replace_existing=True,
|
||||
)
|
||||
scheduler.add_job(
|
||||
_run_evaluation_round_check,
|
||||
trigger="cron",
|
||||
day_of_week="mon",
|
||||
hour=6,
|
||||
minute=0,
|
||||
id="attck_evaluation_check",
|
||||
name="ATT&CK Evaluations new-round check (Mondays 06:00)",
|
||||
replace_existing=True,
|
||||
)
|
||||
scheduler.start()
|
||||
logger.info(
|
||||
"Background scheduler started — mitre_sync (24h), intel_scan (7d), "
|
||||
@@ -470,5 +557,5 @@ def start_scheduler() -> None:
|
||||
"recurring_campaigns (daily), jira_sync (1h), "
|
||||
"osint_enrichment (weekly), stale_detection (daily), "
|
||||
"retention_policies (daily), data_sources_sync (6h), "
|
||||
"alert_evaluation (1h)"
|
||||
"alert_evaluation (1h), attck_evaluation_check (Mondays 06:00)"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user