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,33 +1,49 @@
1
+ from __future__ import annotations
2
+ from typing import List
1
3
  import enum
2
4
 
3
5
 
4
- class AnalyticsType(enum.Enum):
6
+ class AnalyticsType(str, enum.Enum):
5
7
  TTP = "TTP"
6
8
  Anomaly = "Anomaly"
7
9
  Hunting = "Hunting"
8
10
  Correlation = "Correlation"
9
11
 
12
+ class DeploymentType(str, enum.Enum):
13
+ TTP = "TTP"
14
+ Anomaly = "Anomaly"
15
+ Hunting = "Hunting"
16
+ Correlation = "Correlation"
17
+ Baseline = "Baseline"
18
+ Embedded = "Embedded"
19
+
10
20
 
11
- class DataModel(enum.Enum):
12
- Endpoint = 1
13
- Network_Traffic = 2
14
- Authentication = 3
15
- Change = 4
16
- Change_Analysis = 5
17
- Email = 6
18
- Network_Resolution = 7
19
- Network_Sessions = 8
20
- UEBA = 9
21
- Updates = 10
22
- Vulnerabilities = 11
23
- Web = 12
24
- Endpoint_Processes = 13
25
- Endpoint_Filesystem = 14
26
- Endpoint_Registry = 15
27
- Risk = 16
28
- Splunk_Audit = 17
21
+ class DataModel(str,enum.Enum):
22
+ ENDPOINT = "Endpoint"
23
+ NETWORK_TRAFFIC = "Network_Traffic"
24
+ AUTHENTICATION = "Authentication"
25
+ CHANGE = "Change"
26
+ CHANGE_ANALYSIS = "Change_Analysis"
27
+ EMAIL = "Email"
28
+ NETWORK_RESOLUTION = "Network_Resolution"
29
+ NETWORK_SESSIONS = "Network_Sessions"
30
+ UEBA = "UEBA"
31
+ UPDATES = "Updates"
32
+ VULNERABILITIES = "Vulnerabilities"
33
+ WEB = "Web"
34
+ #Should the following more specific DMs be added?
35
+ #Or should they just fall under endpoint?
36
+ #ENDPOINT_PROCESSES = "Endpoint_Processes"
37
+ #ENDPOINT_FILESYSTEM = "Endpoint_Filesystem"
38
+ #ENDPOINT_REGISTRY = "Endpoint_Registry"
39
+ RISK = "Risk"
40
+ SPLUNK_AUDIT = "Splunk_Audit"
29
41
 
30
42
 
43
+ class PlaybookType(str, enum.Enum):
44
+ INVESTIGATION = "Investigation"
45
+ RESPONSE = "Response"
46
+
31
47
  class SecurityContentType(enum.Enum):
32
48
  detections = 1
33
49
  baselines = 2
@@ -57,12 +73,21 @@ class SecurityContentProduct(enum.Enum):
57
73
  CUSTOM = 4
58
74
 
59
75
 
60
- class SigmaConverterTarget(enum.Enum):
61
- CIM = 1
62
- RAW = 2
63
- OCSF = 3
64
- ALL = 4
76
+ class SecurityContentProductName(str, enum.Enum):
77
+ SPLUNK_ENTERPRISE = "Splunk Enterprise"
78
+ SPLUNK_ENTERPRISE_SECURITY = "Splunk Enterprise Security"
79
+ SPLUNK_CLOUD = "Splunk Cloud"
80
+ SPLUNK_SECURITY_ANALYTICS_FOR_AWS = "Splunk Security Analytics for AWS"
81
+ SPLUNK_BEHAVIORAL_ANALYTICS = "Splunk Behavioral Analytics"
65
82
 
83
+ class SecurityContentInvestigationProductName(str, enum.Enum):
84
+ SPLUNK_ENTERPRISE = "Splunk Enterprise"
85
+ SPLUNK_ENTERPRISE_SECURITY = "Splunk Enterprise Security"
86
+ SPLUNK_CLOUD = "Splunk Cloud"
87
+ SPLUNK_SECURITY_ANALYTICS_FOR_AWS = "Splunk Security Analytics for AWS"
88
+ SPLUNK_BEHAVIORAL_ANALYTICS = "Splunk Behavioral Analytics"
89
+ SPLUNK_PHANTOM = "Splunk Phantom"
90
+
66
91
 
67
92
  class DetectionStatus(enum.Enum):
68
93
  production = "production"
@@ -71,6 +96,13 @@ class DetectionStatus(enum.Enum):
71
96
  validation = "validation"
72
97
 
73
98
 
99
+ class DetectionStatusSSA(enum.Enum):
100
+ production = "production"
101
+ deprecated = "deprecated"
102
+ experimental = "experimental"
103
+ validation = "validation"
104
+
105
+
74
106
  class LogLevel(enum.Enum):
75
107
  NONE = "NONE"
76
108
  ERROR = "ERROR"
@@ -129,3 +161,259 @@ class InstanceState(str, enum.Enum):
129
161
  error = "error"
130
162
  stopping = "stopping"
131
163
  stopped = "stopped"
164
+
165
+
166
+ class SigmaConverterTarget(enum.Enum):
167
+ CIM = 1
168
+ RAW = 2
169
+ OCSF = 3
170
+ ALL = 4
171
+
172
+ # It's unclear why we use a mix of constants and enums. The following list was taken from:
173
+ # contentctl/contentctl/helper/constants.py.
174
+ # We convect it to an enum here
175
+ # SES_KILL_CHAIN_MAPPINGS = {
176
+ # "Unknown": 0,
177
+ # "Reconnaissance": 1,
178
+ # "Weaponization": 2,
179
+ # "Delivery": 3,
180
+ # "Exploitation": 4,
181
+ # "Installation": 5,
182
+ # "Command And Control": 6,
183
+ # "Actions on Objectives": 7
184
+ # }
185
+ class KillChainPhase(str, enum.Enum):
186
+ UNKNOWN ="Unknown"
187
+ RECONNAISSANCE = "Reconnaissance"
188
+ WEAPONIZATION = "Weaponization"
189
+ DELIVERY = "Delivery"
190
+ EXPLOITAITON = "Exploitation"
191
+ INSTALLATION = "Installation"
192
+ COMMAND_AND_CONTROL = "Command and Control"
193
+ ACTIONS_ON_OBJECTIVES = "Actions on Objectives"
194
+
195
+
196
+ class DataSource(str,enum.Enum):
197
+ OSQUERY_ES_PROCESS_EVENTS = "OSQuery ES Process Events"
198
+ POWERSHELL_4104 = "Powershell 4104"
199
+ SYSMON_EVENT_ID_1 = "Sysmon Event ID 1"
200
+ SYSMON_EVENT_ID_10 = "Sysmon Event ID 10"
201
+ SYSMON_EVENT_ID_11 = "Sysmon Event ID 11"
202
+ SYSMON_EVENT_ID_13 = "Sysmon Event ID 13"
203
+ SYSMON_EVENT_ID_15 = "Sysmon Event ID 15"
204
+ SYSMON_EVENT_ID_20 = "Sysmon Event ID 20"
205
+ SYSMON_EVENT_ID_21 = "Sysmon Event ID 21"
206
+ SYSMON_EVENT_ID_22 = "Sysmon Event ID 22"
207
+ SYSMON_EVENT_ID_23 = "Sysmon Event ID 23"
208
+ SYSMON_EVENT_ID_3 = "Sysmon Event ID 3"
209
+ SYSMON_EVENT_ID_5 = "Sysmon Event ID 5"
210
+ SYSMON_EVENT_ID_6 = "Sysmon Event ID 6"
211
+ SYSMON_EVENT_ID_7 = "Sysmon Event ID 7"
212
+ SYSMON_EVENT_ID_8 = "Sysmon Event ID 8"
213
+ SYSMON_EVENT_ID_9 = "Sysmon Event ID 9"
214
+ WINDOWS_SECURITY_4624 = "Windows Security 4624"
215
+ WINDOWS_SECURITY_4625 = "Windows Security 4625"
216
+ WINDOWS_SECURITY_4648 = "Windows Security 4648"
217
+ WINDOWS_SECURITY_4663 = "Windows Security 4663"
218
+ WINDOWS_SECURITY_4688 = "Windows Security 4688"
219
+ WINDOWS_SECURITY_4698 = "Windows Security 4698"
220
+ WINDOWS_SECURITY_4703 = "Windows Security 4703"
221
+ WINDOWS_SECURITY_4720 = "Windows Security 4720"
222
+ WINDOWS_SECURITY_4732 = "Windows Security 4732"
223
+ WINDOWS_SECURITY_4738 = "Windows Security 4738"
224
+ WINDOWS_SECURITY_4741 = "Windows Security 4741"
225
+ WINDOWS_SECURITY_4742 = "Windows Security 4742"
226
+ WINDOWS_SECURITY_4768 = "Windows Security 4768"
227
+ WINDOWS_SECURITY_4769 = "Windows Security 4769"
228
+ WINDOWS_SECURITY_4771 = "Windows Security 4771"
229
+ WINDOWS_SECURITY_4776 = "Windows Security 4776"
230
+ WINDOWS_SECURITY_4781 = "Windows Security 4781"
231
+ WINDOWS_SECURITY_4798 = "Windows Security 4798"
232
+ WINDOWS_SECURITY_5136 = "Windows Security 5136"
233
+ WINDOWS_SECURITY_5145 = "Windows Security 5145"
234
+ WINDOWS_SYSTEM_7045 = "Windows System 7045"
235
+
236
+ class ProvidingTechnology(str, enum.Enum):
237
+ AMAZON_SECURITY_LAKE = "Amazon Security Lake"
238
+ AMAZON_WEB_SERVICES_CLOUDTRAIL = "Amazon Web Services - Cloudtrail"
239
+ AZURE_AD = "Azure AD"
240
+ CARBON_BLACK_RESPONSE = "Carbon Black Response"
241
+ CROWDSTRIKE_FALCON = "CrowdStrike Falcon"
242
+ ENTRA_ID = "Entra ID"
243
+ GOOGLE_CLOUD_PLATFORM = "Google Cloud Platform"
244
+ GOOGLE_WORKSPACE = "Google Workspace"
245
+ KUBERNETES = "Kubernetes"
246
+ MICROSOFT_DEFENDER = "Microsoft Defender"
247
+ MICROSOFT_OFFICE_365 = "Microsoft Office 365"
248
+ MICROSOFT_SYSMON = "Microsoft Sysmon"
249
+ MICROSOFT_WINDOWS = "Microsoft Windows"
250
+ OKTA = "Okta"
251
+ PING_ID = "Ping ID"
252
+ SPLUNK_INTERNAL_LOGS = "Splunk Internal Logs"
253
+ SYMANTEC_ENDPOINT_PROTECTION = "Symantec Endpoint Protection"
254
+ ZEEK = "Zeek"
255
+
256
+ @staticmethod
257
+ def getProvidingTechFromSearch(search_string:str)->List[ProvidingTechnology]:
258
+ """_summary_
259
+
260
+ Args:
261
+ search_string (str): The search string to extract the providing technologies from.
262
+ The providing_technologies_mapping provides keywords, macros, etc that can be updated
263
+ with new mappings. If that substring appears in the search, then its list of providing
264
+ technologies is added.
265
+
266
+ Returns:
267
+ List[ProvidingTechnology]: List of providing technologies (with no duplicates because
268
+ it is derived from a set) calculated from the search string.
269
+ """
270
+ matched_technologies:set[ProvidingTechnology] = set()
271
+ #As there are many different sources that use google logs, we define the set once
272
+ google_logs = set([ProvidingTechnology.GOOGLE_WORKSPACE, ProvidingTechnology.GOOGLE_CLOUD_PLATFORM])
273
+ providing_technologies_mapping = {
274
+ '`amazon_security_lake`': set([ProvidingTechnology.AMAZON_SECURITY_LAKE]),
275
+ 'audit_searches': set([ProvidingTechnology.SPLUNK_INTERNAL_LOGS]),
276
+ '`azure_monitor_aad`': set([ProvidingTechnology.AZURE_AD, ProvidingTechnology.ENTRA_ID]),
277
+ '`cloudtrail`': set([ProvidingTechnology.AMAZON_WEB_SERVICES_CLOUDTRAIL]),
278
+ #Endpoint is NOT a Macro (and this is intentional since it is to capture Endpoint Datamodel usage)
279
+ 'Endpoint': set([ProvidingTechnology.MICROSOFT_SYSMON,
280
+ ProvidingTechnology.MICROSOFT_WINDOWS,
281
+ ProvidingTechnology.CARBON_BLACK_RESPONSE,
282
+ ProvidingTechnology.CROWDSTRIKE_FALCON,
283
+ ProvidingTechnology.SYMANTEC_ENDPOINT_PROTECTION]),
284
+ '`google_': google_logs,
285
+ '`gsuite': google_logs,
286
+ '`gws_': google_logs,
287
+ '`kube': set([ProvidingTechnology.KUBERNETES]),
288
+ '`ms_defender`': set([ProvidingTechnology.MICROSOFT_DEFENDER]),
289
+ '`o365_': set([ProvidingTechnology.MICROSOFT_OFFICE_365]),
290
+ '`okta': set([ProvidingTechnology.OKTA]),
291
+ '`pingid`': set([ProvidingTechnology.PING_ID]),
292
+ '`powershell`': set(set([ProvidingTechnology.MICROSOFT_WINDOWS])),
293
+ '`splunkd_': set([ProvidingTechnology.SPLUNK_INTERNAL_LOGS]),
294
+ '`sysmon`': set([ProvidingTechnology.MICROSOFT_SYSMON]),
295
+ '`wineventlog_security`': set([ProvidingTechnology.MICROSOFT_WINDOWS]),
296
+ '`zeek_': set([ProvidingTechnology.ZEEK]),
297
+ }
298
+ for key in providing_technologies_mapping:
299
+ if key in search_string:
300
+ matched_technologies.update(providing_technologies_mapping[key])
301
+ return sorted(list(matched_technologies))
302
+
303
+
304
+ class Cis18Value(str,enum.Enum):
305
+ CIS_0 = "CIS 0"
306
+ CIS_1 = "CIS 1"
307
+ CIS_2 = "CIS 2"
308
+ CIS_3 = "CIS 3"
309
+ CIS_4 = "CIS 4"
310
+ CIS_5 = "CIS 5"
311
+ CIS_6 = "CIS 6"
312
+ CIS_7 = "CIS 7"
313
+ CIS_8 = "CIS 8"
314
+ CIS_9 = "CIS 9"
315
+ CIS_10 = "CIS 10"
316
+ CIS_11 = "CIS 11"
317
+ CIS_12 = "CIS 12"
318
+ CIS_13 = "CIS 13"
319
+ CIS_14 = "CIS 14"
320
+ CIS_15 = "CIS 15"
321
+ CIS_16 = "CIS 16"
322
+ CIS_17 = "CIS 17"
323
+ CIS_18 = "CIS 18"
324
+
325
+ class SecurityDomain(str, enum.Enum):
326
+ ENDPOINT = "endpoint"
327
+ NETWORK = "network"
328
+ THREAT = "threat"
329
+ IDENTITY = "identity"
330
+ ACCESS = "access"
331
+ AUDIT = "audit"
332
+ CLOUD = "cloud"
333
+
334
+ class AssetType(str, enum.Enum):
335
+ AWS_ACCOUNT = "AWS Account"
336
+ AWS_EKS_KUBERNETES_CLUSTER = "AWS EKS Kubernetes cluster"
337
+ AWS_FEDERATED_ACCOUNT = "AWS Federated Account"
338
+ AWS_INSTANCE = "AWS Instance"
339
+ ACCOUNT = "Account"
340
+ AMAZON_EKS_KUBERNETES_CLUSTER = "Amazon EKS Kubernetes cluster"
341
+ AMAZON_EKS_KUBERNETES_CLUSTER_POD = "Amazon EKS Kubernetes cluster Pod"
342
+ AMAZON_ELASTIC_CONTAINER_REGISTRY = "Amazon Elastic Container Registry"
343
+ #AZURE = "Azure"
344
+ #AZURE_AD = "Azure AD"
345
+ #AZURE_AD_TENANT = "Azure AD Tenant"
346
+ AZURE_TENANT = "Azure Tenant"
347
+ AZURE_AKS_KUBERNETES_CLUSTER = "Azure AKS Kubernetes cluster"
348
+ AZURE_ACTIVE_DIRECTORY = "Azure Active Directory"
349
+ CIRCLECI = "CircleCI"
350
+ CLOUD_COMPUTE_INSTANCE = "Cloud Compute Instance"
351
+ CLOUD_INSTANCE = "Cloud Instance"
352
+ DNS_SERVERS = "DNS Servers"
353
+ DATABASE_SERVER = "Database Server"
354
+ DOMAIN_SERVER = "Domain Server"
355
+ EC2_SNAPSHOT = "EC2 Snapshot"
356
+ ENDPOINT = "Endpoint"
357
+ GCP = "GCP"
358
+ GCP_ACCOUNT = "GCP Account"
359
+ GCP_GKE_EKS_KUBERNETES_CLUSTER = "GCP GKE EKS Kubernetes cluster"
360
+ GCP_GKE_KUBERNETES_CLUSTER = "GCP GKE Kubernetes cluster"
361
+ GCP_KUBERNETES_CLUSTER = "GCP Kubernetes cluster"
362
+ GCP_STORAGE_BUCKET = "GCP Storage Bucket"
363
+ GDRIVE = "GDrive"
364
+ GSUITE = "GSuite"
365
+ GITHUB = "GitHub"
366
+ GOOGLE_CLOUD_PLATFORM_TENANT = "Google Cloud Platform tenant"
367
+ IDENTITY = "Identity"
368
+ INFRASTRUCTURE = "Infrastructure"
369
+ INSTANCE = "Instance"
370
+ KUBERNETES = "Kubernetes"
371
+ NETWORK = "Network"
372
+ #OFFICE_365 = "Office 365"
373
+ #OFFICE_365_Tenant = "Office 365 Tenant"
374
+ O365_TENANT = "O365 Tenant"
375
+ OKTA_TENANT = "Okta Tenant"
376
+ PROXY = "Proxy"
377
+ S3_BUCKET = "S3 Bucket"
378
+ SPLUNK_SERVER = "Splunk Server"
379
+ VPN_Appliance = "VPN Appliance"
380
+ WEB_SERVER = "Web Server"
381
+ WEB_PROXY = "Web Proxy"
382
+ WEB_APPLICATION = "Web Application"
383
+ WINDOWS = "Windows"
384
+
385
+ class NistCategory(str, enum.Enum):
386
+ ID_AM = "ID.AM"
387
+ ID_BE = "ID.BE"
388
+ ID_GV = "ID.GV"
389
+ ID_RA = "ID.RA"
390
+ ID_RM = "ID.RM"
391
+ PR_AC = "PR.AC"
392
+ PR_AT = "PR.AT"
393
+ PR_DS = "PR.DS"
394
+ PR_IP = "PR.IP"
395
+ PR_MA = "PR.MA"
396
+ PR_PT = "PR.PT"
397
+ DE_AE = "DE.AE"
398
+ DE_CM = "DE.CM"
399
+ DE_DP = "DE.DP"
400
+ RS_RP = "RS.RP"
401
+ RS_CO = "RS.CO"
402
+ RS_AN = "RS.AN"
403
+ RS_MI = "RS.MI"
404
+ RS_IM = "RS.IM"
405
+ RC_RP = "RC.RP"
406
+ RC_IM = "RC.IM"
407
+ RC_CO = "RC.CO"
408
+
409
+ class RiskLevel(str,enum.Enum):
410
+ INFO = "Info"
411
+ LOW = "Low"
412
+ MEDIUM = "Medium"
413
+ HIGH = "High"
414
+ CRITICAL = "Critical"
415
+
416
+ class RiskSeverity(str,enum.Enum):
417
+ LOW = "low"
418
+ MEDIUM = "medium"
419
+ HIGH = "high"
@@ -13,7 +13,7 @@ class IntegrationTest(BaseTest):
13
13
  An integration test for a detection against ES
14
14
  """
15
15
  # The test type (integration)
16
- test_type: TestType = Field(TestType.INTEGRATION, const=True)
16
+ test_type: TestType = Field(TestType.INTEGRATION)
17
17
 
18
18
  # The test result
19
19
  result: Union[None, IntegrationTestResult] = None
@@ -1,6 +1,4 @@
1
1
  from typing import Optional
2
-
3
- from contentctl.objects.test_config import Infrastructure
4
2
  from contentctl.objects.base_test_result import BaseTestResult
5
3
 
6
4
 
@@ -1,67 +1,76 @@
1
- import enum
2
- import uuid
3
- import string
1
+ from __future__ import annotations
4
2
  import re
5
- import requests
6
-
7
- from pydantic import BaseModel, validator, ValidationError
8
- from dataclasses import dataclass
9
- from datetime import datetime
10
-
3
+ from typing import TYPE_CHECKING, Optional, List, Any
4
+ from pydantic import field_validator, computed_field, Field, ValidationInfo, ConfigDict,model_serializer
5
+ if TYPE_CHECKING:
6
+ from contentctl.input.director import DirectorOutputDto
11
7
  from contentctl.objects.security_content_object import SecurityContentObject
12
- from contentctl.objects.enums import AnalyticsType
13
8
  from contentctl.objects.enums import DataModel
14
- from contentctl.objects.enums import SecurityContentType
15
9
  from contentctl.objects.investigation_tags import InvestigationTags
16
- from contentctl.helper.link_validator import LinkValidator
17
10
 
18
11
 
19
12
  class Investigation(SecurityContentObject):
20
- # investigation spec
21
- #contentType: SecurityContentType = SecurityContentType.investigations
22
- #name: str
23
- #id: str
24
- #version: int
25
- #date: str
26
- #author: str
27
- type: str
28
- datamodel: list
29
- #description: str
30
- search: str
31
- how_to_implement: str
32
- known_false_positives: str
33
- check_references: bool = False #Validation is done in order, this field must be defined first
34
- references: list
35
- inputs: list = None
13
+ model_config = ConfigDict(use_enum_values=True,validate_default=False)
14
+ type: str = Field(...,pattern="^Investigation$")
15
+ datamodel: list[DataModel] = Field(...)
16
+
17
+ search: str = Field(...)
18
+ how_to_implement: str = Field(...)
19
+ known_false_positives: str = Field(...)
20
+
21
+
36
22
  tags: InvestigationTags
37
23
 
38
24
  # enrichment
39
- lowercase_name: str = None
25
+ @computed_field
26
+ @property
27
+ def inputs(self)->List[str]:
28
+ #Parse out and return all inputs from the searchj
29
+ inputs = []
30
+ pattern = r"\$([^\s.]*)\$"
40
31
 
41
- # check_fields=False because we want to override the
42
- # name validator in SecurityContentObject
43
- # (since we allow longer than the default length)
44
- @validator('name',check_fields=False)
45
- def name_max_length(cls, v):
46
- if len(v) > 75:
47
- raise ValueError('name is longer then 75 chars: ' + v)
48
- return v
32
+ for input in re.findall(pattern, self.search):
33
+ inputs.append(input)
49
34
 
50
- @validator('datamodel')
51
- def datamodel_valid(cls, v, values):
52
- for datamodel in v:
53
- if datamodel not in [el.name for el in DataModel]:
54
- raise ValueError('not valid data model: ' + values["name"])
55
- return v
35
+ return inputs
56
36
 
57
- @validator('how_to_implement')
58
- def encode_error(cls, v, values, field):
59
- return SecurityContentObject.free_text_field_valid(cls,v,values,field)
37
+ @computed_field
38
+ @property
39
+ def lowercase_name(self)->str:
40
+ return self.name.replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower().replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower()
41
+
42
+
43
+ @model_serializer
44
+ def serialize_model(self):
45
+ #Call serializer for parent
46
+ super_fields = super().serialize_model()
47
+
48
+ #All fields custom to this model
49
+ model= {
50
+ "type": self.type,
51
+ "datamodel": self.datamodel,
52
+ "search": self.search,
53
+ "how_to_implement": self.how_to_implement,
54
+ "known_false_positives": self.known_false_positives,
55
+ "inputs": self.inputs,
56
+ "tags": self.tags.model_dump(),
57
+ "lowercase_name":self.lowercase_name
58
+ }
59
+
60
+ #Combine fields from this model with fields from parent
61
+ super_fields.update(model)
62
+
63
+ #return the model
64
+ return super_fields
65
+
66
+
67
+ def model_post_init(self, ctx:dict[str,Any]):
68
+ # director: Optional[DirectorOutputDto] = ctx.get("output_dto",None)
69
+ # if not isinstance(director,DirectorOutputDto):
70
+ # raise ValueError("DirectorOutputDto was not passed in context of Detection model_post_init")
71
+ director: Optional[DirectorOutputDto] = ctx.get("output_dto",None)
72
+ for story in self.tags.analytic_story:
73
+ story.investigations.append(self)
60
74
 
61
- @validator('references')
62
- def references_check(cls, v, values):
63
- return LinkValidator.SecurityContentObject_validate_references(v, values)
64
- @validator('search')
65
- def search_validate(cls, v, values):
66
- # write search validator
67
- return v
75
+
76
+
@@ -1,9 +1,33 @@
1
+ from __future__ import annotations
2
+ from pydantic import BaseModel, Field, field_validator, ValidationInfo, model_serializer
3
+ from contentctl.objects.story import Story
4
+ from contentctl.objects.enums import SecurityContentInvestigationProductName, SecurityDomain
1
5
 
2
- from pydantic import BaseModel, validator, ValidationError
6
+ class InvestigationTags(BaseModel):
7
+ analytic_story: list[Story] = Field([],min_length=1)
8
+ product: list[SecurityContentInvestigationProductName] = Field(...,min_length=1)
9
+ required_fields: list[str] = Field(min_length=1)
10
+ security_domain: SecurityDomain = Field(...)
3
11
 
4
12
 
5
- class InvestigationTags(BaseModel):
6
- analytic_story: list
7
- product: list
8
- required_fields: list
9
- security_domain: str
13
+ @field_validator('analytic_story',mode="before")
14
+ @classmethod
15
+ def mapStoryNamesToStoryObjects(cls, v:list[str], info:ValidationInfo)->list[Story]:
16
+ return Story.mapNamesToSecurityContentObjects(v, info.context.get("output_dto",None))
17
+
18
+
19
+ @model_serializer
20
+ def serialize_model(self):
21
+ #All fields custom to this model
22
+ model= {
23
+ "analytic_story": [story.name for story in self.analytic_story],
24
+ "product": self.product,
25
+ "required_fields": self.required_fields,
26
+ "security_domain": self.security_domain,
27
+ }
28
+
29
+ #Combine fields from this model with fields from parent
30
+
31
+
32
+ #return the model
33
+ return model