contentctl 3.6.0__py3-none-any.whl → 4.0.2__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.
- contentctl/actions/build.py +89 -0
- contentctl/actions/detection_testing/DetectionTestingManager.py +48 -49
- contentctl/actions/detection_testing/GitService.py +148 -230
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +14 -24
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +43 -17
- contentctl/actions/detection_testing/views/DetectionTestingView.py +3 -2
- contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -8
- contentctl/actions/doc_gen.py +1 -1
- contentctl/actions/initialize.py +28 -65
- contentctl/actions/inspect.py +260 -0
- contentctl/actions/new_content.py +106 -13
- contentctl/actions/release_notes.py +168 -144
- contentctl/actions/reporting.py +24 -13
- contentctl/actions/test.py +39 -20
- contentctl/actions/validate.py +25 -48
- contentctl/contentctl.py +196 -754
- contentctl/enrichments/attack_enrichment.py +69 -19
- contentctl/enrichments/cve_enrichment.py +28 -13
- contentctl/helper/link_validator.py +24 -26
- contentctl/helper/utils.py +7 -3
- contentctl/input/director.py +139 -201
- contentctl/input/new_content_questions.py +63 -61
- contentctl/input/sigma_converter.py +1 -2
- contentctl/input/ssa_detection_builder.py +16 -7
- contentctl/input/yml_reader.py +4 -3
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +487 -154
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +155 -51
- contentctl/objects/alert_action.py +40 -0
- contentctl/objects/atomic.py +212 -0
- contentctl/objects/baseline.py +44 -43
- contentctl/objects/baseline_tags.py +69 -20
- contentctl/objects/config.py +857 -125
- contentctl/objects/constants.py +0 -1
- contentctl/objects/correlation_search.py +1 -1
- contentctl/objects/data_source.py +2 -4
- contentctl/objects/deployment.py +61 -21
- contentctl/objects/deployment_email.py +2 -2
- contentctl/objects/deployment_notable.py +4 -4
- contentctl/objects/deployment_phantom.py +2 -2
- contentctl/objects/deployment_rba.py +3 -4
- contentctl/objects/deployment_scheduling.py +2 -3
- contentctl/objects/deployment_slack.py +2 -2
- contentctl/objects/detection.py +1 -5
- contentctl/objects/detection_tags.py +210 -119
- contentctl/objects/enums.py +312 -24
- contentctl/objects/integration_test.py +1 -1
- contentctl/objects/integration_test_result.py +0 -2
- contentctl/objects/investigation.py +62 -53
- contentctl/objects/investigation_tags.py +30 -6
- contentctl/objects/lookup.py +80 -31
- contentctl/objects/macro.py +29 -45
- contentctl/objects/mitre_attack_enrichment.py +29 -5
- contentctl/objects/observable.py +3 -7
- contentctl/objects/playbook.py +60 -30
- contentctl/objects/playbook_tags.py +45 -8
- contentctl/objects/security_content_object.py +1 -5
- contentctl/objects/ssa_detection.py +8 -4
- contentctl/objects/ssa_detection_tags.py +19 -26
- contentctl/objects/story.py +142 -44
- contentctl/objects/story_tags.py +46 -33
- contentctl/objects/unit_test.py +7 -2
- contentctl/objects/unit_test_attack_data.py +10 -19
- contentctl/objects/unit_test_baseline.py +1 -1
- contentctl/objects/unit_test_old.py +4 -3
- contentctl/objects/unit_test_result.py +5 -3
- contentctl/objects/unit_test_ssa.py +31 -0
- contentctl/output/api_json_output.py +202 -130
- contentctl/output/attack_nav_output.py +20 -9
- contentctl/output/attack_nav_writer.py +3 -3
- contentctl/output/ba_yml_output.py +3 -3
- contentctl/output/conf_output.py +125 -391
- contentctl/output/conf_writer.py +169 -31
- contentctl/output/jinja_writer.py +2 -2
- contentctl/output/json_writer.py +17 -5
- contentctl/output/new_content_yml_output.py +8 -7
- contentctl/output/svg_output.py +17 -27
- contentctl/output/templates/analyticstories_detections.j2 +8 -4
- contentctl/output/templates/analyticstories_investigations.j2 +1 -1
- contentctl/output/templates/analyticstories_stories.j2 +6 -6
- contentctl/output/templates/app.conf.j2 +2 -2
- contentctl/output/templates/app.manifest.j2 +2 -2
- contentctl/output/templates/detection_coverage.j2 +6 -8
- contentctl/output/templates/doc_detection_page.j2 +2 -2
- contentctl/output/templates/doc_detections.j2 +2 -2
- contentctl/output/templates/doc_stories.j2 +1 -1
- contentctl/output/templates/es_investigations_investigations.j2 +1 -1
- contentctl/output/templates/es_investigations_stories.j2 +1 -1
- contentctl/output/templates/header.j2 +2 -1
- contentctl/output/templates/macros.j2 +6 -10
- contentctl/output/templates/savedsearches_baselines.j2 +5 -5
- contentctl/output/templates/savedsearches_detections.j2 +36 -33
- contentctl/output/templates/savedsearches_investigations.j2 +4 -4
- contentctl/output/templates/transforms.j2 +4 -4
- contentctl/output/yml_writer.py +2 -2
- contentctl/templates/app_template/README.md +7 -0
- contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/nav/default.xml +1 -0
- contentctl/templates/app_template/lookups/mitre_enrichment.csv +638 -0
- contentctl/templates/deployments/{00_default_anomaly.yml → escu_default_configuration_anomaly.yml} +1 -2
- contentctl/templates/deployments/{00_default_baseline.yml → escu_default_configuration_baseline.yml} +1 -2
- contentctl/templates/deployments/{00_default_correlation.yml → escu_default_configuration_correlation.yml} +2 -2
- contentctl/templates/deployments/{00_default_hunting.yml → escu_default_configuration_hunting.yml} +2 -2
- contentctl/templates/deployments/{00_default_ttp.yml → escu_default_configuration_ttp.yml} +1 -2
- contentctl/templates/detections/anomalous_usage_of_7zip.yml +0 -1
- contentctl/templates/stories/cobalt_strike.yml +0 -1
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/METADATA +36 -15
- contentctl-4.0.2.dist-info/RECORD +168 -0
- contentctl/actions/detection_testing/DataManipulation.py +0 -149
- contentctl/actions/generate.py +0 -91
- contentctl/helper/config_handler.py +0 -75
- contentctl/input/baseline_builder.py +0 -66
- contentctl/input/basic_builder.py +0 -58
- contentctl/input/detection_builder.py +0 -370
- contentctl/input/investigation_builder.py +0 -42
- contentctl/input/new_content_generator.py +0 -95
- contentctl/input/playbook_builder.py +0 -68
- contentctl/input/story_builder.py +0 -106
- contentctl/objects/app.py +0 -214
- contentctl/objects/repo_config.py +0 -163
- contentctl/objects/test_config.py +0 -630
- contentctl/output/templates/macros_detections.j2 +0 -7
- contentctl/output/templates/splunk_app/README.md +0 -7
- contentctl-3.6.0.dist-info/RECORD +0 -176
- /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_story_detail.txt +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_summary.txt +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_usage_dashboard.txt +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/analytic_stories.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/app.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/commands.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/content-version.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/escu_summary.xml +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/feedback.xml +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/distsearch.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/usage_searches.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/default/use_case_library.conf +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/metadata/default.meta +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon.png +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt.png +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt_2x.png +0 -0
- /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon_2x.png +0 -0
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/LICENSE.md +0 -0
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/WHEEL +0 -0
- {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/entry_points.txt +0 -0
contentctl/input/director.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
-
import
|
|
4
|
-
from dataclasses import dataclass
|
|
3
|
+
from typing import Union
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
5
|
from pydantic import ValidationError
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
from contentctl.input.yml_reader import YmlReader
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
9
11
|
from contentctl.objects.detection import Detection
|
|
10
12
|
from contentctl.objects.story import Story
|
|
13
|
+
|
|
14
|
+
from contentctl.objects.enums import SecurityContentProduct
|
|
11
15
|
from contentctl.objects.baseline import Baseline
|
|
12
16
|
from contentctl.objects.investigation import Investigation
|
|
13
17
|
from contentctl.objects.playbook import Playbook
|
|
@@ -15,34 +19,22 @@ from contentctl.objects.deployment import Deployment
|
|
|
15
19
|
from contentctl.objects.macro import Macro
|
|
16
20
|
from contentctl.objects.lookup import Lookup
|
|
17
21
|
from contentctl.objects.ssa_detection import SSADetection
|
|
22
|
+
from contentctl.objects.atomic import AtomicTest
|
|
23
|
+
from contentctl.objects.security_content_object import SecurityContentObject
|
|
18
24
|
|
|
19
|
-
from contentctl.input.basic_builder import BasicBuilder
|
|
20
|
-
from contentctl.input.detection_builder import DetectionBuilder
|
|
21
|
-
from contentctl.input.ssa_detection_builder import SSADetectionBuilder
|
|
22
|
-
from contentctl.input.playbook_builder import PlaybookBuilder
|
|
23
|
-
from contentctl.input.baseline_builder import BaselineBuilder
|
|
24
|
-
from contentctl.input.investigation_builder import InvestigationBuilder
|
|
25
|
-
from contentctl.input.story_builder import StoryBuilder
|
|
26
|
-
from contentctl.objects.enums import SecurityContentType
|
|
27
|
-
from contentctl.objects.enums import SecurityContentProduct
|
|
28
|
-
from contentctl.objects.enums import DetectionStatus
|
|
29
|
-
from contentctl.helper.utils import Utils
|
|
30
25
|
from contentctl.enrichments.attack_enrichment import AttackEnrichment
|
|
31
|
-
from contentctl.
|
|
32
|
-
|
|
33
|
-
from contentctl.objects.config import Config
|
|
26
|
+
from contentctl.enrichments.cve_enrichment import CveEnrichment
|
|
34
27
|
|
|
28
|
+
from contentctl.objects.config import validate
|
|
35
29
|
|
|
36
30
|
|
|
37
|
-
@dataclass(frozen=True)
|
|
38
|
-
class DirectorInputDto:
|
|
39
|
-
input_path: pathlib.Path
|
|
40
|
-
product: SecurityContentProduct
|
|
41
|
-
config: Config
|
|
42
|
-
|
|
43
31
|
|
|
44
32
|
@dataclass()
|
|
45
33
|
class DirectorOutputDto:
|
|
34
|
+
# Atomic Tests are first because parsing them
|
|
35
|
+
# is far quicker than attack_enrichment
|
|
36
|
+
atomic_tests: Union[list[AtomicTest],None]
|
|
37
|
+
attack_enrichment: AttackEnrichment
|
|
46
38
|
detections: list[Detection]
|
|
47
39
|
stories: list[Story]
|
|
48
40
|
baselines: list[Baseline]
|
|
@@ -52,188 +44,183 @@ class DirectorOutputDto:
|
|
|
52
44
|
lookups: list[Lookup]
|
|
53
45
|
deployments: list[Deployment]
|
|
54
46
|
ssa_detections: list[SSADetection]
|
|
47
|
+
#cve_enrichment: CveEnrichment
|
|
48
|
+
|
|
49
|
+
name_to_content_map: dict[str, SecurityContentObject] = field(default_factory=dict)
|
|
50
|
+
uuid_to_content_map: dict[UUID, SecurityContentObject] = field(default_factory=dict)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
from contentctl.input.ssa_detection_builder import SSADetectionBuilder
|
|
57
|
+
from contentctl.objects.enums import SecurityContentType
|
|
58
|
+
|
|
59
|
+
from contentctl.objects.enums import DetectionStatus
|
|
60
|
+
from contentctl.helper.utils import Utils
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
55
68
|
|
|
56
69
|
|
|
57
70
|
class Director():
|
|
58
|
-
input_dto:
|
|
71
|
+
input_dto: validate
|
|
59
72
|
output_dto: DirectorOutputDto
|
|
60
|
-
basic_builder: BasicBuilder
|
|
61
|
-
playbook_builder: PlaybookBuilder
|
|
62
|
-
baseline_builder: BaselineBuilder
|
|
63
|
-
investigation_builder: InvestigationBuilder
|
|
64
|
-
story_builder: StoryBuilder
|
|
65
|
-
detection_builder: DetectionBuilder
|
|
66
73
|
ssa_detection_builder: SSADetectionBuilder
|
|
67
|
-
|
|
68
|
-
config: Config
|
|
74
|
+
|
|
69
75
|
|
|
70
76
|
|
|
71
77
|
def __init__(self, output_dto: DirectorOutputDto) -> None:
|
|
72
78
|
self.output_dto = output_dto
|
|
73
|
-
self.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
self.ssa_detection_builder = SSADetectionBuilder()
|
|
80
|
+
|
|
81
|
+
def addContentToDictMappings(self, content:SecurityContentObject):
|
|
82
|
+
content_name = content.name
|
|
83
|
+
if isinstance(content,SSADetection):
|
|
84
|
+
# Since SSA detections may have the same name as ESCU detection,
|
|
85
|
+
# for this function we prepend 'SSA ' to the name.
|
|
86
|
+
content_name = f"SSA {content_name}"
|
|
87
|
+
if content_name in self.output_dto.name_to_content_map:
|
|
88
|
+
raise ValueError(f"Duplicate name '{content_name}' with paths:\n"
|
|
89
|
+
f" - {content.file_path}\n"
|
|
90
|
+
f" - {self.output_dto.name_to_content_map[content_name].file_path}")
|
|
91
|
+
elif content.id in self.output_dto.uuid_to_content_map:
|
|
92
|
+
raise ValueError(f"Duplicate id '{content.id}' with paths:\n"
|
|
93
|
+
f" - {content.file_path}\n"
|
|
94
|
+
f" - {self.output_dto.name_to_content_map[content_name].file_path}")
|
|
95
|
+
|
|
96
|
+
self.output_dto.name_to_content_map[content_name] = content
|
|
97
|
+
self.output_dto.uuid_to_content_map[content.id] = content
|
|
98
|
+
|
|
78
99
|
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
|
|
101
|
+
def execute(self, input_dto: validate) -> None:
|
|
102
|
+
self.input_dto = input_dto
|
|
103
|
+
|
|
81
104
|
|
|
82
|
-
self.
|
|
83
|
-
self.
|
|
84
|
-
self.
|
|
85
|
-
self.
|
|
86
|
-
self.
|
|
87
|
-
self.
|
|
88
|
-
self.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
self.createSecurityContent(SecurityContentType.baselines)
|
|
94
|
-
self.createSecurityContent(SecurityContentType.investigations)
|
|
95
|
-
self.createSecurityContent(SecurityContentType.playbooks)
|
|
96
|
-
self.createSecurityContent(SecurityContentType.detections)
|
|
97
|
-
self.createSecurityContent(SecurityContentType.stories)
|
|
98
|
-
elif self.input_dto.product == SecurityContentProduct.SSA:
|
|
99
|
-
self.createSecurityContent(SecurityContentType.ssa_detections)
|
|
105
|
+
self.createSecurityContent(SecurityContentType.deployments)
|
|
106
|
+
self.createSecurityContent(SecurityContentType.lookups)
|
|
107
|
+
self.createSecurityContent(SecurityContentType.macros)
|
|
108
|
+
self.createSecurityContent(SecurityContentType.stories)
|
|
109
|
+
self.createSecurityContent(SecurityContentType.baselines)
|
|
110
|
+
self.createSecurityContent(SecurityContentType.investigations)
|
|
111
|
+
self.createSecurityContent(SecurityContentType.playbooks)
|
|
112
|
+
self.createSecurityContent(SecurityContentType.detections)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
self.createSecurityContent(SecurityContentType.ssa_detections)
|
|
100
116
|
|
|
101
117
|
|
|
102
|
-
def createSecurityContent(self,
|
|
103
|
-
if
|
|
104
|
-
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
def createSecurityContent(self, contentType: SecurityContentType) -> None:
|
|
119
|
+
if contentType == SecurityContentType.ssa_detections:
|
|
120
|
+
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.path, 'ssa_detections'))
|
|
121
|
+
security_content_files = [f for f in files if f.name.startswith('ssa___')]
|
|
122
|
+
|
|
123
|
+
elif contentType in [SecurityContentType.deployments,
|
|
124
|
+
SecurityContentType.lookups,
|
|
125
|
+
SecurityContentType.macros,
|
|
126
|
+
SecurityContentType.stories,
|
|
127
|
+
SecurityContentType.baselines,
|
|
128
|
+
SecurityContentType.investigations,
|
|
129
|
+
SecurityContentType.playbooks,
|
|
130
|
+
SecurityContentType.detections]:
|
|
131
|
+
files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.path, str(contentType.name)))
|
|
132
|
+
security_content_files = [f for f in files if not f.name.startswith('ssa___')]
|
|
107
133
|
else:
|
|
108
|
-
|
|
134
|
+
raise(Exception(f"Cannot createSecurityContent for unknown product."))
|
|
109
135
|
|
|
110
136
|
validation_errors = []
|
|
111
137
|
|
|
112
138
|
already_ran = False
|
|
113
139
|
progress_percent = 0
|
|
114
|
-
|
|
115
|
-
if self.input_dto.product == SecurityContentProduct.SPLUNK_APP or self.input_dto.product == SecurityContentProduct.API:
|
|
116
|
-
security_content_files = [f for f in files if not f.name.startswith('ssa___')]
|
|
117
|
-
elif self.input_dto.product == SecurityContentProduct.SSA:
|
|
118
|
-
security_content_files = [f for f in files if f.name.startswith('ssa___')]
|
|
119
|
-
else:
|
|
120
|
-
raise(Exception(f"Cannot createSecurityContent for unknown product '{self.input_dto.product}'"))
|
|
121
|
-
|
|
122
140
|
|
|
123
141
|
for index,file in enumerate(security_content_files):
|
|
124
142
|
progress_percent = ((index+1)/len(security_content_files)) * 100
|
|
125
143
|
try:
|
|
126
|
-
type_string =
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
144
|
+
type_string = contentType.name.upper()
|
|
145
|
+
modelDict = YmlReader.load_file(file)
|
|
146
|
+
|
|
147
|
+
if contentType == SecurityContentType.lookups:
|
|
148
|
+
lookup = Lookup.model_validate(modelDict,context={"output_dto":self.output_dto, "config":self.input_dto})
|
|
130
149
|
self.output_dto.lookups.append(lookup)
|
|
150
|
+
self.addContentToDictMappings(lookup)
|
|
131
151
|
|
|
132
|
-
elif
|
|
133
|
-
|
|
134
|
-
macro = self.basic_builder.getObject()
|
|
152
|
+
elif contentType == SecurityContentType.macros:
|
|
153
|
+
macro = Macro.model_validate(modelDict,context={"output_dto":self.output_dto})
|
|
135
154
|
self.output_dto.macros.append(macro)
|
|
155
|
+
self.addContentToDictMappings(macro)
|
|
136
156
|
|
|
137
|
-
elif
|
|
138
|
-
|
|
139
|
-
deployment = self.basic_builder.getObject()
|
|
157
|
+
elif contentType == SecurityContentType.deployments:
|
|
158
|
+
deployment = Deployment.model_validate(modelDict,context={"output_dto":self.output_dto})
|
|
140
159
|
self.output_dto.deployments.append(deployment)
|
|
160
|
+
self.addContentToDictMappings(deployment)
|
|
141
161
|
|
|
142
|
-
elif
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
self.
|
|
162
|
+
elif contentType == SecurityContentType.playbooks:
|
|
163
|
+
playbook = Playbook.model_validate(modelDict,context={"output_dto":self.output_dto})
|
|
164
|
+
self.output_dto.playbooks.append(playbook)
|
|
165
|
+
self.addContentToDictMappings(playbook)
|
|
146
166
|
|
|
147
|
-
elif
|
|
148
|
-
|
|
149
|
-
baseline = self.baseline_builder.getObject()
|
|
167
|
+
elif contentType == SecurityContentType.baselines:
|
|
168
|
+
baseline = Baseline.model_validate(modelDict,context={"output_dto":self.output_dto})
|
|
150
169
|
self.output_dto.baselines.append(baseline)
|
|
170
|
+
self.addContentToDictMappings(baseline)
|
|
151
171
|
|
|
152
|
-
elif
|
|
153
|
-
|
|
154
|
-
investigation = self.investigation_builder.getObject()
|
|
172
|
+
elif contentType == SecurityContentType.investigations:
|
|
173
|
+
investigation = Investigation.model_validate(modelDict,context={"output_dto":self.output_dto})
|
|
155
174
|
self.output_dto.investigations.append(investigation)
|
|
175
|
+
self.addContentToDictMappings(investigation)
|
|
156
176
|
|
|
157
|
-
elif
|
|
158
|
-
|
|
159
|
-
story = self.story_builder.getObject()
|
|
177
|
+
elif contentType == SecurityContentType.stories:
|
|
178
|
+
story = Story.model_validate(modelDict,context={"output_dto":self.output_dto})
|
|
160
179
|
self.output_dto.stories.append(story)
|
|
180
|
+
self.addContentToDictMappings(story)
|
|
161
181
|
|
|
162
|
-
elif
|
|
163
|
-
|
|
164
|
-
detection = self.detection_builder.getObject()
|
|
182
|
+
elif contentType == SecurityContentType.detections:
|
|
183
|
+
detection = Detection.model_validate(modelDict,context={"output_dto":self.output_dto})
|
|
165
184
|
self.output_dto.detections.append(detection)
|
|
185
|
+
self.addContentToDictMappings(detection)
|
|
166
186
|
|
|
167
|
-
elif
|
|
168
|
-
self.constructSSADetection(self.ssa_detection_builder, file)
|
|
169
|
-
|
|
170
|
-
if
|
|
171
|
-
self.output_dto.ssa_detections.append(
|
|
187
|
+
elif contentType == SecurityContentType.ssa_detections:
|
|
188
|
+
self.constructSSADetection(self.ssa_detection_builder, self.output_dto,str(file))
|
|
189
|
+
ssa_detection = self.ssa_detection_builder.getObject()
|
|
190
|
+
if ssa_detection.status in [DetectionStatus.production.value, DetectionStatus.validation.value]:
|
|
191
|
+
self.output_dto.ssa_detections.append(ssa_detection)
|
|
192
|
+
self.addContentToDictMappings(ssa_detection)
|
|
172
193
|
|
|
173
194
|
else:
|
|
174
|
-
raise Exception(f"Unsupported type: [{
|
|
195
|
+
raise Exception(f"Unsupported type: [{contentType}]")
|
|
175
196
|
|
|
176
197
|
if (sys.stdout.isatty() and sys.stdin.isatty() and sys.stderr.isatty()) or not already_ran:
|
|
177
198
|
already_ran = True
|
|
178
199
|
print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True)
|
|
179
200
|
|
|
180
201
|
except (ValidationError, ValueError) as e:
|
|
181
|
-
relative_path = file.absolute().relative_to(self.input_dto.
|
|
202
|
+
relative_path = file.absolute().relative_to(self.input_dto.path.absolute())
|
|
182
203
|
validation_errors.append((relative_path,e))
|
|
204
|
+
|
|
183
205
|
|
|
184
|
-
print(f"\r{f'{
|
|
206
|
+
print(f"\r{f'{contentType.name.upper()} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True)
|
|
185
207
|
print("Done!")
|
|
186
208
|
|
|
187
209
|
if len(validation_errors) > 0:
|
|
188
|
-
errors_string = '\n\n'.join([f"{e_tuple[0]}\
|
|
210
|
+
errors_string = '\n\n'.join([f"File: {e_tuple[0]}\nError: {str(e_tuple[1])}" for e_tuple in validation_errors])
|
|
211
|
+
#print(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
|
|
212
|
+
# We quit after validation a single type/group of content because it can cause significant cascading errors in subsequent
|
|
213
|
+
# types of content (since they may import or otherwise use it)
|
|
189
214
|
raise Exception(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
|
|
190
215
|
|
|
191
216
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
builder.setObject(file_path)
|
|
195
|
-
builder.addDeployment(self.output_dto.deployments)
|
|
196
|
-
builder.addMitreAttackEnrichment(self.attack_enrichment)
|
|
197
|
-
builder.addKillChainPhase()
|
|
198
|
-
builder.addCIS()
|
|
199
|
-
builder.addNist()
|
|
200
|
-
builder.addDatamodel()
|
|
201
|
-
builder.addRBA()
|
|
202
|
-
builder.addProvidingTechnologies()
|
|
203
|
-
builder.addNesFields()
|
|
204
|
-
builder.addAnnotations()
|
|
205
|
-
builder.addMappings()
|
|
206
|
-
builder.addBaseline(self.output_dto.baselines)
|
|
207
|
-
builder.addPlaybook(self.output_dto.playbooks)
|
|
208
|
-
builder.addMacros(self.output_dto.macros)
|
|
209
|
-
builder.addLookups(self.output_dto.lookups)
|
|
210
|
-
|
|
211
|
-
if self.input_dto.config.enrichments.attack_enrichment:
|
|
212
|
-
builder.addMitreAttackEnrichment(self.attack_enrichment)
|
|
213
|
-
|
|
214
|
-
if self.input_dto.config.enrichments.cve_enrichment:
|
|
215
|
-
builder.addCve()
|
|
216
|
-
|
|
217
|
-
if self.input_dto.config.enrichments.splunk_app_enrichment:
|
|
218
|
-
builder.addSplunkApp()
|
|
219
|
-
|
|
220
|
-
# Skip all integration tests if configured to do so
|
|
221
|
-
# TODO: is there a better way to handle this? The `test` portion of the config is not defined for validate
|
|
222
|
-
if (self.input_dto.config.test is not None) and (not self.input_dto.config.test.enable_integration_testing):
|
|
223
|
-
builder.skipIntegrationTests()
|
|
224
|
-
|
|
225
|
-
if builder.security_content_obj is not None and \
|
|
226
|
-
builder.security_content_obj.tags is not None and \
|
|
227
|
-
isinstance(builder.security_content_obj.tags.manual_test,str):
|
|
228
|
-
# Set all tests, both Unit AND Integration, to manual_test. Note that integration test messages
|
|
229
|
-
# will intentionally overwrite the justification in the skipIntegrationTests call above.
|
|
230
|
-
builder.skipAllTests(builder.security_content_obj.tags.manual_test)
|
|
231
|
-
|
|
217
|
+
|
|
218
|
+
|
|
232
219
|
|
|
233
|
-
def constructSSADetection(self, builder:
|
|
220
|
+
def constructSSADetection(self, builder: SSADetectionBuilder, directorOutput:DirectorOutputDto, file_path: str) -> None:
|
|
234
221
|
builder.reset()
|
|
235
|
-
builder.setObject(file_path)
|
|
236
|
-
builder.
|
|
222
|
+
builder.setObject(file_path,self.output_dto)
|
|
223
|
+
builder.addMitreAttackEnrichmentNew(directorOutput.attack_enrichment)
|
|
237
224
|
builder.addKillChainPhase()
|
|
238
225
|
builder.addCIS()
|
|
239
226
|
builder.addNist()
|
|
@@ -243,53 +230,4 @@ class Director():
|
|
|
243
230
|
builder.addRBA()
|
|
244
231
|
|
|
245
232
|
|
|
246
|
-
|
|
247
|
-
builder.reset()
|
|
248
|
-
builder.setObject(file_path)
|
|
249
|
-
builder.addDetections(self.output_dto.detections, self.input_dto.config)
|
|
250
|
-
builder.addInvestigations(self.output_dto.investigations)
|
|
251
|
-
builder.addBaselines(self.output_dto.baselines)
|
|
252
|
-
builder.addAuthorCompanyName()
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def constructBaseline(self, builder: BaselineBuilder, file_path: str) -> None:
|
|
256
|
-
builder.reset()
|
|
257
|
-
builder.setObject(file_path)
|
|
258
|
-
builder.addDeployment(self.output_dto.deployments)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def constructDeployment(self, builder: BasicBuilder, file_path: str) -> None:
|
|
262
|
-
builder.reset()
|
|
263
|
-
builder.setObject(file_path, SecurityContentType.deployments)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def constructLookup(self, builder: BasicBuilder, file_path: str) -> None:
|
|
267
|
-
builder.reset()
|
|
268
|
-
builder.setObject(file_path, SecurityContentType.lookups)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def constructMacro(self, builder: BasicBuilder, file_path: str) -> None:
|
|
272
|
-
builder.reset()
|
|
273
|
-
builder.setObject(file_path, SecurityContentType.macros)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def constructPlaybook(self, builder: PlaybookBuilder, file_path: str) -> None:
|
|
277
|
-
builder.reset()
|
|
278
|
-
builder.setObject(file_path)
|
|
279
|
-
builder.addDetections()
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
def constructTest(self, builder: BasicBuilder, file_path: str) -> None:
|
|
283
|
-
builder.reset()
|
|
284
|
-
builder.setObject(file_path, SecurityContentType.unit_tests)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def constructInvestigation(self, builder: InvestigationBuilder, file_path: str) -> None:
|
|
288
|
-
builder.reset()
|
|
289
|
-
builder.setObject(file_path)
|
|
290
|
-
builder.addInputs()
|
|
291
|
-
builder.addLowercaseName()
|
|
292
|
-
|
|
293
|
-
def constructObjects(self, builder: BasicBuilder, file_path: str) -> None:
|
|
294
|
-
builder.reset()
|
|
295
|
-
builder.setObject(file_path)
|
|
233
|
+
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
class NewContentQuestions():
|
|
1
|
+
class NewContentQuestions:
|
|
4
2
|
|
|
5
3
|
@classmethod
|
|
6
4
|
def get_questions_detection(self) -> list:
|
|
7
5
|
questions = [
|
|
8
6
|
{
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
"type": "text",
|
|
8
|
+
"message": "enter detection name",
|
|
9
|
+
"name": "detection_name",
|
|
10
|
+
"default": "Powershell Encoded Command",
|
|
13
11
|
},
|
|
14
12
|
{
|
|
15
13
|
'type': 'select',
|
|
@@ -30,18 +28,23 @@ class NewContentQuestions():
|
|
|
30
28
|
'name': 'detection_author',
|
|
31
29
|
},
|
|
32
30
|
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
"type": "text",
|
|
32
|
+
"message": "enter author name",
|
|
33
|
+
"name": "detection_author",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "select",
|
|
37
|
+
"message": "select a detection type",
|
|
38
|
+
"name": "detection_type",
|
|
39
|
+
"choices": [
|
|
40
|
+
"TTP",
|
|
41
|
+
"Anomaly",
|
|
42
|
+
"Hunting",
|
|
43
|
+
"Baseline",
|
|
44
|
+
"Investigation",
|
|
45
|
+
"Correlation",
|
|
43
46
|
],
|
|
44
|
-
|
|
47
|
+
"default": "TTP",
|
|
45
48
|
},
|
|
46
49
|
{
|
|
47
50
|
'type': 'checkbox',
|
|
@@ -89,16 +92,16 @@ class NewContentQuestions():
|
|
|
89
92
|
]
|
|
90
93
|
},
|
|
91
94
|
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
"type": "text",
|
|
96
|
+
"message": "enter search (spl)",
|
|
97
|
+
"name": "detection_search",
|
|
98
|
+
"default": "| UPDATE_SPL",
|
|
96
99
|
},
|
|
97
100
|
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
"type": "text",
|
|
102
|
+
"message": "enter MITRE ATT&CK Technique IDs related to the detection, comma delimited for multiple",
|
|
103
|
+
"name": "mitre_attack_ids",
|
|
104
|
+
"default": "T1003.002",
|
|
102
105
|
},
|
|
103
106
|
{
|
|
104
107
|
'type': 'select',
|
|
@@ -117,49 +120,48 @@ class NewContentQuestions():
|
|
|
117
120
|
]
|
|
118
121
|
return questions
|
|
119
122
|
|
|
120
|
-
|
|
121
123
|
@classmethod
|
|
122
124
|
def get_questions_story(self) -> list:
|
|
123
125
|
questions = [
|
|
124
126
|
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
"type": "text",
|
|
128
|
+
"message": "enter story name",
|
|
129
|
+
"name": "story_name",
|
|
130
|
+
"default": "Suspicious Powershell Behavior",
|
|
129
131
|
},
|
|
130
132
|
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
"type": "text",
|
|
134
|
+
"message": "enter author name",
|
|
135
|
+
"name": "story_author",
|
|
134
136
|
},
|
|
135
137
|
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
]
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
138
|
+
"type": "checkbox",
|
|
139
|
+
"message": "select a category",
|
|
140
|
+
"name": "category",
|
|
141
|
+
"choices": [
|
|
142
|
+
"Adversary Tactics",
|
|
143
|
+
"Account Compromise",
|
|
144
|
+
"Unauthorized Software",
|
|
145
|
+
"Best Practices",
|
|
146
|
+
"Cloud Security",
|
|
147
|
+
"Command and Control",
|
|
148
|
+
"Lateral Movement",
|
|
149
|
+
"Ransomware",
|
|
150
|
+
"Privilege Escalation",
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"type": "select",
|
|
155
|
+
"message": "select a use case",
|
|
156
|
+
"name": "usecase",
|
|
157
|
+
"choices": [
|
|
158
|
+
"Advanced Threat Detection",
|
|
159
|
+
"Security Monitoring",
|
|
160
|
+
"Compliance",
|
|
161
|
+
"Insider Threat",
|
|
162
|
+
"Application Security",
|
|
163
|
+
"Other",
|
|
162
164
|
],
|
|
163
165
|
},
|
|
164
166
|
]
|
|
165
|
-
return questions
|
|
167
|
+
return questions
|
|
@@ -213,7 +213,6 @@ class SigmaConverter():
|
|
|
213
213
|
|
|
214
214
|
def read_detection(self, detection_path : str) -> Detection:
|
|
215
215
|
yml_dict = YmlReader.load_file(detection_path)
|
|
216
|
-
yml_dict["tags"]["name"] = yml_dict["name"]
|
|
217
216
|
|
|
218
217
|
#SSA Detections are ALLOWED to have names longer than 67 characters,
|
|
219
218
|
#unlike Splunk App Detections. Because we still want to use the
|
|
@@ -234,7 +233,7 @@ class SigmaConverter():
|
|
|
234
233
|
|
|
235
234
|
detection.name = name
|
|
236
235
|
|
|
237
|
-
|
|
236
|
+
|
|
238
237
|
return detection
|
|
239
238
|
|
|
240
239
|
|