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/objects/enums.py
CHANGED
|
@@ -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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
UEBA =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
16
|
+
test_type: TestType = Field(TestType.INTEGRATION)
|
|
17
17
|
|
|
18
18
|
# The test result
|
|
19
19
|
result: Union[None, IntegrationTestResult] = None
|
|
@@ -1,67 +1,76 @@
|
|
|
1
|
-
import
|
|
2
|
-
import uuid
|
|
3
|
-
import string
|
|
1
|
+
from __future__ import annotations
|
|
4
2
|
import re
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
@
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|