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.
Files changed (142) hide show
  1. contentctl/actions/build.py +89 -0
  2. contentctl/actions/detection_testing/DetectionTestingManager.py +48 -49
  3. contentctl/actions/detection_testing/GitService.py +148 -230
  4. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +14 -24
  5. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +43 -17
  6. contentctl/actions/detection_testing/views/DetectionTestingView.py +3 -2
  7. contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -8
  8. contentctl/actions/doc_gen.py +1 -1
  9. contentctl/actions/initialize.py +28 -65
  10. contentctl/actions/inspect.py +260 -0
  11. contentctl/actions/new_content.py +106 -13
  12. contentctl/actions/release_notes.py +168 -144
  13. contentctl/actions/reporting.py +24 -13
  14. contentctl/actions/test.py +39 -20
  15. contentctl/actions/validate.py +25 -48
  16. contentctl/contentctl.py +196 -754
  17. contentctl/enrichments/attack_enrichment.py +69 -19
  18. contentctl/enrichments/cve_enrichment.py +28 -13
  19. contentctl/helper/link_validator.py +24 -26
  20. contentctl/helper/utils.py +7 -3
  21. contentctl/input/director.py +139 -201
  22. contentctl/input/new_content_questions.py +63 -61
  23. contentctl/input/sigma_converter.py +1 -2
  24. contentctl/input/ssa_detection_builder.py +16 -7
  25. contentctl/input/yml_reader.py +4 -3
  26. contentctl/objects/abstract_security_content_objects/detection_abstract.py +487 -154
  27. contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +155 -51
  28. contentctl/objects/alert_action.py +40 -0
  29. contentctl/objects/atomic.py +212 -0
  30. contentctl/objects/baseline.py +44 -43
  31. contentctl/objects/baseline_tags.py +69 -20
  32. contentctl/objects/config.py +857 -125
  33. contentctl/objects/constants.py +0 -1
  34. contentctl/objects/correlation_search.py +1 -1
  35. contentctl/objects/data_source.py +2 -4
  36. contentctl/objects/deployment.py +61 -21
  37. contentctl/objects/deployment_email.py +2 -2
  38. contentctl/objects/deployment_notable.py +4 -4
  39. contentctl/objects/deployment_phantom.py +2 -2
  40. contentctl/objects/deployment_rba.py +3 -4
  41. contentctl/objects/deployment_scheduling.py +2 -3
  42. contentctl/objects/deployment_slack.py +2 -2
  43. contentctl/objects/detection.py +1 -5
  44. contentctl/objects/detection_tags.py +210 -119
  45. contentctl/objects/enums.py +312 -24
  46. contentctl/objects/integration_test.py +1 -1
  47. contentctl/objects/integration_test_result.py +0 -2
  48. contentctl/objects/investigation.py +62 -53
  49. contentctl/objects/investigation_tags.py +30 -6
  50. contentctl/objects/lookup.py +80 -31
  51. contentctl/objects/macro.py +29 -45
  52. contentctl/objects/mitre_attack_enrichment.py +29 -5
  53. contentctl/objects/observable.py +3 -7
  54. contentctl/objects/playbook.py +60 -30
  55. contentctl/objects/playbook_tags.py +45 -8
  56. contentctl/objects/security_content_object.py +1 -5
  57. contentctl/objects/ssa_detection.py +8 -4
  58. contentctl/objects/ssa_detection_tags.py +19 -26
  59. contentctl/objects/story.py +142 -44
  60. contentctl/objects/story_tags.py +46 -33
  61. contentctl/objects/unit_test.py +7 -2
  62. contentctl/objects/unit_test_attack_data.py +10 -19
  63. contentctl/objects/unit_test_baseline.py +1 -1
  64. contentctl/objects/unit_test_old.py +4 -3
  65. contentctl/objects/unit_test_result.py +5 -3
  66. contentctl/objects/unit_test_ssa.py +31 -0
  67. contentctl/output/api_json_output.py +202 -130
  68. contentctl/output/attack_nav_output.py +20 -9
  69. contentctl/output/attack_nav_writer.py +3 -3
  70. contentctl/output/ba_yml_output.py +3 -3
  71. contentctl/output/conf_output.py +125 -391
  72. contentctl/output/conf_writer.py +169 -31
  73. contentctl/output/jinja_writer.py +2 -2
  74. contentctl/output/json_writer.py +17 -5
  75. contentctl/output/new_content_yml_output.py +8 -7
  76. contentctl/output/svg_output.py +17 -27
  77. contentctl/output/templates/analyticstories_detections.j2 +8 -4
  78. contentctl/output/templates/analyticstories_investigations.j2 +1 -1
  79. contentctl/output/templates/analyticstories_stories.j2 +6 -6
  80. contentctl/output/templates/app.conf.j2 +2 -2
  81. contentctl/output/templates/app.manifest.j2 +2 -2
  82. contentctl/output/templates/detection_coverage.j2 +6 -8
  83. contentctl/output/templates/doc_detection_page.j2 +2 -2
  84. contentctl/output/templates/doc_detections.j2 +2 -2
  85. contentctl/output/templates/doc_stories.j2 +1 -1
  86. contentctl/output/templates/es_investigations_investigations.j2 +1 -1
  87. contentctl/output/templates/es_investigations_stories.j2 +1 -1
  88. contentctl/output/templates/header.j2 +2 -1
  89. contentctl/output/templates/macros.j2 +6 -10
  90. contentctl/output/templates/savedsearches_baselines.j2 +5 -5
  91. contentctl/output/templates/savedsearches_detections.j2 +36 -33
  92. contentctl/output/templates/savedsearches_investigations.j2 +4 -4
  93. contentctl/output/templates/transforms.j2 +4 -4
  94. contentctl/output/yml_writer.py +2 -2
  95. contentctl/templates/app_template/README.md +7 -0
  96. contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/nav/default.xml +1 -0
  97. contentctl/templates/app_template/lookups/mitre_enrichment.csv +638 -0
  98. contentctl/templates/deployments/{00_default_anomaly.yml → escu_default_configuration_anomaly.yml} +1 -2
  99. contentctl/templates/deployments/{00_default_baseline.yml → escu_default_configuration_baseline.yml} +1 -2
  100. contentctl/templates/deployments/{00_default_correlation.yml → escu_default_configuration_correlation.yml} +2 -2
  101. contentctl/templates/deployments/{00_default_hunting.yml → escu_default_configuration_hunting.yml} +2 -2
  102. contentctl/templates/deployments/{00_default_ttp.yml → escu_default_configuration_ttp.yml} +1 -2
  103. contentctl/templates/detections/anomalous_usage_of_7zip.yml +0 -1
  104. contentctl/templates/stories/cobalt_strike.yml +0 -1
  105. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/METADATA +36 -15
  106. contentctl-4.0.2.dist-info/RECORD +168 -0
  107. contentctl/actions/detection_testing/DataManipulation.py +0 -149
  108. contentctl/actions/generate.py +0 -91
  109. contentctl/helper/config_handler.py +0 -75
  110. contentctl/input/baseline_builder.py +0 -66
  111. contentctl/input/basic_builder.py +0 -58
  112. contentctl/input/detection_builder.py +0 -370
  113. contentctl/input/investigation_builder.py +0 -42
  114. contentctl/input/new_content_generator.py +0 -95
  115. contentctl/input/playbook_builder.py +0 -68
  116. contentctl/input/story_builder.py +0 -106
  117. contentctl/objects/app.py +0 -214
  118. contentctl/objects/repo_config.py +0 -163
  119. contentctl/objects/test_config.py +0 -630
  120. contentctl/output/templates/macros_detections.j2 +0 -7
  121. contentctl/output/templates/splunk_app/README.md +0 -7
  122. contentctl-3.6.0.dist-info/RECORD +0 -176
  123. /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_story_detail.txt +0 -0
  124. /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_summary.txt +0 -0
  125. /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_usage_dashboard.txt +0 -0
  126. /contentctl/{output/templates/splunk_app → templates/app_template}/default/analytic_stories.conf +0 -0
  127. /contentctl/{output/templates/splunk_app → templates/app_template}/default/app.conf +0 -0
  128. /contentctl/{output/templates/splunk_app → templates/app_template}/default/commands.conf +0 -0
  129. /contentctl/{output/templates/splunk_app → templates/app_template}/default/content-version.conf +0 -0
  130. /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/escu_summary.xml +0 -0
  131. /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/feedback.xml +0 -0
  132. /contentctl/{output/templates/splunk_app → templates/app_template}/default/distsearch.conf +0 -0
  133. /contentctl/{output/templates/splunk_app → templates/app_template}/default/usage_searches.conf +0 -0
  134. /contentctl/{output/templates/splunk_app → templates/app_template}/default/use_case_library.conf +0 -0
  135. /contentctl/{output/templates/splunk_app → templates/app_template}/metadata/default.meta +0 -0
  136. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon.png +0 -0
  137. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt.png +0 -0
  138. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt_2x.png +0 -0
  139. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon_2x.png +0 -0
  140. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/LICENSE.md +0 -0
  141. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/WHEEL +0 -0
  142. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/entry_points.txt +0 -0
@@ -1,13 +1,17 @@
1
1
  import os
2
2
  import sys
3
- import pathlib
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.objects.config import Config
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: DirectorInputDto
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
- attack_enrichment: dict
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.attack_enrichment = dict()
74
-
75
-
76
- def execute(self, input_dto: DirectorInputDto) -> None:
77
- self.input_dto = input_dto
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
- if self.input_dto.config.enrichments.attack_enrichment:
80
- self.attack_enrichment = AttackEnrichment.get_attack_lookup(self.input_dto.input_path)
100
+
101
+ def execute(self, input_dto: validate) -> None:
102
+ self.input_dto = input_dto
103
+
81
104
 
82
- self.basic_builder = BasicBuilder()
83
- self.playbook_builder = PlaybookBuilder(self.input_dto.input_path)
84
- self.baseline_builder = BaselineBuilder()
85
- self.investigation_builder = InvestigationBuilder()
86
- self.story_builder = StoryBuilder()
87
- self.detection_builder = DetectionBuilder()
88
- self.ssa_detection_builder = SSADetectionBuilder()
89
- if self.input_dto.product == SecurityContentProduct.SPLUNK_APP or self.input_dto.product == SecurityContentProduct.API:
90
- self.createSecurityContent(SecurityContentType.deployments)
91
- self.createSecurityContent(SecurityContentType.lookups)
92
- self.createSecurityContent(SecurityContentType.macros)
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, type: SecurityContentType) -> None:
103
- if type == SecurityContentType.ssa_detections:
104
- files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, 'ssa_detections'))
105
- elif type == SecurityContentType.unit_tests:
106
- files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, 'tests'))
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
- files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.input_path, str(type.name)))
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 = type.name.upper()
127
- if type == SecurityContentType.lookups:
128
- self.constructLookup(self.basic_builder, file)
129
- lookup = self.basic_builder.getObject()
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 type == SecurityContentType.macros:
133
- self.constructMacro(self.basic_builder, file)
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 type == SecurityContentType.deployments:
138
- self.constructDeployment(self.basic_builder, file)
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 type == SecurityContentType.playbooks:
143
- self.constructPlaybook(self.playbook_builder, file)
144
- playbook = self.playbook_builder.getObject()
145
- self.output_dto.playbooks.append(playbook)
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 type == SecurityContentType.baselines:
148
- self.constructBaseline(self.baseline_builder, file)
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 type == SecurityContentType.investigations:
153
- self.constructInvestigation(self.investigation_builder, file)
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 type == SecurityContentType.stories:
158
- self.constructStory(self.story_builder, file)
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 type == SecurityContentType.detections:
163
- self.constructDetection(self.detection_builder, file)
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 type == SecurityContentType.ssa_detections:
168
- self.constructSSADetection(self.ssa_detection_builder, file)
169
- detection = self.ssa_detection_builder.getObject()
170
- if detection.status in [DetectionStatus.production.value, DetectionStatus.validation.value]:
171
- self.output_dto.ssa_detections.append(detection)
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: [{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.input_path.absolute())
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'{type.name.upper()} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True)
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]}\n{str(e_tuple[1])}" for e_tuple in validation_errors])
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
- def constructDetection(self, builder: DetectionBuilder, file_path: str) -> None:
193
- builder.reset()
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: DetectionBuilder, file_path: str) -> None:
220
+ def constructSSADetection(self, builder: SSADetectionBuilder, directorOutput:DirectorOutputDto, file_path: str) -> None:
234
221
  builder.reset()
235
- builder.setObject(file_path)
236
- builder.addMitreAttackEnrichment(self.attack_enrichment)
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
- def constructStory(self, builder: StoryBuilder, file_path: str) -> None:
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
- 'type': 'text',
10
- 'message': 'enter detection name',
11
- 'name': 'detection_name',
12
- 'default': 'Powershell Encoded Command',
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
- 'type': 'select',
34
- 'message': 'select a detection type',
35
- 'name': 'detection_type',
36
- 'choices': [
37
- 'TTP',
38
- 'Anomaly',
39
- 'Hunting',
40
- 'Baseline',
41
- 'Investigation',
42
- 'Correlation'
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
- 'default': 'TTP'
47
+ "default": "TTP",
45
48
  },
46
49
  {
47
50
  'type': 'checkbox',
@@ -89,16 +92,16 @@ class NewContentQuestions():
89
92
  ]
90
93
  },
91
94
  {
92
- 'type': 'text',
93
- 'message': 'enter search (spl)',
94
- 'name': 'detection_search',
95
- 'default': '| UPDATE_SPL'
95
+ "type": "text",
96
+ "message": "enter search (spl)",
97
+ "name": "detection_search",
98
+ "default": "| UPDATE_SPL",
96
99
  },
97
100
  {
98
- 'type': 'text',
99
- 'message': 'enter MITRE ATT&CK Technique IDs related to the detection, comma delimited for multiple',
100
- 'name': 'mitre_attack_ids',
101
- 'default': 'T1003.002'
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
- 'type': 'text',
126
- 'message': 'enter story name',
127
- 'name': 'story_name',
128
- 'default': 'Suspicious Powershell Behavior',
127
+ "type": "text",
128
+ "message": "enter story name",
129
+ "name": "story_name",
130
+ "default": "Suspicious Powershell Behavior",
129
131
  },
130
132
  {
131
- 'type': 'text',
132
- 'message': 'enter author name',
133
- 'name': 'story_author',
133
+ "type": "text",
134
+ "message": "enter author name",
135
+ "name": "story_author",
134
136
  },
135
137
  {
136
- 'type': 'checkbox',
137
- 'message': 'select a category',
138
- 'name': 'category',
139
- 'choices': [
140
- 'Adversary Tactics',
141
- 'Account Compromise',
142
- 'Unauthorized Software',
143
- 'Best Practices',
144
- 'Cloud Security',
145
- 'Command and Control',
146
- 'Lateral Movement',
147
- 'Ransomware',
148
- 'Privilege Escalation'
149
- ]
150
- },
151
- {
152
- 'type': 'select',
153
- 'message': 'select a use case',
154
- 'name': 'usecase',
155
- 'choices': [
156
- 'Advanced Threat Detection',
157
- 'Security Monitoring',
158
- 'Compliance',
159
- 'Insider Threat',
160
- 'Application Security',
161
- 'Other'
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
- detection.source = os.path.split(os.path.dirname(detection_path))[-1]
236
+
238
237
  return detection
239
238
 
240
239