feat(compliance): add DORA (EU 2022/2554) framework with ATT&CK mappings
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Implements the Digital Operational Resilience Act as a compliance framework
using the same pattern as CIS Controls v8 (hardcoded curated mappings,
no official STIX bundle exists for DORA).
22 controls across 5 chapters mapped to MITRE ATT&CK techniques:
Ch. II — ICT Risk Management (Art. 5–15): governance, identification,
protection, detection, response, backup, threat intel
Ch. III — Incident Management (Art. 17–19): classification, reporting
Ch. IV — Resilience Testing (Art. 24–27): general testing + TLPT
(Art. 26 explicitly based on TIBER-EU/ATT&CK threat-led testing)
Ch. V — Third-Party Risk (Art. 28, 30, 42): supply chain, trusted rels.
Ch. VI — Information Sharing (Art. 45)
Technique mappings derived from ENISA DORA guidelines and TIBER-EU framework.
Import is triggered via POST /api/v1/compliance/import/dora (admin only).
Frontend: new 'DORA' button in the Compliance page import section.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from app.services.compliance_service import (
|
|||||||
from app.services.compliance_import_service import (
|
from app.services.compliance_import_service import (
|
||||||
import_nist_800_53_mappings,
|
import_nist_800_53_mappings,
|
||||||
import_cis_controls_v8_mappings,
|
import_cis_controls_v8_mappings,
|
||||||
|
import_dora_mappings,
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/compliance", tags=["compliance"])
|
router = APIRouter(prefix="/compliance", tags=["compliance"])
|
||||||
@@ -119,3 +120,13 @@ def import_cis(
|
|||||||
"""Import CIS Controls v8 mappings (admin only)."""
|
"""Import CIS Controls v8 mappings (admin only)."""
|
||||||
result = import_cis_controls_v8_mappings(db)
|
result = import_cis_controls_v8_mappings(db)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/import/dora")
|
||||||
|
def import_dora(
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(require_role("admin")),
|
||||||
|
):
|
||||||
|
"""Import DORA (EU 2022/2554) compliance mappings (admin only)."""
|
||||||
|
result = import_dora_mappings(db)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -482,6 +482,252 @@ def import_cis_controls_v8_mappings(db: Session) -> dict:
|
|||||||
return summary
|
return summary
|
||||||
|
|
||||||
|
|
||||||
|
def import_dora_mappings(db: Session) -> dict:
|
||||||
|
"""Import DORA (Digital Operational Resilience Act) with ATT&CK technique mappings.
|
||||||
|
|
||||||
|
DORA (EU 2022/2554) applies to financial entities and ICT third-party providers.
|
||||||
|
Controls map the key cybersecurity articles (Chapters II–VI) to MITRE ATT&CK
|
||||||
|
techniques based on ENISA guidance and TIBER-EU threat-led testing framework.
|
||||||
|
|
||||||
|
Returns a summary dict with counts.
|
||||||
|
"""
|
||||||
|
# ── 1. Create or get framework ────────────────────────────────
|
||||||
|
framework = (
|
||||||
|
db.query(ComplianceFramework)
|
||||||
|
.filter(ComplianceFramework.name == "DORA")
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
if not framework:
|
||||||
|
framework = ComplianceFramework(
|
||||||
|
name="DORA",
|
||||||
|
version="2022/2554",
|
||||||
|
description=(
|
||||||
|
"Digital Operational Resilience Act (Regulation EU 2022/2554) — "
|
||||||
|
"EU regulation establishing ICT risk management, incident reporting, "
|
||||||
|
"digital operational resilience testing, and ICT third-party risk "
|
||||||
|
"management requirements for financial entities."
|
||||||
|
),
|
||||||
|
url="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32022R2554",
|
||||||
|
is_active=True,
|
||||||
|
)
|
||||||
|
db.add(framework)
|
||||||
|
db.flush()
|
||||||
|
logger.info("Created DORA framework")
|
||||||
|
else:
|
||||||
|
logger.info("DORA framework already exists")
|
||||||
|
|
||||||
|
# ── 2. Control definitions with ATT&CK mappings ───────────────
|
||||||
|
# Based on ENISA DORA guidelines and TIBER-EU threat intelligence framework.
|
||||||
|
# Each control maps to a DORA article and the ATT&CK techniques it addresses.
|
||||||
|
DORA_CONTROLS = [
|
||||||
|
# ─── Chapter II — ICT Risk Management ────────────────────────────
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.5",
|
||||||
|
"title": "Governance and Organisation",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1078", "T1136", "T1098", "T1087"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.6",
|
||||||
|
"title": "ICT Risk Management Framework",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1595", "T1590", "T1589", "T1046", "T1018", "T1082"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.7",
|
||||||
|
"title": "ICT Systems, Protocols and Tools",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1574", "T1543", "T1112", "T1546", "T1195", "T1133"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.8",
|
||||||
|
"title": "Identification",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1018", "T1082", "T1083", "T1087", "T1590", "T1592"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.9",
|
||||||
|
"title": "Protection and Prevention",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1078", "T1548", "T1134", "T1190", "T1574", "T1543", "T1021"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.10",
|
||||||
|
"title": "Detection",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1562", "T1070", "T1059", "T1053", "T1547", "T1037"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.11",
|
||||||
|
"title": "Response and Recovery",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1486", "T1490", "T1561", "T1485", "T1048", "T1041"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.12",
|
||||||
|
"title": "Backup Policies and Recovery Methods",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1486", "T1490", "T1561", "T1485"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.13",
|
||||||
|
"title": "Learning and Evolving",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1566", "T1589", "T1590", "T1595", "T1598"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.14",
|
||||||
|
"title": "Communication",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1114", "T1566", "T1102", "T1071"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.15",
|
||||||
|
"title": "Further Harmonisation of ICT Risk Management Tools",
|
||||||
|
"category": "Chapter II — ICT Risk Management",
|
||||||
|
"techniques": ["T1078", "T1190", "T1133", "T1021", "T1199"],
|
||||||
|
},
|
||||||
|
# ─── Chapter III — ICT-related Incident Management ────────────────
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.17",
|
||||||
|
"title": "ICT-related Incidents Classification",
|
||||||
|
"category": "Chapter III — Incident Management",
|
||||||
|
"techniques": ["T1499", "T1498", "T1486", "T1041", "T1048", "T1565"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.18",
|
||||||
|
"title": "Major ICT-Related Incidents Reporting",
|
||||||
|
"category": "Chapter III — Incident Management",
|
||||||
|
"techniques": ["T1486", "T1041", "T1048", "T1499", "T1498"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.19",
|
||||||
|
"title": "Harmonisation of Reporting Content and Formats",
|
||||||
|
"category": "Chapter III — Incident Management",
|
||||||
|
"techniques": ["T1566", "T1190", "T1203", "T1059"],
|
||||||
|
},
|
||||||
|
# ─── Chapter IV — Digital Operational Resilience Testing ──────────
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.24",
|
||||||
|
"title": "General Digital Operational Resilience Testing",
|
||||||
|
"category": "Chapter IV — Resilience Testing",
|
||||||
|
"techniques": ["T1059", "T1190", "T1046", "T1595", "T1078"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.25",
|
||||||
|
"title": "Testing of ICT Tools and Systems",
|
||||||
|
"category": "Chapter IV — Resilience Testing",
|
||||||
|
"techniques": ["T1059", "T1190", "T1046", "T1595", "T1078", "T1068", "T1210"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.26",
|
||||||
|
"title": "Advanced Testing — Threat-Led Penetration Testing (TLPT)",
|
||||||
|
"category": "Chapter IV — Resilience Testing",
|
||||||
|
"techniques": [
|
||||||
|
"T1566", "T1204", "T1055", "T1059", "T1021", "T1078",
|
||||||
|
"T1190", "T1046", "T1548", "T1134", "T1027",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.27",
|
||||||
|
"title": "Requirements for Testers Carrying Out TLPT",
|
||||||
|
"category": "Chapter IV — Resilience Testing",
|
||||||
|
"techniques": ["T1595", "T1046", "T1190", "T1059", "T1078"],
|
||||||
|
},
|
||||||
|
# ─── Chapter V — ICT Third-Party Risk Management ──────────────────
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.28",
|
||||||
|
"title": "General Principles of ICT Third-Party Risk Management",
|
||||||
|
"category": "Chapter V — Third-Party Risk",
|
||||||
|
"techniques": ["T1199", "T1195", "T1078", "T1133"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.30",
|
||||||
|
"title": "Key Contractual Provisions for ICT Services",
|
||||||
|
"category": "Chapter V — Third-Party Risk",
|
||||||
|
"techniques": ["T1199", "T1195", "T1078"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.42",
|
||||||
|
"title": "Oversight of Critical ICT Third-Party Providers",
|
||||||
|
"category": "Chapter V — Third-Party Risk",
|
||||||
|
"techniques": ["T1199", "T1195", "T1133", "T1078", "T1190"],
|
||||||
|
},
|
||||||
|
# ─── Chapter VI — Information Sharing ────────────────────────────
|
||||||
|
{
|
||||||
|
"control_id": "DORA-Art.45",
|
||||||
|
"title": "Arrangements for Information Sharing on Cyber Threats",
|
||||||
|
"category": "Chapter VI — Information Sharing",
|
||||||
|
"techniques": ["T1566", "T1589", "T1590", "T1595", "T1598"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Build technique lookup
|
||||||
|
all_techniques = {t.mitre_id: t for t in db.query(Technique).all()}
|
||||||
|
|
||||||
|
existing_controls = {
|
||||||
|
c.control_id: c
|
||||||
|
for c in db.query(ComplianceControl)
|
||||||
|
.filter(ComplianceControl.framework_id == framework.id)
|
||||||
|
.all()
|
||||||
|
}
|
||||||
|
|
||||||
|
existing_mappings = set()
|
||||||
|
for m in (
|
||||||
|
db.query(ComplianceControlMapping)
|
||||||
|
.join(ComplianceControl)
|
||||||
|
.filter(ComplianceControl.framework_id == framework.id)
|
||||||
|
.all()
|
||||||
|
):
|
||||||
|
existing_mappings.add((str(m.compliance_control_id), str(m.technique_id)))
|
||||||
|
|
||||||
|
controls_created = 0
|
||||||
|
mappings_created = 0
|
||||||
|
|
||||||
|
for item in DORA_CONTROLS:
|
||||||
|
if item["control_id"] in existing_controls:
|
||||||
|
control = existing_controls[item["control_id"]]
|
||||||
|
else:
|
||||||
|
control = ComplianceControl(
|
||||||
|
framework_id=framework.id,
|
||||||
|
control_id=item["control_id"],
|
||||||
|
title=item["title"],
|
||||||
|
category=item["category"],
|
||||||
|
)
|
||||||
|
db.add(control)
|
||||||
|
db.flush()
|
||||||
|
existing_controls[item["control_id"]] = control
|
||||||
|
controls_created += 1
|
||||||
|
|
||||||
|
for mitre_id in item["techniques"]:
|
||||||
|
technique = all_techniques.get(mitre_id)
|
||||||
|
if not technique:
|
||||||
|
continue
|
||||||
|
key = (str(control.id), str(technique.id))
|
||||||
|
if key in existing_mappings:
|
||||||
|
continue
|
||||||
|
mapping = ComplianceControlMapping(
|
||||||
|
compliance_control_id=control.id,
|
||||||
|
technique_id=technique.id,
|
||||||
|
)
|
||||||
|
db.add(mapping)
|
||||||
|
existing_mappings.add(key)
|
||||||
|
mappings_created += 1
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"framework": framework.name,
|
||||||
|
"controls_created": controls_created,
|
||||||
|
"controls_existing": len(existing_controls) - controls_created,
|
||||||
|
"mappings_created": mappings_created,
|
||||||
|
"total_controls": len(existing_controls),
|
||||||
|
}
|
||||||
|
logger.info(f"DORA import complete: {summary}")
|
||||||
|
return summary
|
||||||
|
|
||||||
|
|
||||||
def _get_nist_category(family_code: str) -> str:
|
def _get_nist_category(family_code: str) -> str:
|
||||||
"""Map NIST 800-53 family code to category name."""
|
"""Map NIST 800-53 family code to category name."""
|
||||||
categories = {
|
categories = {
|
||||||
|
|||||||
@@ -120,3 +120,9 @@ export async function importCisMappings(): Promise<Record<string, unknown>> {
|
|||||||
const { data } = await client.post("/compliance/import/cis-controls-v8");
|
const { data } = await client.post("/compliance/import/cis-controls-v8");
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Import DORA (EU 2022/2554) compliance mappings (admin). */
|
||||||
|
export async function importDoraMappings(): Promise<Record<string, unknown>> {
|
||||||
|
const { data } = await client.post("/compliance/import/dora");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
downloadComplianceCSV,
|
downloadComplianceCSV,
|
||||||
importNistMappings,
|
importNistMappings,
|
||||||
importCisMappings,
|
importCisMappings,
|
||||||
|
importDoraMappings,
|
||||||
type ComplianceFrameworkSummary,
|
type ComplianceFrameworkSummary,
|
||||||
} from "../api/compliance";
|
} from "../api/compliance";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
@@ -81,7 +82,15 @@ export default function CompliancePage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isImporting = importNist.isPending || importCis.isPending;
|
const importDora = useMutation({
|
||||||
|
mutationFn: importDoraMappings,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["compliance-frameworks"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["compliance-status"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isImporting = importNist.isPending || importCis.isPending || importDora.isPending;
|
||||||
|
|
||||||
if (isLoading && !frameworkStatus) {
|
if (isLoading && !frameworkStatus) {
|
||||||
return (
|
return (
|
||||||
@@ -215,10 +224,18 @@ export default function CompliancePage() {
|
|||||||
{importCis.isPending ? <Loader2 className="h-3 w-3 animate-spin" /> : <Plus className="h-3 w-3" />}
|
{importCis.isPending ? <Loader2 className="h-3 w-3 animate-spin" /> : <Plus className="h-3 w-3" />}
|
||||||
CIS Controls v8
|
CIS Controls v8
|
||||||
</button>
|
</button>
|
||||||
{(importNist.isSuccess || importCis.isSuccess) && (
|
<button
|
||||||
|
onClick={() => importDora.mutate()}
|
||||||
|
disabled={isImporting}
|
||||||
|
className="flex items-center gap-1.5 rounded-lg border border-gray-700 bg-gray-800 px-3 py-1.5 text-xs font-medium text-gray-300 hover:border-cyan-500/50 hover:text-white transition-colors disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{importDora.isPending ? <Loader2 className="h-3 w-3 animate-spin" /> : <Plus className="h-3 w-3" />}
|
||||||
|
DORA
|
||||||
|
</button>
|
||||||
|
{(importNist.isSuccess || importCis.isSuccess || importDora.isSuccess) && (
|
||||||
<span className="text-xs text-green-400">Import complete</span>
|
<span className="text-xs text-green-400">Import complete</span>
|
||||||
)}
|
)}
|
||||||
{(importNist.isError || importCis.isError) && (
|
{(importNist.isError || importCis.isError || importDora.isError) && (
|
||||||
<span className="text-xs text-red-400">Import failed</span>
|
<span className="text-xs text-red-400">Import failed</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user