1
1
import os
2
2
import sys
3
+ import pathlib
3
4
from typing import Union
4
5
from dataclasses import dataclass , field
5
6
from pydantic import ValidationError
20
21
from contentctl .objects .ssa_detection import SSADetection
21
22
from contentctl .objects .atomic import AtomicTest
22
23
from contentctl .objects .security_content_object import SecurityContentObject
24
+ from contentctl .objects .data_source import DataSource
25
+ from contentctl .objects .event_source import EventSource
23
26
24
27
from contentctl .enrichments .attack_enrichment import AttackEnrichment
25
28
from contentctl .enrichments .cve_enrichment import CveEnrichment
26
29
27
30
from contentctl .objects .config import validate
31
+ from contentctl .input .ssa_detection_builder import SSADetectionBuilder
32
+ from contentctl .objects .enums import SecurityContentType
33
+
34
+ from contentctl .objects .enums import DetectionStatus
35
+ from contentctl .helper .utils import Utils
36
+
37
+ from contentctl .input .ssa_detection_builder import SSADetectionBuilder
38
+ from contentctl .objects .enums import SecurityContentType
39
+
40
+ from contentctl .objects .enums import DetectionStatus
41
+ from contentctl .helper .utils import Utils
28
42
29
43
30
44
@dataclass
@@ -43,7 +57,8 @@ class DirectorOutputDto:
43
57
lookups : list [Lookup ]
44
58
deployments : list [Deployment ]
45
59
ssa_detections : list [SSADetection ]
46
-
60
+ data_sources : list [DataSource ]
61
+ event_sources : list [EventSource ]
47
62
name_to_content_map : dict [str , SecurityContentObject ] = field (default_factory = dict )
48
63
uuid_to_content_map : dict [UUID , SecurityContentObject ] = field (default_factory = dict )
49
64
@@ -92,66 +107,84 @@ def addContentToDictMappings(self, content: SecurityContentObject):
92
107
self .uuid_to_content_map [content .id ] = content
93
108
94
109
95
- from contentctl .input .ssa_detection_builder import SSADetectionBuilder
96
- from contentctl .objects .enums import SecurityContentType
97
-
98
- from contentctl .objects .enums import DetectionStatus
99
- from contentctl .helper .utils import Utils
100
-
101
-
102
110
class Director ():
103
111
input_dto : validate
104
112
output_dto : DirectorOutputDto
105
113
ssa_detection_builder : SSADetectionBuilder
106
-
107
-
108
114
109
115
def __init__ (self , output_dto : DirectorOutputDto ) -> None :
110
116
self .output_dto = output_dto
111
117
self .ssa_detection_builder = SSADetectionBuilder ()
112
-
118
+
113
119
def execute (self , input_dto : validate ) -> None :
114
120
self .input_dto = input_dto
115
-
116
-
117
121
self .createSecurityContent (SecurityContentType .deployments )
118
122
self .createSecurityContent (SecurityContentType .lookups )
119
123
self .createSecurityContent (SecurityContentType .macros )
120
124
self .createSecurityContent (SecurityContentType .stories )
121
125
self .createSecurityContent (SecurityContentType .baselines )
122
126
self .createSecurityContent (SecurityContentType .investigations )
127
+ self .createSecurityContent (SecurityContentType .event_sources )
128
+ self .createSecurityContent (SecurityContentType .data_sources )
123
129
self .createSecurityContent (SecurityContentType .playbooks )
124
130
self .createSecurityContent (SecurityContentType .detections )
125
-
126
-
127
131
self .createSecurityContent (SecurityContentType .ssa_detections )
128
-
129
132
130
133
def createSecurityContent (self , contentType : SecurityContentType ) -> None :
131
134
if contentType == SecurityContentType .ssa_detections :
132
- files = Utils .get_all_yml_files_from_directory (os .path .join (self .input_dto .path , 'ssa_detections' ))
133
- security_content_files = [f for f in files if f .name .startswith ('ssa___' )]
134
-
135
- elif contentType in [SecurityContentType .deployments ,
136
- SecurityContentType .lookups ,
137
- SecurityContentType .macros ,
138
- SecurityContentType .stories ,
139
- SecurityContentType .baselines ,
140
- SecurityContentType .investigations ,
141
- SecurityContentType .playbooks ,
142
- SecurityContentType .detections ]:
143
- files = Utils .get_all_yml_files_from_directory (os .path .join (self .input_dto .path , str (contentType .name )))
144
- security_content_files = [f for f in files if not f .name .startswith ('ssa___' )]
135
+ files = Utils .get_all_yml_files_from_directory (
136
+ os .path .join (self .input_dto .path , "ssa_detections" )
137
+ )
138
+ security_content_files = [f for f in files if f .name .startswith ("ssa___" )]
139
+
140
+ elif contentType == SecurityContentType .data_sources :
141
+ security_content_files = (
142
+ Utils .get_all_yml_files_from_directory_one_layer_deep (
143
+ os .path .join (self .input_dto .path , "data_sources" )
144
+ )
145
+ )
146
+
147
+ elif contentType == SecurityContentType .event_sources :
148
+ security_content_files = Utils .get_all_yml_files_from_directory (
149
+ os .path .join (self .input_dto .path , "data_sources" , "cloud" , "event_sources" )
150
+ )
151
+ security_content_files .extend (
152
+ Utils .get_all_yml_files_from_directory (
153
+ os .path .join (self .input_dto .path , "data_sources" , "endpoint" , "event_sources" )
154
+ )
155
+ )
156
+ security_content_files .extend (
157
+ Utils .get_all_yml_files_from_directory (
158
+ os .path .join (self .input_dto .path , "data_sources" , "network" , "event_sources" )
159
+ )
160
+ )
161
+
162
+ elif contentType in [
163
+ SecurityContentType .deployments ,
164
+ SecurityContentType .lookups ,
165
+ SecurityContentType .macros ,
166
+ SecurityContentType .stories ,
167
+ SecurityContentType .baselines ,
168
+ SecurityContentType .investigations ,
169
+ SecurityContentType .playbooks ,
170
+ SecurityContentType .detections ,
171
+ ]:
172
+ files = Utils .get_all_yml_files_from_directory (
173
+ os .path .join (self .input_dto .path , str (contentType .name ))
174
+ )
175
+ security_content_files = [
176
+ f for f in files if not f .name .startswith ("ssa___" )
177
+ ]
145
178
else :
146
- raise (Exception (f"Cannot createSecurityContent for unknown product." ))
179
+ raise (Exception (f"Cannot createSecurityContent for unknown product." ))
147
180
148
181
validation_errors = []
149
-
182
+
150
183
already_ran = False
151
184
progress_percent = 0
152
-
153
- for index ,file in enumerate (security_content_files ):
154
- progress_percent = ((index + 1 ) / len (security_content_files )) * 100
185
+
186
+ for index , file in enumerate (security_content_files ):
187
+ progress_percent = ((index + 1 ) / len (security_content_files )) * 100
155
188
try :
156
189
type_string = contentType .name .upper ()
157
190
modelDict = YmlReader .load_file (file )
@@ -167,7 +200,7 @@ def createSecurityContent(self, contentType: SecurityContentType) -> None:
167
200
elif contentType == SecurityContentType .deployments :
168
201
deployment = Deployment .model_validate (modelDict ,context = {"output_dto" :self .output_dto })
169
202
self .output_dto .addContentToDictMappings (deployment )
170
-
203
+
171
204
elif contentType == SecurityContentType .playbooks :
172
205
playbook = Playbook .model_validate (modelDict ,context = {"output_dto" :self .output_dto })
173
206
self .output_dto .addContentToDictMappings (playbook )
@@ -193,36 +226,67 @@ def createSecurityContent(self, contentType: SecurityContentType) -> None:
193
226
ssa_detection = self .ssa_detection_builder .getObject ()
194
227
if ssa_detection .status in [DetectionStatus .production .value , DetectionStatus .validation .value ]:
195
228
self .output_dto .addContentToDictMappings (ssa_detection )
229
+
230
+ elif contentType == SecurityContentType .data_sources :
231
+ data_source = DataSource .model_validate (
232
+ modelDict , context = {"output_dto" : self .output_dto }
233
+ )
234
+ self .output_dto .data_sources .append (data_source )
235
+
236
+ elif contentType == SecurityContentType .event_sources :
237
+ event_source = EventSource .model_validate (
238
+ modelDict , context = {"output_dto" : self .output_dto }
239
+ )
240
+ self .output_dto .event_sources .append (event_source )
196
241
197
242
else :
198
- raise Exception (f"Unsupported type: [{ contentType } ]" )
199
-
200
- if (sys .stdout .isatty () and sys .stdin .isatty () and sys .stderr .isatty ()) or not already_ran :
201
- already_ran = True
202
- print (f"\r { f'{ type_string } Progress' .rjust (23 )} : [{ progress_percent :3.0f} %]..." , end = "" , flush = True )
203
-
204
- except (ValidationError , ValueError ) as e :
205
- relative_path = file .absolute ().relative_to (self .input_dto .path .absolute ())
206
- validation_errors .append ((relative_path ,e ))
207
-
243
+ raise Exception (f"Unsupported type: [{ contentType } ]" )
244
+
245
+ if (
246
+ sys .stdout .isatty () and sys .stdin .isatty () and sys .stderr .isatty ()
247
+ ) or not already_ran :
248
+ already_ran = True
249
+ print (
250
+ f"\r { f'{ type_string } Progress' .rjust (23 )} : [{ progress_percent :3.0f} %]..." ,
251
+ end = "" ,
252
+ flush = True ,
253
+ )
208
254
209
- print (f"\r { f'{ contentType .name .upper ()} Progress' .rjust (23 )} : [{ progress_percent :3.0f} %]..." , end = "" , flush = True )
255
+ except (ValidationError , ValueError ) as e :
256
+ relative_path = file .absolute ().relative_to (
257
+ self .input_dto .path .absolute ()
258
+ )
259
+ validation_errors .append ((relative_path , e ))
260
+
261
+ print (
262
+ f"\r { f'{ contentType .name .upper ()} Progress' .rjust (23 )} : [{ progress_percent :3.0f} %]..." ,
263
+ end = "" ,
264
+ flush = True ,
265
+ )
210
266
print ("Done!" )
211
267
212
268
if len (validation_errors ) > 0 :
213
- errors_string = '\n \n ' .join ([f"File: { e_tuple [0 ]} \n Error: { str (e_tuple [1 ])} " for e_tuple in validation_errors ])
214
- #print(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
269
+ errors_string = "\n \n " .join (
270
+ [
271
+ f"File: { e_tuple [0 ]} \n Error: { str (e_tuple [1 ])} "
272
+ for e_tuple in validation_errors
273
+ ]
274
+ )
275
+ # print(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
215
276
# We quit after validation a single type/group of content because it can cause significant cascading errors in subsequent
216
277
# types of content (since they may import or otherwise use it)
217
- raise Exception (f"The following { len (validation_errors )} error(s) were found during validation:\n \n { errors_string } \n \n VALIDATION FAILED" )
218
-
219
-
220
-
221
-
278
+ raise Exception (
279
+ f"The following { len (validation_errors )} error(s) were found during validation:\n \n { errors_string } \n \n VALIDATION FAILED"
280
+ )
222
281
223
- def constructSSADetection (self , builder : SSADetectionBuilder , directorOutput :DirectorOutputDto , file_path : str ) -> None :
282
+ def constructSSADetection (
283
+ self ,
284
+ builder : SSADetectionBuilder ,
285
+ directorOutput : DirectorOutputDto ,
286
+ file_path : str ,
287
+ ) -> None :
224
288
builder .reset ()
225
- builder .setObject (file_path , self . output_dto )
289
+ builder .setObject (file_path )
226
290
builder .addMitreAttackEnrichmentNew (directorOutput .attack_enrichment )
227
291
builder .addKillChainPhase ()
228
292
builder .addCIS ()
0 commit comments