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:
@@ -501,6 +501,133 @@ def update_email_config(
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ATT&CK Evaluations endpoints (admin only)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.get("/attck-evaluations/rounds")
|
||||
def list_evaluation_rounds(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Return all public CrowdStrike ENTERPRISE evaluation rounds with import status.
|
||||
|
||||
Each entry includes whether it has already been imported into this platform.
|
||||
"""
|
||||
from app.services.attck_evaluations_service import fetch_available_rounds
|
||||
from app.models.evaluation_import import EvaluationImport
|
||||
|
||||
try:
|
||||
rounds = fetch_available_rounds()
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Could not reach MITRE Evaluations API: {exc}")
|
||||
|
||||
imported = {
|
||||
row.adversary_name.lower(): row
|
||||
for row in db.query(EvaluationImport).filter(EvaluationImport.status == "completed").all()
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
"name": r["name"],
|
||||
"display_name": r.get("display_name", r["name"]),
|
||||
"eval_round": r["eval_round"],
|
||||
"imported": r["name"].lower() in imported,
|
||||
"imported_at": imported[r["name"].lower()].imported_at.isoformat()
|
||||
if r["name"].lower() in imported else None,
|
||||
"tests_created": imported[r["name"].lower()].tests_created
|
||||
if r["name"].lower() in imported else None,
|
||||
"techniques_covered": imported[r["name"].lower()].techniques_covered
|
||||
if r["name"].lower() in imported else None,
|
||||
}
|
||||
for r in rounds
|
||||
]
|
||||
|
||||
|
||||
@router.post("/attck-evaluations/import")
|
||||
def import_evaluation_round(
|
||||
payload: dict,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Import a specific ATT&CK Evaluation round for CrowdStrike.
|
||||
|
||||
Body: { "adversary_name": "apt29", "adversary_display": "APT29", "eval_round": 2 }
|
||||
|
||||
Creates tests in ``in_review`` state — Blue Leads must validate each
|
||||
result before it counts as real coverage.
|
||||
"""
|
||||
from app.services.attck_evaluations_service import import_evaluation_round as _import
|
||||
|
||||
adversary_name = payload.get("adversary_name", "")
|
||||
adversary_display = payload.get("adversary_display", adversary_name)
|
||||
eval_round = payload.get("eval_round", 0)
|
||||
|
||||
if not adversary_name or not eval_round:
|
||||
raise HTTPException(status_code=400, detail="adversary_name and eval_round are required")
|
||||
|
||||
try:
|
||||
summary = _import(db, adversary_name, adversary_display, eval_round, current_user)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc))
|
||||
except Exception as exc:
|
||||
logger.error("ATT&CK Evaluation import failed: %s", exc, exc_info=True)
|
||||
raise HTTPException(status_code=502, detail=f"Import failed: {exc}")
|
||||
|
||||
return {
|
||||
"message": f"Import complete — {summary['created']} tests created",
|
||||
**summary,
|
||||
}
|
||||
|
||||
|
||||
@router.post("/attck-evaluations/import-latest")
|
||||
def import_latest_evaluation(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Import the latest available CrowdStrike evaluation round automatically.
|
||||
|
||||
Returns 409 if the latest round was already imported.
|
||||
"""
|
||||
from app.services.attck_evaluations_service import get_latest_round, import_evaluation_round as _import
|
||||
|
||||
try:
|
||||
latest = get_latest_round()
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=502, detail=f"Could not reach MITRE Evaluations API: {exc}")
|
||||
|
||||
try:
|
||||
summary = _import(
|
||||
db,
|
||||
latest["name"],
|
||||
latest.get("display_name", latest["name"]),
|
||||
latest["eval_round"],
|
||||
current_user,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc))
|
||||
except Exception as exc:
|
||||
logger.error("ATT&CK Evaluation import failed: %s", exc, exc_info=True)
|
||||
raise HTTPException(status_code=502, detail=f"Import failed: {exc}")
|
||||
|
||||
return {
|
||||
"message": f"Import complete — {summary['created']} tests created",
|
||||
**summary,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/attck-evaluations/check-new")
|
||||
def check_new_evaluation_round(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Check if a new ATT&CK Evaluation round is available that hasn't been imported yet."""
|
||||
from app.services.attck_evaluations_service import check_for_new_round
|
||||
|
||||
return check_for_new_round(db)
|
||||
|
||||
|
||||
@router.post("/email-test")
|
||||
def send_test_email(
|
||||
payload: EmailTestRequest,
|
||||
|
||||
Reference in New Issue
Block a user