Skip to content

Commit 7e59f18

Browse files
authored
Merge pull request #378 from splunk/TR-3506_mitre_update
updated mitre map gen This was previously reviewed and passed, the autoupdated content in the last commit was trivial.
2 parents 7c03fa1 + 816e3e3 commit 7e59f18

File tree

2 files changed

+64
-41
lines changed

2 files changed

+64
-41
lines changed

contentctl/output/attack_nav_output.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from typing import List, Union
21
import pathlib
2+
from typing import List, Union
33

44
from contentctl.objects.detection import Detection
55
from contentctl.output.attack_nav_writer import AttackNavWriter
@@ -10,14 +10,21 @@ def writeObjects(
1010
self, detections: List[Detection], output_path: pathlib.Path
1111
) -> None:
1212
techniques: dict[str, dict[str, Union[List[str], int]]] = {}
13+
1314
for detection in detections:
1415
for tactic in detection.tags.mitre_attack_id:
1516
if tactic not in techniques:
1617
techniques[tactic] = {"score": 0, "file_paths": []}
1718

18-
detection_url = f"https://github.com/splunk/security_content/blob/develop/detections/{detection.source}/{detection.file_path.name}"
19-
techniques[tactic]["score"] += 1
20-
techniques[tactic]["file_paths"].append(detection_url)
19+
detection_type = detection.source
20+
detection_id = detection.id
21+
22+
# Store all three pieces of information separately
23+
detection_info = f"{detection_type}|{detection_id}|{detection.name}"
24+
25+
techniques[tactic]["score"] = techniques[tactic].get("score", 0) + 1
26+
if isinstance(techniques[tactic]["file_paths"], list):
27+
techniques[tactic]["file_paths"].append(detection_info)
2128

2229
"""
2330
for detection in objects:

contentctl/output/attack_nav_writer.py

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import json
2-
from typing import Union, List
32
import pathlib
3+
from typing import List, Union
44

5-
VERSION = "4.3"
5+
VERSION = "4.5"
66
NAME = "Detection Coverage"
7-
DESCRIPTION = "security_content detection coverage"
8-
DOMAIN = "mitre-enterprise"
7+
DESCRIPTION = "Security Content Detection Coverage"
8+
DOMAIN = "enterprise-attack"
99

1010

1111
class AttackNavWriter:
@@ -14,52 +14,68 @@ def writeAttackNavFile(
1414
mitre_techniques: dict[str, dict[str, Union[List[str], int]]],
1515
output_path: pathlib.Path,
1616
) -> None:
17-
max_count = 0
18-
for technique_id in mitre_techniques.keys():
19-
if mitre_techniques[technique_id]["score"] > max_count:
20-
max_count = mitre_techniques[technique_id]["score"]
17+
max_count = max(
18+
(technique["score"] for technique in mitre_techniques.values()), default=0
19+
)
2120

2221
layer_json = {
23-
"version": VERSION,
22+
"versions": {"attack": "16", "navigator": "5.1.0", "layer": VERSION},
2423
"name": NAME,
2524
"description": DESCRIPTION,
2625
"domain": DOMAIN,
2726
"techniques": [],
27+
"gradient": {
28+
"colors": ["#ffffff", "#66b1ff", "#096ed7"],
29+
"minValue": 0,
30+
"maxValue": max_count,
31+
},
32+
"filters": {
33+
"platforms": [
34+
"Windows",
35+
"Linux",
36+
"macOS",
37+
"Network",
38+
"AWS",
39+
"GCP",
40+
"Azure",
41+
"Azure AD",
42+
"Office 365",
43+
"SaaS",
44+
]
45+
},
46+
"layout": {
47+
"layout": "side",
48+
"showName": True,
49+
"showID": True,
50+
"showAggregateScores": False,
51+
},
52+
"legendItems": [
53+
{"label": "No detections", "color": "#ffffff"},
54+
{"label": "Has detections", "color": "#66b1ff"},
55+
],
56+
"showTacticRowBackground": True,
57+
"tacticRowBackground": "#dddddd",
58+
"selectTechniquesAcrossTactics": True,
2859
}
2960

30-
layer_json["gradient"] = {
31-
"colors": ["#ffffff", "#66b1ff", "#096ed7"],
32-
"minValue": 0,
33-
"maxValue": max_count,
34-
}
35-
36-
layer_json["filters"] = {
37-
"platforms": [
38-
"Windows",
39-
"Linux",
40-
"macOS",
41-
"AWS",
42-
"GCP",
43-
"Azure",
44-
"Office 365",
45-
"SaaS",
46-
]
47-
}
61+
for technique_id, data in mitre_techniques.items():
62+
links = []
63+
for detection_info in data["file_paths"]:
64+
# Split the detection info into its components
65+
detection_type, detection_id, detection_name = detection_info.split("|")
4866

49-
layer_json["legendItems"] = [
50-
{"label": "NO available detections", "color": "#ffffff"},
51-
{"label": "Some detections available", "color": "#66b1ff"},
52-
]
67+
# Construct research website URL (without the name)
68+
research_url = (
69+
f"https://research.splunk.com/{detection_type}/{detection_id}/"
70+
)
5371

54-
layer_json["showTacticRowBackground"] = True
55-
layer_json["tacticRowBackground"] = "#dddddd"
56-
layer_json["sorting"] = 3
72+
links.append({"label": detection_name, "url": research_url})
5773

58-
for technique_id in mitre_techniques.keys():
5974
layer_technique = {
6075
"techniqueID": technique_id,
61-
"score": mitre_techniques[technique_id]["score"],
62-
"comment": "\n\n".join(mitre_techniques[technique_id]["file_paths"]),
76+
"score": data["score"],
77+
"enabled": True,
78+
"links": links,
6379
}
6480
layer_json["techniques"].append(layer_technique)
6581

0 commit comments

Comments
 (0)