contentctl 4.4.7__py3-none-any.whl → 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. contentctl/__init__.py +1 -1
  2. contentctl/actions/build.py +102 -57
  3. contentctl/actions/deploy_acs.py +29 -24
  4. contentctl/actions/detection_testing/DetectionTestingManager.py +66 -42
  5. contentctl/actions/detection_testing/GitService.py +134 -76
  6. contentctl/actions/detection_testing/generate_detection_coverage_badge.py +48 -30
  7. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +192 -147
  8. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +45 -32
  9. contentctl/actions/detection_testing/progress_bar.py +9 -6
  10. contentctl/actions/detection_testing/views/DetectionTestingView.py +16 -19
  11. contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +1 -5
  12. contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +2 -2
  13. contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +1 -4
  14. contentctl/actions/doc_gen.py +9 -5
  15. contentctl/actions/initialize.py +45 -33
  16. contentctl/actions/inspect.py +118 -61
  17. contentctl/actions/new_content.py +155 -108
  18. contentctl/actions/release_notes.py +276 -146
  19. contentctl/actions/reporting.py +23 -19
  20. contentctl/actions/test.py +33 -28
  21. contentctl/actions/validate.py +55 -34
  22. contentctl/api.py +54 -45
  23. contentctl/contentctl.py +124 -90
  24. contentctl/enrichments/attack_enrichment.py +112 -72
  25. contentctl/enrichments/cve_enrichment.py +34 -28
  26. contentctl/enrichments/splunk_app_enrichment.py +38 -36
  27. contentctl/helper/link_validator.py +101 -78
  28. contentctl/helper/splunk_app.py +69 -41
  29. contentctl/helper/utils.py +58 -53
  30. contentctl/input/director.py +68 -36
  31. contentctl/input/new_content_questions.py +27 -35
  32. contentctl/input/yml_reader.py +28 -18
  33. contentctl/objects/abstract_security_content_objects/detection_abstract.py +303 -259
  34. contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +115 -52
  35. contentctl/objects/alert_action.py +10 -9
  36. contentctl/objects/annotated_types.py +1 -1
  37. contentctl/objects/atomic.py +65 -54
  38. contentctl/objects/base_test.py +5 -3
  39. contentctl/objects/base_test_result.py +19 -11
  40. contentctl/objects/baseline.py +62 -30
  41. contentctl/objects/baseline_tags.py +30 -24
  42. contentctl/objects/config.py +790 -597
  43. contentctl/objects/constants.py +33 -56
  44. contentctl/objects/correlation_search.py +150 -136
  45. contentctl/objects/dashboard.py +55 -41
  46. contentctl/objects/data_source.py +16 -17
  47. contentctl/objects/deployment.py +43 -44
  48. contentctl/objects/deployment_email.py +3 -2
  49. contentctl/objects/deployment_notable.py +4 -2
  50. contentctl/objects/deployment_phantom.py +7 -6
  51. contentctl/objects/deployment_rba.py +3 -2
  52. contentctl/objects/deployment_scheduling.py +3 -2
  53. contentctl/objects/deployment_slack.py +3 -2
  54. contentctl/objects/detection.py +5 -2
  55. contentctl/objects/detection_metadata.py +1 -0
  56. contentctl/objects/detection_stanza.py +7 -2
  57. contentctl/objects/detection_tags.py +58 -103
  58. contentctl/objects/drilldown.py +66 -34
  59. contentctl/objects/enums.py +81 -100
  60. contentctl/objects/errors.py +16 -24
  61. contentctl/objects/integration_test.py +3 -3
  62. contentctl/objects/integration_test_result.py +1 -0
  63. contentctl/objects/investigation.py +59 -36
  64. contentctl/objects/investigation_tags.py +30 -19
  65. contentctl/objects/lookup.py +304 -101
  66. contentctl/objects/macro.py +55 -39
  67. contentctl/objects/manual_test.py +3 -3
  68. contentctl/objects/manual_test_result.py +1 -0
  69. contentctl/objects/mitre_attack_enrichment.py +17 -16
  70. contentctl/objects/notable_action.py +2 -1
  71. contentctl/objects/notable_event.py +1 -3
  72. contentctl/objects/playbook.py +37 -35
  73. contentctl/objects/playbook_tags.py +23 -13
  74. contentctl/objects/rba.py +96 -0
  75. contentctl/objects/risk_analysis_action.py +15 -11
  76. contentctl/objects/risk_event.py +110 -160
  77. contentctl/objects/risk_object.py +1 -0
  78. contentctl/objects/savedsearches_conf.py +9 -7
  79. contentctl/objects/security_content_object.py +5 -2
  80. contentctl/objects/story.py +54 -49
  81. contentctl/objects/story_tags.py +56 -45
  82. contentctl/objects/test_attack_data.py +2 -1
  83. contentctl/objects/test_group.py +5 -2
  84. contentctl/objects/threat_object.py +1 -0
  85. contentctl/objects/throttling.py +27 -18
  86. contentctl/objects/unit_test.py +3 -4
  87. contentctl/objects/unit_test_baseline.py +5 -5
  88. contentctl/objects/unit_test_result.py +6 -6
  89. contentctl/output/api_json_output.py +233 -220
  90. contentctl/output/attack_nav_output.py +21 -21
  91. contentctl/output/attack_nav_writer.py +29 -37
  92. contentctl/output/conf_output.py +235 -172
  93. contentctl/output/conf_writer.py +201 -125
  94. contentctl/output/data_source_writer.py +38 -26
  95. contentctl/output/doc_md_output.py +53 -27
  96. contentctl/output/jinja_writer.py +19 -15
  97. contentctl/output/json_writer.py +21 -11
  98. contentctl/output/svg_output.py +56 -38
  99. contentctl/output/templates/analyticstories_detections.j2 +2 -2
  100. contentctl/output/templates/analyticstories_stories.j2 +1 -1
  101. contentctl/output/templates/collections.j2 +1 -1
  102. contentctl/output/templates/doc_detections.j2 +0 -5
  103. contentctl/output/templates/es_investigations_investigations.j2 +1 -1
  104. contentctl/output/templates/es_investigations_stories.j2 +1 -1
  105. contentctl/output/templates/savedsearches_baselines.j2 +2 -2
  106. contentctl/output/templates/savedsearches_detections.j2 +10 -11
  107. contentctl/output/templates/savedsearches_investigations.j2 +2 -2
  108. contentctl/output/templates/transforms.j2 +6 -8
  109. contentctl/output/yml_writer.py +29 -20
  110. contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
  111. contentctl/templates/stories/cobalt_strike.yml +1 -0
  112. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/METADATA +5 -4
  113. contentctl-5.0.0.dist-info/RECORD +168 -0
  114. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/WHEEL +1 -1
  115. contentctl/actions/initialize_old.py +0 -245
  116. contentctl/objects/event_source.py +0 -11
  117. contentctl/objects/observable.py +0 -37
  118. contentctl/output/detection_writer.py +0 -28
  119. contentctl/output/new_content_yml_output.py +0 -56
  120. contentctl/output/yml_output.py +0 -66
  121. contentctl-4.4.7.dist-info/RECORD +0 -173
  122. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/LICENSE.md +0 -0
  123. {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,246 +1,259 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from contentctl.objects.detection import Detection
6
+ from contentctl.objects.lookup import Lookup
7
+ from contentctl.objects.macro import Macro
8
+ from contentctl.objects.story import Story
9
+ from contentctl.objects.baseline import Baseline
10
+ from contentctl.objects.investigation import Investigation
11
+ from contentctl.objects.deployment import Deployment
12
+
1
13
  import os
2
- import json
3
14
  import pathlib
4
15
 
5
16
  from contentctl.output.json_writer import JsonWriter
6
- from contentctl.objects.enums import SecurityContentType
7
- from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
8
- SecurityContentObject_Abstract,
9
- )
10
-
11
17
 
12
18
 
13
19
  class ApiJsonOutput:
20
+ output_path: pathlib.Path
21
+ app_label: str
22
+
23
+ def __init__(self, output_path: pathlib.Path, app_label: str):
24
+ self.output_path = output_path
25
+ self.app_label = app_label
14
26
 
15
- def writeObjects(
27
+ def writeDetections(
16
28
  self,
17
- objects: list[SecurityContentObject_Abstract],
18
- output_path: pathlib.Path,
19
- app_label:str = "ESCU",
20
- contentType: SecurityContentType = None
29
+ objects: list[Detection],
21
30
  ) -> None:
22
- """#Serialize all objects
23
- try:
24
- for obj in objects:
25
-
26
- serialized_objects.append(obj.model_dump())
27
- except Exception as e:
28
- raise Exception(f"Error serializing object with name '{obj.name}' and type '{type(obj).__name__}': '{str(e)}'")
29
- """
30
-
31
- if contentType == SecurityContentType.detections:
32
- detections = [
33
- detection.model_dump(
34
- include=set(
35
- [
36
- "name",
37
- "author",
38
- "date",
39
- "version",
40
- "id",
41
- "description",
42
- "tags",
43
- "search",
44
- "how_to_implement",
45
- "known_false_positives",
46
- "references",
47
- "datamodel",
48
- "macros",
49
- "lookups",
50
- "source",
51
- "nes_fields",
52
- ]
53
- )
31
+ detections = [
32
+ detection.model_dump(
33
+ include=set(
34
+ [
35
+ "name",
36
+ "author",
37
+ "date",
38
+ "version",
39
+ "id",
40
+ "description",
41
+ "tags",
42
+ "search",
43
+ "how_to_implement",
44
+ "known_false_positives",
45
+ "references",
46
+ "datamodel",
47
+ "macros",
48
+ "lookups",
49
+ "source",
50
+ "nes_fields",
51
+ ]
54
52
  )
55
- for detection in objects
56
- ]
57
- #Only a subset of macro fields are required:
58
- # for detection in detections:
59
- # new_macros = []
60
- # for macro in detection.get("macros",[]):
61
- # new_macro_fields = {}
62
- # new_macro_fields["name"] = macro.get("name")
63
- # new_macro_fields["definition"] = macro.get("definition")
64
- # new_macro_fields["description"] = macro.get("description")
65
- # if len(macro.get("arguments", [])) > 0:
66
- # new_macro_fields["arguments"] = macro.get("arguments")
67
- # new_macros.append(new_macro_fields)
68
- # detection["macros"] = new_macros
69
- # del()
70
-
71
-
72
- JsonWriter.writeJsonObject(
73
- os.path.join(output_path, "detections.json"), "detections", detections
74
53
  )
54
+ for detection in objects
55
+ ]
56
+ # Only a subset of macro fields are required:
57
+ # for detection in detections:
58
+ # new_macros = []
59
+ # for macro in detection.get("macros",[]):
60
+ # new_macro_fields = {}
61
+ # new_macro_fields["name"] = macro.get("name")
62
+ # new_macro_fields["definition"] = macro.get("definition")
63
+ # new_macro_fields["description"] = macro.get("description")
64
+ # if len(macro.get("arguments", [])) > 0:
65
+ # new_macro_fields["arguments"] = macro.get("arguments")
66
+ # new_macros.append(new_macro_fields)
67
+ # detection["macros"] = new_macros
68
+ # del()
75
69
 
76
- elif contentType == SecurityContentType.macros:
77
- macros = [
78
- macro.model_dump(include=set(["definition", "description", "name"]))
79
- for macro in objects
80
- ]
81
- for macro in macros:
82
- for k in ["author", "date","version","id","references"]:
83
- if k in macro:
84
- del(macro[k])
85
- JsonWriter.writeJsonObject(
86
- os.path.join(output_path, "macros.json"), "macros", macros
87
- )
70
+ JsonWriter.writeJsonObject(
71
+ os.path.join(self.output_path, "detections.json"), "detections", detections
72
+ )
88
73
 
89
- elif contentType == SecurityContentType.stories:
90
- stories = [
91
- story.model_dump(
92
- include=set(
93
- [
94
- "name",
95
- "author",
96
- "date",
97
- "version",
98
- "id",
99
- "description",
100
- "narrative",
101
- "references",
102
- "tags",
103
- "detections_names",
104
- "investigation_names",
105
- "baseline_names",
106
- "detections",
107
- ]
108
- )
74
+ def writeMacros(
75
+ self,
76
+ objects: list[Macro],
77
+ ) -> None:
78
+ macros = [
79
+ macro.model_dump(include=set(["definition", "description", "name"]))
80
+ for macro in objects
81
+ ]
82
+ for macro in macros:
83
+ for k in ["author", "date", "version", "id", "references"]:
84
+ if k in macro:
85
+ del macro[k]
86
+ JsonWriter.writeJsonObject(
87
+ os.path.join(self.output_path, "macros.json"), "macros", macros
88
+ )
89
+
90
+ def writeStories(
91
+ self,
92
+ objects: list[Story],
93
+ ) -> None:
94
+ stories = [
95
+ story.model_dump(
96
+ include=set(
97
+ [
98
+ "name",
99
+ "author",
100
+ "date",
101
+ "version",
102
+ "id",
103
+ "description",
104
+ "narrative",
105
+ "references",
106
+ "tags",
107
+ "detections_names",
108
+ "investigation_names",
109
+ "baseline_names",
110
+ "detections",
111
+ ]
109
112
  )
110
- for story in objects
111
- ]
112
- # Only get certain fields from detections
113
- for story in stories:
114
- # Only use a small subset of fields from the detection
115
- story["detections"] = [
116
- {
117
- "name": detection["name"],
118
- "source": detection["source"],
119
- "type": detection["type"],
120
- "tags": detection["tags"].get("mitre_attack_enrichments", []),
121
- }
122
- for detection in story["detections"]
123
- ]
124
- story["detection_names"] = [f"{app_label} - {name} - Rule" for name in story["detection_names"]]
125
-
126
-
127
- JsonWriter.writeJsonObject(
128
- os.path.join(output_path, "stories.json"), "stories", stories
129
113
  )
114
+ for story in objects
115
+ ]
116
+ # Only get certain fields from detections
117
+ for story in stories:
118
+ # Only use a small subset of fields from the detection
119
+ story["detections"] = [
120
+ {
121
+ "name": detection["name"],
122
+ "source": detection["source"],
123
+ "type": detection["type"],
124
+ "tags": detection["tags"].get("mitre_attack_enrichments", []),
125
+ }
126
+ for detection in story["detections"]
127
+ ]
128
+ story["detection_names"] = [
129
+ f"{self.app_label} - {name} - Rule" for name in story["detection_names"]
130
+ ]
130
131
 
131
- elif contentType == SecurityContentType.baselines:
132
- try:
133
- baselines = [
134
- baseline.model_dump(
135
- include=set(
136
- [
137
- "name",
138
- "author",
139
- "date",
140
- "version",
141
- "id",
142
- "description",
143
- "type",
144
- "datamodel",
145
- "search",
146
- "how_to_implement",
147
- "known_false_positives",
148
- "references",
149
- "tags",
150
- ]
151
- )
152
- )
153
- for baseline in objects
154
- ]
155
- except Exception as e:
156
- print(e)
157
- print('wait')
158
-
159
- JsonWriter.writeJsonObject(
160
- os.path.join(output_path, "baselines.json"), "baselines", baselines
132
+ JsonWriter.writeJsonObject(
133
+ os.path.join(self.output_path, "stories.json"), "stories", stories
134
+ )
135
+
136
+ def writeBaselines(
137
+ self,
138
+ objects: list[Baseline],
139
+ ) -> None:
140
+ baselines = [
141
+ baseline.model_dump(
142
+ include=set(
143
+ [
144
+ "name",
145
+ "author",
146
+ "date",
147
+ "version",
148
+ "id",
149
+ "description",
150
+ "type",
151
+ "datamodel",
152
+ "search",
153
+ "how_to_implement",
154
+ "known_false_positives",
155
+ "references",
156
+ "tags",
157
+ ]
161
158
  )
159
+ )
160
+ for baseline in objects
161
+ ]
162
162
 
163
- elif contentType == SecurityContentType.investigations:
164
- investigations = [
165
- investigation.model_dump(
166
- include=set(
167
- [
168
- "name",
169
- "author",
170
- "date",
171
- "version",
172
- "id",
173
- "description",
174
- "type",
175
- "datamodel",
176
- "search",
177
- "how_to_implemnet",
178
- "known_false_positives",
179
- "references",
180
- "inputs",
181
- "tags",
182
- "lowercase_name",
183
- ]
184
- )
163
+ JsonWriter.writeJsonObject(
164
+ os.path.join(self.output_path, "baselines.json"), "baselines", baselines
165
+ )
166
+
167
+ def writeInvestigations(
168
+ self,
169
+ objects: list[Investigation],
170
+ ) -> None:
171
+ investigations = [
172
+ investigation.model_dump(
173
+ include=set(
174
+ [
175
+ "name",
176
+ "author",
177
+ "date",
178
+ "version",
179
+ "id",
180
+ "description",
181
+ "type",
182
+ "datamodel",
183
+ "search",
184
+ "how_to_implemnet",
185
+ "known_false_positives",
186
+ "references",
187
+ "inputs",
188
+ "tags",
189
+ "lowercase_name",
190
+ ]
185
191
  )
186
- for investigation in objects
187
- ]
188
- JsonWriter.writeJsonObject(
189
- os.path.join(output_path, "response_tasks.json"),
190
- "response_tasks",
191
- investigations,
192
192
  )
193
+ for investigation in objects
194
+ ]
195
+ JsonWriter.writeJsonObject(
196
+ os.path.join(self.output_path, "response_tasks.json"),
197
+ "response_tasks",
198
+ investigations,
199
+ )
193
200
 
194
- elif contentType == SecurityContentType.lookups:
195
- lookups = [
196
- lookup.model_dump(
197
- include=set(
198
- [
199
- "name",
200
- "description",
201
- "collection",
202
- "fields_list",
203
- "filename",
204
- "default_match",
205
- "match_type",
206
- "min_matches",
207
- "case_sensitive_match",
208
- ]
209
- )
201
+ def writeLookups(
202
+ self,
203
+ objects: list[Lookup],
204
+ ) -> None:
205
+ lookups = [
206
+ lookup.model_dump(
207
+ include=set(
208
+ [
209
+ "name",
210
+ "description",
211
+ "collection",
212
+ "fields_list",
213
+ "filename",
214
+ "default_match",
215
+ "match_type",
216
+ "min_matches",
217
+ "case_sensitive_match",
218
+ ]
210
219
  )
211
- for lookup in objects
212
- ]
213
- for lookup in lookups:
214
- for k in ["author","date","version","id","references"]:
215
- if k in lookup:
216
- del(lookup[k])
217
- JsonWriter.writeJsonObject(
218
- os.path.join(output_path, "lookups.json"), "lookups", lookups
219
220
  )
221
+ for lookup in objects
222
+ ]
223
+ for lookup in lookups:
224
+ for k in ["author", "date", "version", "id", "references"]:
225
+ if k in lookup:
226
+ del lookup[k]
227
+ JsonWriter.writeJsonObject(
228
+ os.path.join(self.output_path, "lookups.json"), "lookups", lookups
229
+ )
220
230
 
221
- elif contentType == SecurityContentType.deployments:
222
- deployments = [
223
- deployment.model_dump(
224
- include=set(
225
- [
226
- "name",
227
- "author",
228
- "date",
229
- "version",
230
- "id",
231
- "description",
232
- "scheduling",
233
- "rba",
234
- "tags"
235
- ]
236
- )
231
+ def writeDeployments(
232
+ self,
233
+ objects: list[Deployment],
234
+ ) -> None:
235
+ deployments = [
236
+ deployment.model_dump(
237
+ include=set(
238
+ [
239
+ "name",
240
+ "author",
241
+ "date",
242
+ "version",
243
+ "id",
244
+ "description",
245
+ "scheduling",
246
+ "rba",
247
+ "tags",
248
+ ]
237
249
  )
238
- for deployment in objects
239
- ]
240
- #references are not to be included, but have been deleted in the
241
- #model_serialization logic
242
- JsonWriter.writeJsonObject(
243
- os.path.join(output_path, "deployments.json"),
244
- "deployments",
245
- deployments,
246
- )
250
+ )
251
+ for deployment in objects
252
+ ]
253
+ # references are not to be included, but have been deleted in the
254
+ # model_serialization logic
255
+ JsonWriter.writeJsonObject(
256
+ os.path.join(self.output_path, "deployments.json"),
257
+ "deployments",
258
+ deployments,
259
+ )
@@ -1,25 +1,25 @@
1
- import os
2
- from typing import List,Union
1
+ from typing import List, Union
3
2
  import pathlib
4
3
 
5
4
  from contentctl.objects.detection import Detection
6
5
  from contentctl.output.attack_nav_writer import AttackNavWriter
7
6
 
8
7
 
9
- class AttackNavOutput():
10
-
11
- def writeObjects(self, detections: List[Detection], output_path: pathlib.Path) -> None:
12
- techniques:dict[str,dict[str,Union[List[str],int]]] = {}
8
+ class AttackNavOutput:
9
+ def writeObjects(
10
+ self, detections: List[Detection], output_path: pathlib.Path
11
+ ) -> None:
12
+ techniques: dict[str, dict[str, Union[List[str], int]]] = {}
13
13
  for detection in detections:
14
14
  for tactic in detection.tags.mitre_attack_id:
15
15
  if tactic not in techniques:
16
- techniques[tactic] = {'score':0,'file_paths':[]}
17
-
16
+ techniques[tactic] = {"score": 0, "file_paths": []}
17
+
18
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)
21
-
22
- '''
19
+ techniques[tactic]["score"] += 1
20
+ techniques[tactic]["file_paths"].append(detection_url)
21
+
22
+ """
23
23
  for detection in objects:
24
24
  if detection.tags.mitre_attack_enrichments:
25
25
  for mitre_attack_enrichment in detection.tags.mitre_attack_enrichments:
@@ -31,16 +31,16 @@ class AttackNavOutput():
31
31
  else:
32
32
  techniques[mitre_attack_enrichment.mitre_attack_id]['score'] = techniques[mitre_attack_enrichment.mitre_attack_id]['score'] + 1
33
33
  techniques[mitre_attack_enrichment.mitre_attack_id]['file_paths'].append('https://github.com/splunk/security_content/blob/develop/detections/' + detection.getSource() + '/' + self.convertNameToFileName(detection.name))
34
- '''
35
- AttackNavWriter.writeAttackNavFile(techniques, output_path / 'coverage.json')
36
-
34
+ """
35
+ AttackNavWriter.writeAttackNavFile(techniques, output_path / "coverage.json")
37
36
 
38
37
  def convertNameToFileName(self, name: str):
39
- file_name = name \
40
- .replace(' ', '_') \
41
- .replace('-','_') \
42
- .replace('.','_') \
43
- .replace('/','_') \
38
+ file_name = (
39
+ name.replace(" ", "_")
40
+ .replace("-", "_")
41
+ .replace(".", "_")
42
+ .replace("/", "_")
44
43
  .lower()
45
- file_name = file_name + '.yml'
44
+ )
45
+ file_name = file_name + ".yml"
46
46
  return file_name
@@ -1,75 +1,67 @@
1
-
2
1
  import json
3
2
  from typing import Union, List
4
3
  import pathlib
4
+
5
5
  VERSION = "4.3"
6
6
  NAME = "Detection Coverage"
7
7
  DESCRIPTION = "security_content detection coverage"
8
8
  DOMAIN = "mitre-enterprise"
9
9
 
10
10
 
11
- class AttackNavWriter():
12
-
11
+ class AttackNavWriter:
13
12
  @staticmethod
14
- def writeAttackNavFile(mitre_techniques : dict[str,dict[str,Union[List[str],int]]], output_path : pathlib.Path) -> None:
13
+ def writeAttackNavFile(
14
+ mitre_techniques: dict[str, dict[str, Union[List[str], int]]],
15
+ output_path: pathlib.Path,
16
+ ) -> None:
15
17
  max_count = 0
16
18
  for technique_id in mitre_techniques.keys():
17
- if mitre_techniques[technique_id]['score'] > max_count:
18
- max_count = mitre_techniques[technique_id]['score']
19
-
19
+ if mitre_techniques[technique_id]["score"] > max_count:
20
+ max_count = mitre_techniques[technique_id]["score"]
21
+
20
22
  layer_json = {
21
23
  "version": VERSION,
22
24
  "name": NAME,
23
25
  "description": DESCRIPTION,
24
26
  "domain": DOMAIN,
25
- "techniques": []
27
+ "techniques": [],
26
28
  }
27
29
 
28
30
  layer_json["gradient"] = {
29
- "colors": [
30
- "#ffffff",
31
- "#66b1ff",
32
- "#096ed7"
33
- ],
31
+ "colors": ["#ffffff", "#66b1ff", "#096ed7"],
34
32
  "minValue": 0,
35
- "maxValue": max_count
33
+ "maxValue": max_count,
36
34
  }
37
35
 
38
36
  layer_json["filters"] = {
39
- "platforms":
40
- ["Windows",
41
- "Linux",
42
- "macOS",
43
- "AWS",
44
- "GCP",
45
- "Azure",
46
- "Office 365",
47
- "SaaS"
48
- ]
37
+ "platforms": [
38
+ "Windows",
39
+ "Linux",
40
+ "macOS",
41
+ "AWS",
42
+ "GCP",
43
+ "Azure",
44
+ "Office 365",
45
+ "SaaS",
46
+ ]
49
47
  }
50
48
 
51
49
  layer_json["legendItems"] = [
52
- {
53
- "label": "NO available detections",
54
- "color": "#ffffff"
55
- },
56
- {
57
- "label": "Some detections available",
58
- "color": "#66b1ff"
59
- }
50
+ {"label": "NO available detections", "color": "#ffffff"},
51
+ {"label": "Some detections available", "color": "#66b1ff"},
60
52
  ]
61
53
 
62
- layer_json['showTacticRowBackground'] = True
63
- layer_json['tacticRowBackground'] = "#dddddd"
54
+ layer_json["showTacticRowBackground"] = True
55
+ layer_json["tacticRowBackground"] = "#dddddd"
64
56
  layer_json["sorting"] = 3
65
57
 
66
58
  for technique_id in mitre_techniques.keys():
67
59
  layer_technique = {
68
60
  "techniqueID": technique_id,
69
- "score": mitre_techniques[technique_id]['score'],
70
- "comment": "\n\n".join(mitre_techniques[technique_id]['file_paths'])
61
+ "score": mitre_techniques[technique_id]["score"],
62
+ "comment": "\n\n".join(mitre_techniques[technique_id]["file_paths"]),
71
63
  }
72
64
  layer_json["techniques"].append(layer_technique)
73
65
 
74
- with open(output_path, 'w') as outfile:
66
+ with open(output_path, "w") as outfile:
75
67
  json.dump(layer_json, outfile, ensure_ascii=False, indent=4)