contentctl 4.4.7__py3-none-any.whl → 5.0.0__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/__init__.py +1 -1
- contentctl/actions/build.py +102 -57
- contentctl/actions/deploy_acs.py +29 -24
- contentctl/actions/detection_testing/DetectionTestingManager.py +66 -42
- contentctl/actions/detection_testing/GitService.py +134 -76
- contentctl/actions/detection_testing/generate_detection_coverage_badge.py +48 -30
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +192 -147
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +45 -32
- contentctl/actions/detection_testing/progress_bar.py +9 -6
- contentctl/actions/detection_testing/views/DetectionTestingView.py +16 -19
- contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +1 -5
- contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +2 -2
- contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +1 -4
- contentctl/actions/doc_gen.py +9 -5
- contentctl/actions/initialize.py +45 -33
- contentctl/actions/inspect.py +118 -61
- contentctl/actions/new_content.py +155 -108
- contentctl/actions/release_notes.py +276 -146
- contentctl/actions/reporting.py +23 -19
- contentctl/actions/test.py +33 -28
- contentctl/actions/validate.py +55 -34
- contentctl/api.py +54 -45
- contentctl/contentctl.py +124 -90
- contentctl/enrichments/attack_enrichment.py +112 -72
- contentctl/enrichments/cve_enrichment.py +34 -28
- contentctl/enrichments/splunk_app_enrichment.py +38 -36
- contentctl/helper/link_validator.py +101 -78
- contentctl/helper/splunk_app.py +69 -41
- contentctl/helper/utils.py +58 -53
- contentctl/input/director.py +68 -36
- contentctl/input/new_content_questions.py +27 -35
- contentctl/input/yml_reader.py +28 -18
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +303 -259
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +115 -52
- contentctl/objects/alert_action.py +10 -9
- contentctl/objects/annotated_types.py +1 -1
- contentctl/objects/atomic.py +65 -54
- contentctl/objects/base_test.py +5 -3
- contentctl/objects/base_test_result.py +19 -11
- contentctl/objects/baseline.py +62 -30
- contentctl/objects/baseline_tags.py +30 -24
- contentctl/objects/config.py +790 -597
- contentctl/objects/constants.py +33 -56
- contentctl/objects/correlation_search.py +150 -136
- contentctl/objects/dashboard.py +55 -41
- contentctl/objects/data_source.py +16 -17
- contentctl/objects/deployment.py +43 -44
- contentctl/objects/deployment_email.py +3 -2
- contentctl/objects/deployment_notable.py +4 -2
- contentctl/objects/deployment_phantom.py +7 -6
- contentctl/objects/deployment_rba.py +3 -2
- contentctl/objects/deployment_scheduling.py +3 -2
- contentctl/objects/deployment_slack.py +3 -2
- contentctl/objects/detection.py +5 -2
- contentctl/objects/detection_metadata.py +1 -0
- contentctl/objects/detection_stanza.py +7 -2
- contentctl/objects/detection_tags.py +58 -103
- contentctl/objects/drilldown.py +66 -34
- contentctl/objects/enums.py +81 -100
- contentctl/objects/errors.py +16 -24
- contentctl/objects/integration_test.py +3 -3
- contentctl/objects/integration_test_result.py +1 -0
- contentctl/objects/investigation.py +59 -36
- contentctl/objects/investigation_tags.py +30 -19
- contentctl/objects/lookup.py +304 -101
- contentctl/objects/macro.py +55 -39
- contentctl/objects/manual_test.py +3 -3
- contentctl/objects/manual_test_result.py +1 -0
- contentctl/objects/mitre_attack_enrichment.py +17 -16
- contentctl/objects/notable_action.py +2 -1
- contentctl/objects/notable_event.py +1 -3
- contentctl/objects/playbook.py +37 -35
- contentctl/objects/playbook_tags.py +23 -13
- contentctl/objects/rba.py +96 -0
- contentctl/objects/risk_analysis_action.py +15 -11
- contentctl/objects/risk_event.py +110 -160
- contentctl/objects/risk_object.py +1 -0
- contentctl/objects/savedsearches_conf.py +9 -7
- contentctl/objects/security_content_object.py +5 -2
- contentctl/objects/story.py +54 -49
- contentctl/objects/story_tags.py +56 -45
- contentctl/objects/test_attack_data.py +2 -1
- contentctl/objects/test_group.py +5 -2
- contentctl/objects/threat_object.py +1 -0
- contentctl/objects/throttling.py +27 -18
- contentctl/objects/unit_test.py +3 -4
- contentctl/objects/unit_test_baseline.py +5 -5
- contentctl/objects/unit_test_result.py +6 -6
- contentctl/output/api_json_output.py +233 -220
- contentctl/output/attack_nav_output.py +21 -21
- contentctl/output/attack_nav_writer.py +29 -37
- contentctl/output/conf_output.py +235 -172
- contentctl/output/conf_writer.py +201 -125
- contentctl/output/data_source_writer.py +38 -26
- contentctl/output/doc_md_output.py +53 -27
- contentctl/output/jinja_writer.py +19 -15
- contentctl/output/json_writer.py +21 -11
- contentctl/output/svg_output.py +56 -38
- contentctl/output/templates/analyticstories_detections.j2 +2 -2
- contentctl/output/templates/analyticstories_stories.j2 +1 -1
- contentctl/output/templates/collections.j2 +1 -1
- contentctl/output/templates/doc_detections.j2 +0 -5
- contentctl/output/templates/es_investigations_investigations.j2 +1 -1
- contentctl/output/templates/es_investigations_stories.j2 +1 -1
- contentctl/output/templates/savedsearches_baselines.j2 +2 -2
- contentctl/output/templates/savedsearches_detections.j2 +10 -11
- contentctl/output/templates/savedsearches_investigations.j2 +2 -2
- contentctl/output/templates/transforms.j2 +6 -8
- contentctl/output/yml_writer.py +29 -20
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
- contentctl/templates/stories/cobalt_strike.yml +1 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/METADATA +5 -4
- contentctl-5.0.0.dist-info/RECORD +168 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/WHEEL +1 -1
- contentctl/actions/initialize_old.py +0 -245
- contentctl/objects/event_source.py +0 -11
- contentctl/objects/observable.py +0 -37
- contentctl/output/detection_writer.py +0 -28
- contentctl/output/new_content_yml_output.py +0 -56
- contentctl/output/yml_output.py +0 -66
- contentctl-4.4.7.dist-info/RECORD +0 -173
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/LICENSE.md +0 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/entry_points.txt +0 -0
contentctl/objects/dashboard.py
CHANGED
|
@@ -8,7 +8,7 @@ from contentctl.objects.security_content_object import SecurityContentObject
|
|
|
8
8
|
from contentctl.objects.config import build
|
|
9
9
|
from enum import StrEnum
|
|
10
10
|
|
|
11
|
-
DEFAULT_DASHBAORD_JINJA2_TEMPLATE =
|
|
11
|
+
DEFAULT_DASHBAORD_JINJA2_TEMPLATE = """<dashboard version="2" theme="{{ dashboard.theme }}">
|
|
12
12
|
<label>{{ dashboard.label(config) }}</label>
|
|
13
13
|
<description></description>
|
|
14
14
|
<definition><![CDATA[
|
|
@@ -21,28 +21,40 @@ DEFAULT_DASHBAORD_JINJA2_TEMPLATE = '''<dashboard version="2" theme="{{ dashboar
|
|
|
21
21
|
"hideExport": false
|
|
22
22
|
}
|
|
23
23
|
]]></meta>
|
|
24
|
-
</dashboard>
|
|
24
|
+
</dashboard>"""
|
|
25
|
+
|
|
25
26
|
|
|
26
27
|
class DashboardTheme(StrEnum):
|
|
27
28
|
light = "light"
|
|
28
29
|
dark = "dark"
|
|
29
30
|
|
|
31
|
+
|
|
30
32
|
class Dashboard(SecurityContentObject):
|
|
31
|
-
j2_template: str = Field(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
j2_template: str = Field(
|
|
34
|
+
default=DEFAULT_DASHBAORD_JINJA2_TEMPLATE,
|
|
35
|
+
description="Jinja2 Template used to construct the dashboard",
|
|
36
|
+
)
|
|
37
|
+
description: str = Field(
|
|
38
|
+
...,
|
|
39
|
+
description="A description of the dashboard. This does not have to match "
|
|
40
|
+
"the description of the dashboard in the JSON file.",
|
|
41
|
+
max_length=10000,
|
|
42
|
+
)
|
|
43
|
+
theme: DashboardTheme = Field(
|
|
44
|
+
default=DashboardTheme.light,
|
|
45
|
+
description="The theme of the dashboard. Choose between 'light' and 'dark'.",
|
|
46
|
+
)
|
|
47
|
+
json_obj: Json[dict[str, Any]] = Field(
|
|
48
|
+
..., description="Valid JSON object that describes the dashboard"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def label(self, config: build) -> str:
|
|
40
52
|
return f"{config.app.label} - {self.name}"
|
|
41
|
-
|
|
53
|
+
|
|
42
54
|
@model_validator(mode="before")
|
|
43
55
|
@classmethod
|
|
44
|
-
def validate_fields_from_json(cls, data:Any)->Any:
|
|
45
|
-
yml_file_name:str|None = data.get("file_path", None)
|
|
56
|
+
def validate_fields_from_json(cls, data: Any) -> Any:
|
|
57
|
+
yml_file_name: str | None = data.get("file_path", None)
|
|
46
58
|
if yml_file_name is None:
|
|
47
59
|
raise ValueError("File name not passed to dashboard constructor")
|
|
48
60
|
yml_file_path = pathlib.Path(yml_file_name)
|
|
@@ -50,51 +62,53 @@ class Dashboard(SecurityContentObject):
|
|
|
50
62
|
|
|
51
63
|
if not json_file_path.is_file():
|
|
52
64
|
raise ValueError(f"Required file {json_file_path} does not exist.")
|
|
53
|
-
|
|
54
|
-
with open(json_file_path,
|
|
65
|
+
|
|
66
|
+
with open(json_file_path, "r") as jsonFilePointer:
|
|
55
67
|
try:
|
|
56
|
-
json_obj:dict[str,Any] = json.load(jsonFilePointer)
|
|
68
|
+
json_obj: dict[str, Any] = json.load(jsonFilePointer)
|
|
57
69
|
except Exception as e:
|
|
58
70
|
raise ValueError(f"Unable to load data from {json_file_path}: {str(e)}")
|
|
59
71
|
|
|
60
|
-
name_from_file = data.get("name",None)
|
|
61
|
-
name_from_json
|
|
72
|
+
name_from_file = data.get("name", None)
|
|
73
|
+
name_from_json = json_obj.get("title", None)
|
|
62
74
|
|
|
63
|
-
errors:list[str] = []
|
|
75
|
+
errors: list[str] = []
|
|
64
76
|
if name_from_json is None:
|
|
65
77
|
errors.append(f"'title' field is missing from {json_file_path}")
|
|
66
78
|
elif name_from_json != name_from_file:
|
|
67
|
-
errors.append(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
errors.append(
|
|
80
|
+
f"The 'title' field in the JSON file [{json_file_path}] does not match the 'name' field in the YML object [{yml_file_path}]. These two MUST match:\n "
|
|
81
|
+
f"title in JSON : {name_from_json}\n "
|
|
82
|
+
f"title in YML : {name_from_file}\n "
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
description_from_json = json_obj.get("description", None)
|
|
72
86
|
if description_from_json is None:
|
|
73
87
|
errors.append("'description' field is missing from field 'json_object'")
|
|
74
|
-
|
|
75
|
-
if len(errors) > 0
|
|
88
|
+
|
|
89
|
+
if len(errors) > 0:
|
|
76
90
|
err_string = "\n - ".join(errors)
|
|
77
91
|
raise ValueError(f"Error(s) validating dashboard:\n - {err_string}")
|
|
78
|
-
|
|
79
|
-
data[
|
|
80
|
-
data[
|
|
92
|
+
|
|
93
|
+
data["name"] = name_from_file
|
|
94
|
+
data["json_obj"] = json.dumps(json_obj)
|
|
81
95
|
return data
|
|
82
96
|
|
|
83
|
-
|
|
84
97
|
def pretty_print_json_obj(self):
|
|
85
98
|
return json.dumps(self.json_obj, indent=4)
|
|
86
|
-
|
|
87
|
-
def getOutputFilepathRelativeToAppRoot(self, config:build)->pathlib.Path:
|
|
99
|
+
|
|
100
|
+
def getOutputFilepathRelativeToAppRoot(self, config: build) -> pathlib.Path:
|
|
88
101
|
filename = f"{self.file_path.stem}.xml".lower()
|
|
89
|
-
return pathlib.Path("default/data/ui/views")/filename
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def writeDashboardFile(self, j2_env:Environment, config:build):
|
|
102
|
+
return pathlib.Path("default/data/ui/views") / filename
|
|
103
|
+
|
|
104
|
+
def writeDashboardFile(self, j2_env: Environment, config: build):
|
|
93
105
|
template = j2_env.from_string(self.j2_template)
|
|
94
106
|
dashboard_text = template.render(config=config, dashboard=self)
|
|
95
107
|
|
|
96
|
-
with open(
|
|
97
|
-
|
|
108
|
+
with open(
|
|
109
|
+
config.getPackageDirectoryPath()
|
|
110
|
+
/ self.getOutputFilepathRelativeToAppRoot(config),
|
|
111
|
+
"a",
|
|
112
|
+
) as f:
|
|
113
|
+
output_xml = dashboard_text.encode("utf-8", "ignore").decode("utf-8")
|
|
98
114
|
f.write(output_xml)
|
|
99
|
-
|
|
100
|
-
|
|
@@ -2,32 +2,32 @@ from __future__ import annotations
|
|
|
2
2
|
from typing import Optional, Any
|
|
3
3
|
from pydantic import Field, HttpUrl, model_serializer, BaseModel
|
|
4
4
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
5
|
-
from contentctl.objects.event_source import EventSource
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class TA(BaseModel):
|
|
9
8
|
name: str
|
|
10
9
|
url: HttpUrl | None = None
|
|
11
10
|
version: str
|
|
11
|
+
|
|
12
|
+
|
|
12
13
|
class DataSource(SecurityContentObject):
|
|
13
14
|
source: str = Field(...)
|
|
14
15
|
sourcetype: str = Field(...)
|
|
15
16
|
separator: Optional[str] = None
|
|
16
17
|
configuration: Optional[str] = None
|
|
17
18
|
supported_TA: list[TA] = []
|
|
18
|
-
fields:
|
|
19
|
-
field_mappings:
|
|
20
|
-
convert_to_log_source:
|
|
21
|
-
example_log:
|
|
22
|
-
|
|
19
|
+
fields: None | list = None
|
|
20
|
+
field_mappings: None | list = None
|
|
21
|
+
convert_to_log_source: None | list = None
|
|
22
|
+
example_log: None | str = None
|
|
23
23
|
|
|
24
24
|
@model_serializer
|
|
25
25
|
def serialize_model(self):
|
|
26
|
-
#Call serializer for parent
|
|
26
|
+
# Call serializer for parent
|
|
27
27
|
super_fields = super().serialize_model()
|
|
28
|
-
|
|
29
|
-
#All fields custom to this model
|
|
30
|
-
model:dict[str,Any] = {
|
|
28
|
+
|
|
29
|
+
# All fields custom to this model
|
|
30
|
+
model: dict[str, Any] = {
|
|
31
31
|
"source": self.source,
|
|
32
32
|
"sourcetype": self.sourcetype,
|
|
33
33
|
"separator": self.separator,
|
|
@@ -36,12 +36,11 @@ class DataSource(SecurityContentObject):
|
|
|
36
36
|
"fields": self.fields,
|
|
37
37
|
"field_mappings": self.field_mappings,
|
|
38
38
|
"convert_to_log_source": self.convert_to_log_source,
|
|
39
|
-
"example_log":self.example_log
|
|
39
|
+
"example_log": self.example_log,
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
#Combine fields from this model with fields from parent
|
|
41
|
+
|
|
42
|
+
# Combine fields from this model with fields from parent
|
|
44
43
|
super_fields.update(model)
|
|
45
|
-
|
|
46
|
-
#return the model
|
|
47
|
-
return super_fields
|
|
44
|
+
|
|
45
|
+
# return the model
|
|
46
|
+
return super_fields
|
contentctl/objects/deployment.py
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from pydantic import
|
|
2
|
+
from pydantic import (
|
|
3
|
+
Field,
|
|
4
|
+
computed_field,
|
|
5
|
+
ValidationInfo,
|
|
6
|
+
model_serializer,
|
|
7
|
+
NonNegativeInt,
|
|
8
|
+
)
|
|
3
9
|
from typing import Any
|
|
4
10
|
import uuid
|
|
5
11
|
import datetime
|
|
@@ -11,75 +17,68 @@ from contentctl.objects.enums import DeploymentType
|
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class Deployment(SecurityContentObject):
|
|
14
|
-
#id: str = None
|
|
15
|
-
#date: str = None
|
|
16
|
-
#author: str = None
|
|
17
|
-
#description: str = None
|
|
18
|
-
#contentType: SecurityContentType = SecurityContentType.deployments
|
|
19
|
-
|
|
20
|
-
|
|
21
20
|
scheduling: DeploymentScheduling = Field(...)
|
|
22
21
|
alert_action: AlertAction = AlertAction()
|
|
23
22
|
type: DeploymentType = Field(...)
|
|
24
|
-
author: str = Field(...,max_length=255)
|
|
23
|
+
author: str = Field(..., max_length=255)
|
|
25
24
|
version: NonNegativeInt = 1
|
|
26
25
|
|
|
27
|
-
#Type was the only tag exposed and should likely be removed/refactored.
|
|
28
|
-
#For transitional reasons, provide this as a computed_field in prep for removal
|
|
26
|
+
# Type was the only tag exposed and should likely be removed/refactored.
|
|
27
|
+
# For transitional reasons, provide this as a computed_field in prep for removal
|
|
29
28
|
@computed_field
|
|
30
29
|
@property
|
|
31
|
-
def tags(self)->dict[str,DeploymentType]:
|
|
30
|
+
def tags(self) -> dict[str, DeploymentType]:
|
|
32
31
|
return {"type": self.type}
|
|
33
32
|
|
|
34
|
-
|
|
35
33
|
@staticmethod
|
|
36
|
-
def getDeployment(v:dict[str,Any], info:ValidationInfo)->Deployment:
|
|
34
|
+
def getDeployment(v: dict[str, Any], info: ValidationInfo) -> Deployment:
|
|
37
35
|
if v != {}:
|
|
38
36
|
# If the user has defined a deployment, then allow it to be validated
|
|
39
37
|
# and override the default deployment info defined in type:Baseline
|
|
40
|
-
v[
|
|
41
|
-
|
|
38
|
+
v["type"] = DeploymentType.Embedded
|
|
39
|
+
|
|
42
40
|
detection_name = info.data.get("name", None)
|
|
43
41
|
if detection_name is None:
|
|
44
|
-
raise ValueError(
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"Could not create inline deployment - Baseline or Detection lacking 'name' field,"
|
|
44
|
+
)
|
|
45
45
|
|
|
46
|
-
# Add a number of static values
|
|
47
|
-
v.update(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
# Add a number of static values
|
|
47
|
+
v.update(
|
|
48
|
+
{
|
|
49
|
+
"name": f"{detection_name} - Inline Deployment",
|
|
50
|
+
"id": uuid.uuid4(),
|
|
51
|
+
"date": datetime.date.today(),
|
|
52
|
+
"description": "Inline deployment created at runtime.",
|
|
53
|
+
"author": "contentctl tool",
|
|
54
|
+
}
|
|
55
|
+
)
|
|
54
56
|
|
|
55
|
-
|
|
56
57
|
# This constructs a temporary in-memory deployment,
|
|
57
|
-
# allowing the deployment to be easily defined in the
|
|
58
|
+
# allowing the deployment to be easily defined in the
|
|
58
59
|
# detection on a per detection basis.
|
|
59
60
|
return Deployment.model_validate(v)
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
else:
|
|
62
|
-
return SecurityContentObject.getDeploymentFromType(
|
|
63
|
-
|
|
63
|
+
return SecurityContentObject.getDeploymentFromType(
|
|
64
|
+
info.data.get("type", None), info
|
|
65
|
+
)
|
|
66
|
+
|
|
64
67
|
@model_serializer
|
|
65
68
|
def serialize_model(self):
|
|
66
|
-
#Call serializer for parent
|
|
69
|
+
# Call serializer for parent
|
|
67
70
|
super_fields = super().serialize_model()
|
|
68
|
-
|
|
69
|
-
#All fields custom to this model
|
|
70
|
-
model= {
|
|
71
|
-
"scheduling": self.scheduling.model_dump(),
|
|
72
|
-
"tags": self.tags
|
|
73
|
-
}
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
# All fields custom to this model
|
|
73
|
+
model = {"scheduling": self.scheduling.model_dump(), "tags": self.tags}
|
|
74
|
+
|
|
75
|
+
# Combine fields from this model with fields from parent
|
|
77
76
|
model.update(super_fields)
|
|
78
|
-
|
|
77
|
+
|
|
79
78
|
alert_action_fields = self.alert_action.model_dump()
|
|
80
79
|
model.update(alert_action_fields)
|
|
81
80
|
|
|
82
|
-
del
|
|
83
|
-
|
|
84
|
-
#return the model
|
|
85
|
-
return model
|
|
81
|
+
del model["references"]
|
|
82
|
+
|
|
83
|
+
# return the model
|
|
84
|
+
return model
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from pydantic import BaseModel
|
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
class DeploymentNotable(BaseModel):
|
|
7
|
+
model_config = ConfigDict(extra="forbid")
|
|
6
8
|
rule_description: str
|
|
7
9
|
rule_title: str
|
|
8
|
-
nes_fields: List[str]
|
|
10
|
+
nes_fields: List[str]
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from pydantic import BaseModel
|
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class DeploymentPhantom(BaseModel):
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
model_config = ConfigDict(extra="forbid")
|
|
7
|
+
cam_workers: str
|
|
8
|
+
label: str
|
|
9
|
+
phantom_server: str
|
|
10
|
+
sensitivity: str
|
|
11
|
+
severity: str
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from pydantic import BaseModel
|
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class DeploymentScheduling(BaseModel):
|
|
6
|
+
model_config = ConfigDict(extra="forbid")
|
|
6
7
|
cron_schedule: str
|
|
7
8
|
earliest_time: str
|
|
8
9
|
latest_time: str
|
|
9
|
-
schedule_window: str
|
|
10
|
+
schedule_window: str
|
contentctl/objects/detection.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from contentctl.objects.abstract_security_content_objects.detection_abstract import
|
|
2
|
+
from contentctl.objects.abstract_security_content_objects.detection_abstract import (
|
|
3
|
+
Detection_Abstract,
|
|
4
|
+
)
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
class Detection(Detection_Abstract):
|
|
5
8
|
# Customization to the Detection Class go here.
|
|
@@ -12,4 +15,4 @@ class Detection(Detection_Abstract):
|
|
|
12
15
|
# them or modifying their behavior may cause
|
|
13
16
|
# undefined issues with the contentctl tooling
|
|
14
17
|
# or output of the tooling.
|
|
15
|
-
pass
|
|
18
|
+
pass
|
|
@@ -11,6 +11,7 @@ class DetectionStanza(BaseModel):
|
|
|
11
11
|
"""
|
|
12
12
|
A model representing a stanza for a detection in savedsearches.conf
|
|
13
13
|
"""
|
|
14
|
+
|
|
14
15
|
# The lines that comprise this stanza, in the order they appear in the conf
|
|
15
16
|
lines: list[str] = Field(...)
|
|
16
17
|
|
|
@@ -47,7 +48,9 @@ class DetectionStanza(BaseModel):
|
|
|
47
48
|
raise Exception(f"No metadata for detection '{self.name}' found in stanza.")
|
|
48
49
|
|
|
49
50
|
# Parse the metadata JSON into a model
|
|
50
|
-
return DetectionMetadata.model_validate_json(
|
|
51
|
+
return DetectionMetadata.model_validate_json(
|
|
52
|
+
meta_line[len(DetectionStanza.METADATA_LINE_PREFIX) :]
|
|
53
|
+
)
|
|
51
54
|
|
|
52
55
|
@computed_field
|
|
53
56
|
@cached_property
|
|
@@ -76,4 +79,6 @@ class DetectionStanza(BaseModel):
|
|
|
76
79
|
:returns: True if the version still needs to be bumped
|
|
77
80
|
:rtype: bool
|
|
78
81
|
"""
|
|
79
|
-
return (self.hash != previous.hash) and (
|
|
82
|
+
return (self.hash != previous.hash) and (
|
|
83
|
+
self.metadata.detection_version <= previous.metadata.detection_version
|
|
84
|
+
)
|