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/story.py
CHANGED
|
@@ -1,49 +1,147 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING,List
|
|
3
|
+
from contentctl.objects.story_tags import StoryTags
|
|
4
|
+
from pydantic import Field, model_serializer,computed_field, model_validator
|
|
5
|
+
import re
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from contentctl.objects.detection import Detection
|
|
8
|
+
from contentctl.objects.investigation import Investigation
|
|
9
|
+
from contentctl.objects.baseline import Baseline
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
#from contentctl.objects.investigation import Investigation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
12
22
|
class Story(SecurityContentObject):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#id: str
|
|
16
|
-
#version: int
|
|
17
|
-
#date: str
|
|
18
|
-
#author: str
|
|
19
|
-
#description: str
|
|
20
|
-
#contentType: SecurityContentType = SecurityContentType.stories
|
|
21
|
-
narrative: str
|
|
22
|
-
check_references: bool = False #Validation is done in order, this field must be defined first
|
|
23
|
-
references: list
|
|
24
|
-
tags: StoryTags
|
|
23
|
+
narrative: str = Field(...)
|
|
24
|
+
tags: StoryTags = Field(...)
|
|
25
25
|
|
|
26
26
|
# enrichments
|
|
27
|
-
detection_names:
|
|
28
|
-
investigation_names:
|
|
29
|
-
baseline_names:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
27
|
+
#detection_names: List[str] = []
|
|
28
|
+
#investigation_names: List[str] = []
|
|
29
|
+
#baseline_names: List[str] = []
|
|
30
|
+
|
|
31
|
+
# These are updated when detection and investigation objects are created.
|
|
32
|
+
# Specifically in the model_post_init functions
|
|
33
|
+
detections:List[Detection] = []
|
|
34
|
+
investigations: List[Investigation] = []
|
|
35
|
+
baselines: List[Baseline] = []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def storyAndInvestigationNamesWithApp(self, app_name:str)->List[str]:
|
|
39
|
+
return [f"{app_name} - {name} - Rule" for name in self.detection_names] + \
|
|
40
|
+
[f"{app_name} - {name} - Response Task" for name in self.investigation_names]
|
|
41
|
+
|
|
42
|
+
@model_serializer
|
|
43
|
+
def serialize_model(self):
|
|
44
|
+
#Call serializer for parent
|
|
45
|
+
super_fields = super().serialize_model()
|
|
46
|
+
|
|
47
|
+
#All fields custom to this model
|
|
48
|
+
model= {
|
|
49
|
+
"narrative": self.narrative,
|
|
50
|
+
"tags": self.tags.model_dump(),
|
|
51
|
+
"detection_names": self.detection_names,
|
|
52
|
+
"investigation_names": self.investigation_names,
|
|
53
|
+
"baseline_names": self.baseline_names,
|
|
54
|
+
"author_company": self.author_company,
|
|
55
|
+
"author_name":self.author_name
|
|
56
|
+
}
|
|
57
|
+
detections = []
|
|
58
|
+
for detection in self.detections:
|
|
59
|
+
new_detection = {
|
|
60
|
+
"name":detection.name,
|
|
61
|
+
"source":detection.source,
|
|
62
|
+
"type":detection.type
|
|
63
|
+
}
|
|
64
|
+
if self.tags.mitre_attack_enrichments is not None:
|
|
65
|
+
new_detection['tags'] = {"mitre_attack_enrichments": [{"mitre_attack_technique": enrichment.mitre_attack_technique} for enrichment in detection.tags.mitre_attack_enrichments]}
|
|
66
|
+
detections.append(new_detection)
|
|
67
|
+
|
|
68
|
+
model['detections'] = detections
|
|
69
|
+
#Combine fields from this model with fields from parent
|
|
70
|
+
super_fields.update(model)
|
|
71
|
+
|
|
72
|
+
#return the model
|
|
73
|
+
return super_fields
|
|
74
|
+
|
|
75
|
+
@model_validator(mode="after")
|
|
76
|
+
def setTagsFields(self):
|
|
77
|
+
|
|
78
|
+
enrichments = []
|
|
79
|
+
for detection in self.detections:
|
|
80
|
+
enrichments.extend(detection.tags.mitre_attack_enrichments)
|
|
81
|
+
self.tags.mitre_attack_enrichments = list(set(enrichments))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
tactics = []
|
|
85
|
+
for enrichment in self.tags.mitre_attack_enrichments:
|
|
86
|
+
tactics.extend(enrichment.mitre_attack_tactics)
|
|
87
|
+
self.tags.mitre_attack_tactics = set(tactics)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
datamodels = []
|
|
92
|
+
for detection in self.detections:
|
|
93
|
+
datamodels.extend(detection.datamodel)
|
|
94
|
+
self.tags.datamodels = set(datamodels)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
kill_chain_phases = []
|
|
99
|
+
for detection in self.detections:
|
|
100
|
+
kill_chain_phases.extend(detection.tags.kill_chain_phases)
|
|
101
|
+
self.tags.kill_chain_phases = set(kill_chain_phases)
|
|
102
|
+
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@computed_field
|
|
107
|
+
@property
|
|
108
|
+
def author_name(self)->str:
|
|
109
|
+
match_author = re.search(r'^([^,]+)', self.author)
|
|
110
|
+
if match_author is None:
|
|
111
|
+
return 'no'
|
|
112
|
+
else:
|
|
113
|
+
return match_author.group(1)
|
|
114
|
+
|
|
115
|
+
@computed_field
|
|
116
|
+
@property
|
|
117
|
+
def author_company(self)->str:
|
|
118
|
+
match_company = re.search(r',\s?(.*)$', self.author)
|
|
119
|
+
if match_company is None:
|
|
120
|
+
return 'no'
|
|
121
|
+
else:
|
|
122
|
+
return match_company.group(1)
|
|
123
|
+
|
|
124
|
+
@computed_field
|
|
125
|
+
@property
|
|
126
|
+
def author_email(self)->str:
|
|
127
|
+
return "-"
|
|
128
|
+
|
|
129
|
+
@computed_field
|
|
130
|
+
@property
|
|
131
|
+
def detection_names(self)->List[str]:
|
|
132
|
+
return [detection.name for detection in self.detections]
|
|
133
|
+
|
|
134
|
+
@computed_field
|
|
135
|
+
@property
|
|
136
|
+
def investigation_names(self)->List[str]:
|
|
137
|
+
return [investigation.name for investigation in self.investigations]
|
|
138
|
+
|
|
139
|
+
@computed_field
|
|
140
|
+
@property
|
|
141
|
+
def baseline_names(self)->List[str]:
|
|
142
|
+
return [baseline.name for baseline in self.baselines]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
contentctl/objects/story_tags.py
CHANGED
|
@@ -1,38 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import BaseModel, Field, model_serializer, ConfigDict
|
|
3
|
+
from typing import List,Set,Optional, Annotated
|
|
1
4
|
|
|
5
|
+
from enum import Enum
|
|
2
6
|
|
|
3
|
-
from pydantic import BaseModel, validator, ValidationError
|
|
4
7
|
from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
|
|
5
|
-
from contentctl.objects.enums import StoryCategory
|
|
8
|
+
from contentctl.objects.enums import StoryCategory, DataModel, KillChainPhase, SecurityContentProductName
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StoryUseCase(str,Enum):
|
|
12
|
+
FRAUD_DETECTION = "Fraud Detection"
|
|
13
|
+
COMPLIANCE = "Compliance"
|
|
14
|
+
APPLICATION_SECURITY = "Application Security"
|
|
15
|
+
SECURITY_MONITORING = "Security Monitoring"
|
|
16
|
+
ADVANCED_THREAD_DETECTION = "Advanced Threat Detection"
|
|
6
17
|
|
|
7
18
|
class StoryTags(BaseModel):
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
19
|
+
model_config = ConfigDict(extra='forbid', use_enum_values=True)
|
|
20
|
+
category: List[StoryCategory] = Field(...,min_length=1)
|
|
21
|
+
product: List[SecurityContentProductName] = Field(...,min_length=1)
|
|
22
|
+
usecase: StoryUseCase = Field(...)
|
|
23
|
+
|
|
24
|
+
# enrichment
|
|
25
|
+
mitre_attack_enrichments: Optional[List[MitreAttackEnrichment]] = None
|
|
26
|
+
mitre_attack_tactics: Optional[Set[Annotated[str, Field(pattern="^T\d{4}(.\d{3})?$")]]] = None
|
|
27
|
+
datamodels: Optional[Set[DataModel]] = None
|
|
28
|
+
kill_chain_phases: Optional[Set[KillChainPhase]] = None
|
|
29
|
+
cve: List[Annotated[str, "^CVE-[1|2][0-9]{3}-[0-9]+$"]] = []
|
|
30
|
+
group: List[str] = Field([], description="A list of groups who leverage the techniques list in this Analytic Story.")
|
|
31
|
+
|
|
32
|
+
def getCategory_conf(self) -> str:
|
|
33
|
+
#if len(self.category) > 1:
|
|
34
|
+
# print("Story with more than 1 category. We can only have 1 category, fix it!")
|
|
35
|
+
return list(self.category)[0]
|
|
36
|
+
|
|
37
|
+
@model_serializer
|
|
38
|
+
def serialize_model(self):
|
|
39
|
+
#no super to call
|
|
40
|
+
return {
|
|
41
|
+
"category": list(self.category),
|
|
42
|
+
"product": list(self.product),
|
|
43
|
+
"usecase": self.usecase,
|
|
44
|
+
"mitre_attack_enrichments": self.mitre_attack_enrichments,
|
|
45
|
+
"mitre_attack_tactics": list(self.mitre_attack_tactics) if self.mitre_attack_tactics is not None else None,
|
|
46
|
+
"datamodels": list(self.datamodels) if self.datamodels is not None else None,
|
|
47
|
+
"kill_chain_phases": list(self.kill_chain_phases) if self.kill_chain_phases is not None else None
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
contentctl/objects/unit_test.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import Field
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from contentctl.objects.unit_test_attack_data import UnitTestAttackData
|
|
6
|
+
from contentctl.objects.unit_test_result import UnitTestResult
|
|
2
7
|
|
|
3
8
|
from typing import Union
|
|
4
9
|
|
|
@@ -20,7 +25,7 @@ class UnitTest(BaseTest):
|
|
|
20
25
|
# contentType: SecurityContentType = SecurityContentType.unit_tests
|
|
21
26
|
|
|
22
27
|
# The test type (unit)
|
|
23
|
-
test_type: TestType = Field(TestType.UNIT
|
|
28
|
+
test_type: TestType = Field(TestType.UNIT)
|
|
24
29
|
|
|
25
30
|
# The condition to check if the search was successful
|
|
26
31
|
pass_condition: Union[str, None] = None
|
|
@@ -1,22 +1,13 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from typing import Union
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import BaseModel, HttpUrl, FilePath, Field
|
|
3
|
+
from typing import Union, Optional
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class UnitTestAttackData(BaseModel):
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
source
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
custom_index: str = None
|
|
13
|
-
host: str = None
|
|
14
|
-
|
|
15
|
-
@validator("data", always=True)
|
|
16
|
-
def validate_data(cls, v, values):
|
|
17
|
-
return v
|
|
18
|
-
try:
|
|
19
|
-
Utils.verify_file_exists(v)
|
|
20
|
-
except Exception as e:
|
|
21
|
-
raise (ValueError(f"Cannot find file {v}: {str(e)}"))
|
|
22
|
-
return v
|
|
7
|
+
data: Union[HttpUrl, FilePath] = Field(...)
|
|
8
|
+
# TODO - should source and sourcetype should be mapped to a list
|
|
9
|
+
# of supported source and sourcetypes in a given environment?
|
|
10
|
+
source: str = Field(...)
|
|
11
|
+
sourcetype: str = Field(...)
|
|
12
|
+
custom_index: Optional[str] = None
|
|
13
|
+
host: Optional[str] = None
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import BaseModel
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
from contentctl.objects.
|
|
5
|
+
from contentctl.objects.unit_test_ssa import UnitTestSSA
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class UnitTestOld(BaseModel):
|
|
8
9
|
name: str
|
|
9
|
-
tests: list[
|
|
10
|
+
tests: list[UnitTestSSA]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Union,TYPE_CHECKING
|
|
3
4
|
from splunklib.data import Record
|
|
4
|
-
|
|
5
|
-
from contentctl.objects.test_config import Infrastructure
|
|
6
5
|
from contentctl.objects.base_test_result import BaseTestResult, TestResultStatus
|
|
7
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from contentctl.objects.config import Infrastructure
|
|
9
|
+
|
|
8
10
|
FORCE_TEST_FAILURE_FOR_MISSING_OBSERVABLE = False
|
|
9
11
|
|
|
10
12
|
NO_SID = "Testing Failed, NO Search ID"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UnitTestAttackDataSSA(BaseModel):
|
|
8
|
+
file_name:Optional[str] = None
|
|
9
|
+
data: str = Field(...)
|
|
10
|
+
# TODO - should source and sourcetype should be mapped to a list
|
|
11
|
+
# of supported source and sourcetypes in a given environment?
|
|
12
|
+
source: str = Field(...)
|
|
13
|
+
|
|
14
|
+
sourcetype: Optional[str] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UnitTestSSA(BaseModel):
|
|
18
|
+
"""
|
|
19
|
+
A unit test for a detection
|
|
20
|
+
"""
|
|
21
|
+
name: str
|
|
22
|
+
|
|
23
|
+
# The attack data to be ingested for the unit test
|
|
24
|
+
attack_data: list[UnitTestAttackDataSSA] = Field(...)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|