contentctl 4.4.7__py3-none-any.whl → 5.0.0a2__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 +39 -27
- contentctl/actions/detection_testing/DetectionTestingManager.py +0 -1
- contentctl/actions/detection_testing/GitService.py +132 -72
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +32 -26
- contentctl/actions/detection_testing/progress_bar.py +6 -6
- contentctl/actions/detection_testing/views/DetectionTestingView.py +4 -4
- contentctl/actions/new_content.py +98 -81
- contentctl/actions/test.py +4 -5
- contentctl/actions/validate.py +2 -1
- contentctl/contentctl.py +114 -80
- contentctl/helper/utils.py +0 -14
- contentctl/input/director.py +5 -5
- contentctl/input/new_content_questions.py +2 -2
- contentctl/input/yml_reader.py +11 -6
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +228 -120
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +5 -7
- contentctl/objects/alert_action.py +2 -1
- contentctl/objects/atomic.py +1 -0
- contentctl/objects/base_test.py +4 -3
- contentctl/objects/base_test_result.py +3 -3
- contentctl/objects/baseline.py +26 -6
- contentctl/objects/baseline_tags.py +2 -3
- contentctl/objects/config.py +789 -596
- contentctl/objects/constants.py +4 -1
- contentctl/objects/correlation_search.py +89 -95
- contentctl/objects/data_source.py +5 -6
- contentctl/objects/deployment.py +2 -10
- contentctl/objects/deployment_email.py +2 -1
- contentctl/objects/deployment_notable.py +2 -1
- contentctl/objects/deployment_phantom.py +2 -1
- contentctl/objects/deployment_rba.py +2 -1
- contentctl/objects/deployment_scheduling.py +2 -1
- contentctl/objects/deployment_slack.py +2 -1
- contentctl/objects/detection_tags.py +7 -42
- contentctl/objects/drilldown.py +1 -0
- contentctl/objects/enums.py +21 -58
- contentctl/objects/investigation.py +6 -5
- contentctl/objects/investigation_tags.py +2 -3
- contentctl/objects/lookup.py +145 -63
- contentctl/objects/macro.py +2 -3
- contentctl/objects/mitre_attack_enrichment.py +2 -2
- contentctl/objects/observable.py +3 -1
- contentctl/objects/playbook_tags.py +5 -1
- contentctl/objects/rba.py +90 -0
- contentctl/objects/risk_event.py +87 -144
- contentctl/objects/story_tags.py +1 -2
- contentctl/objects/test_attack_data.py +2 -1
- contentctl/objects/unit_test_baseline.py +2 -1
- contentctl/output/api_json_output.py +233 -220
- contentctl/output/conf_output.py +51 -44
- contentctl/output/conf_writer.py +201 -125
- contentctl/output/data_source_writer.py +0 -1
- contentctl/output/json_writer.py +2 -4
- contentctl/output/svg_output.py +1 -1
- contentctl/output/templates/analyticstories_detections.j2 +1 -1
- contentctl/output/templates/collections.j2 +1 -1
- contentctl/output/templates/doc_detections.j2 +0 -5
- contentctl/output/templates/savedsearches_detections.j2 +8 -3
- contentctl/output/templates/transforms.j2 +4 -4
- contentctl/output/yml_writer.py +15 -0
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
- {contentctl-4.4.7.dist-info → contentctl-5.0.0a2.dist-info}/METADATA +5 -4
- {contentctl-4.4.7.dist-info → contentctl-5.0.0a2.dist-info}/RECORD +66 -69
- {contentctl-4.4.7.dist-info → contentctl-5.0.0a2.dist-info}/WHEEL +1 -1
- contentctl/objects/event_source.py +0 -11
- 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 → contentctl-5.0.0a2.dist-info}/LICENSE.md +0 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0a2.dist-info}/entry_points.txt +0 -0
contentctl/actions/build.py
CHANGED
|
@@ -4,17 +4,17 @@ import os
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
|
|
7
|
-
from contentctl.objects.enums import
|
|
7
|
+
from contentctl.objects.enums import SecurityContentType
|
|
8
8
|
from contentctl.input.director import Director, DirectorOutputDto
|
|
9
9
|
from contentctl.output.conf_output import ConfOutput
|
|
10
10
|
from contentctl.output.conf_writer import ConfWriter
|
|
11
11
|
from contentctl.output.api_json_output import ApiJsonOutput
|
|
12
12
|
from contentctl.output.data_source_writer import DataSourceWriter
|
|
13
|
-
from contentctl.objects.lookup import
|
|
13
|
+
from contentctl.objects.lookup import CSVLookup, Lookup_Type
|
|
14
14
|
import pathlib
|
|
15
15
|
import json
|
|
16
16
|
import datetime
|
|
17
|
-
|
|
17
|
+
import uuid
|
|
18
18
|
|
|
19
19
|
from contentctl.objects.config import build
|
|
20
20
|
|
|
@@ -34,27 +34,41 @@ class Build:
|
|
|
34
34
|
updated_conf_files:set[pathlib.Path] = set()
|
|
35
35
|
conf_output = ConfOutput(input_dto.config)
|
|
36
36
|
|
|
37
|
+
|
|
38
|
+
# Construct a path to a YML that does not actually exist.
|
|
39
|
+
# We mock this "fake" path since the YML does not exist.
|
|
40
|
+
# This ensures the checking for the existence of the CSV is correct
|
|
41
|
+
data_sources_fake_yml_path = input_dto.config.getPackageDirectoryPath() / "lookups" / "data_sources.yml"
|
|
42
|
+
|
|
37
43
|
# Construct a special lookup whose CSV is created at runtime and
|
|
38
|
-
# written directly into the
|
|
39
|
-
#
|
|
44
|
+
# written directly into the lookups folder. We will delete this after a build,
|
|
45
|
+
# assuming that it is successful.
|
|
40
46
|
data_sources_lookup_csv_path = input_dto.config.getPackageDirectoryPath() / "lookups" / "data_sources.csv"
|
|
41
|
-
DataSourceWriter.writeDataSourceCsv(input_dto.director_output_dto.data_sources, data_sources_lookup_csv_path)
|
|
42
|
-
input_dto.director_output_dto.addContentToDictMappings(Lookup.model_construct(description= "A lookup file that will contain the data source objects for detections.",
|
|
43
|
-
filename=data_sources_lookup_csv_path,
|
|
44
|
-
name="data_sources"))
|
|
45
47
|
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
DataSourceWriter.writeDataSourceCsv(input_dto.director_output_dto.data_sources, data_sources_lookup_csv_path)
|
|
51
|
+
input_dto.director_output_dto.addContentToDictMappings(CSVLookup.model_construct(name="data_sources",
|
|
52
|
+
id=uuid.UUID("b45c1403-6e09-47b0-824f-cf6e44f15ac8"),
|
|
53
|
+
version=1,
|
|
54
|
+
author=input_dto.config.app.author_name,
|
|
55
|
+
date = datetime.date.today(),
|
|
56
|
+
description= "A lookup file that will contain the data source objects for detections.",
|
|
57
|
+
lookup_type=Lookup_Type.csv,
|
|
58
|
+
file_path=data_sources_fake_yml_path))
|
|
46
59
|
updated_conf_files.update(conf_output.writeHeaders())
|
|
47
|
-
updated_conf_files.update(conf_output.
|
|
48
|
-
updated_conf_files.update(conf_output.
|
|
49
|
-
updated_conf_files.update(conf_output.
|
|
50
|
-
updated_conf_files.update(conf_output.
|
|
51
|
-
updated_conf_files.update(conf_output.
|
|
52
|
-
updated_conf_files.update(conf_output.
|
|
53
|
-
updated_conf_files.update(conf_output.
|
|
60
|
+
updated_conf_files.update(conf_output.writeLookups(input_dto.director_output_dto.lookups))
|
|
61
|
+
updated_conf_files.update(conf_output.writeDetections(input_dto.director_output_dto.detections))
|
|
62
|
+
updated_conf_files.update(conf_output.writeStories(input_dto.director_output_dto.stories))
|
|
63
|
+
updated_conf_files.update(conf_output.writeBaselines(input_dto.director_output_dto.baselines))
|
|
64
|
+
updated_conf_files.update(conf_output.writeInvestigations(input_dto.director_output_dto.investigations))
|
|
65
|
+
updated_conf_files.update(conf_output.writeMacros(input_dto.director_output_dto.macros))
|
|
66
|
+
updated_conf_files.update(conf_output.writeDashboards(input_dto.director_output_dto.dashboards))
|
|
54
67
|
updated_conf_files.update(conf_output.writeMiscellaneousAppFiles())
|
|
55
68
|
|
|
56
69
|
|
|
57
70
|
|
|
71
|
+
|
|
58
72
|
#Ensure that the conf file we just generated/update is syntactically valid
|
|
59
73
|
for conf_file in updated_conf_files:
|
|
60
74
|
ConfWriter.validateConfFile(conf_file)
|
|
@@ -67,17 +81,15 @@ class Build:
|
|
|
67
81
|
if input_dto.config.build_api:
|
|
68
82
|
shutil.rmtree(input_dto.config.getAPIPath(), ignore_errors=True)
|
|
69
83
|
input_dto.config.getAPIPath().mkdir(parents=True)
|
|
70
|
-
api_json_output = ApiJsonOutput()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
api_json_output = ApiJsonOutput(input_dto.config.getAPIPath(), input_dto.config.app.label)
|
|
85
|
+
api_json_output.writeDetections(input_dto.director_output_dto.detections)
|
|
86
|
+
api_json_output.writeStories(input_dto.director_output_dto.stories)
|
|
87
|
+
api_json_output.writeBaselines(input_dto.director_output_dto.baselines)
|
|
88
|
+
api_json_output.writeInvestigations(input_dto.director_output_dto.investigations)
|
|
89
|
+
api_json_output.writeLookups(input_dto.director_output_dto.lookups)
|
|
90
|
+
api_json_output.writeMacros(input_dto.director_output_dto.macros)
|
|
91
|
+
api_json_output.writeDeployments(input_dto.director_output_dto.deployments)
|
|
92
|
+
|
|
81
93
|
|
|
82
94
|
#create version file for sse api
|
|
83
95
|
version_file = input_dto.config.getAPIPath()/"version.json"
|
|
@@ -5,7 +5,6 @@ from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfras
|
|
|
5
5
|
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureServer import DetectionTestingInfrastructureServer
|
|
6
6
|
from urllib.parse import urlparse
|
|
7
7
|
from copy import deepcopy
|
|
8
|
-
from contentctl.objects.enums import DetectionTestingTargetInfrastructure
|
|
9
8
|
import signal
|
|
10
9
|
import datetime
|
|
11
10
|
# from queue import Queue
|
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
import pathlib
|
|
4
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
5
|
+
|
|
4
6
|
import pygit2
|
|
5
|
-
from pygit2.enums import DeltaStatus
|
|
6
|
-
from typing import List, Optional
|
|
7
7
|
from pydantic import BaseModel, FilePath
|
|
8
|
-
from
|
|
8
|
+
from pygit2.enums import DeltaStatus
|
|
9
|
+
|
|
9
10
|
if TYPE_CHECKING:
|
|
10
11
|
from contentctl.input.director import DirectorOutputDto
|
|
11
|
-
|
|
12
12
|
|
|
13
|
-
from contentctl.objects.
|
|
14
|
-
from contentctl.objects.lookup import Lookup
|
|
15
|
-
from contentctl.objects.detection import Detection
|
|
13
|
+
from contentctl.objects.config import All, Changes, Selected, test_common
|
|
16
14
|
from contentctl.objects.data_source import DataSource
|
|
15
|
+
from contentctl.objects.detection import Detection
|
|
16
|
+
from contentctl.objects.lookup import CSVLookup, Lookup
|
|
17
|
+
from contentctl.objects.macro import Macro
|
|
17
18
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
18
|
-
from contentctl.objects.config import test_common, All, Changes, Selected
|
|
19
19
|
|
|
20
20
|
# Logger
|
|
21
21
|
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
|
22
22
|
LOGGER = logging.getLogger(__name__)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
from contentctl.input.director import DirectorOutputDto
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
|
|
30
28
|
class GitService(BaseModel):
|
|
31
29
|
director: DirectorOutputDto
|
|
32
30
|
config: test_common
|
|
33
31
|
gitHash: Optional[str] = None
|
|
34
|
-
|
|
35
|
-
def getHash(self)->str:
|
|
32
|
+
|
|
33
|
+
def getHash(self) -> str:
|
|
36
34
|
if self.gitHash is None:
|
|
37
35
|
raise Exception("Cannot get hash of repo, it was not set")
|
|
38
36
|
return self.gitHash
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
def getContent(self)->List[Detection]:
|
|
38
|
+
def getContent(self) -> List[Detection]:
|
|
42
39
|
if isinstance(self.config.mode, Selected):
|
|
43
40
|
return self.getSelected(self.config.mode.files)
|
|
44
41
|
elif isinstance(self.config.mode, Changes):
|
|
@@ -46,142 +43,205 @@ class GitService(BaseModel):
|
|
|
46
43
|
if isinstance(self.config.mode, All):
|
|
47
44
|
return self.getAll()
|
|
48
45
|
else:
|
|
49
|
-
raise Exception(
|
|
50
|
-
|
|
46
|
+
raise Exception(
|
|
47
|
+
f"Could not get content to test. Unsupported test mode '{self.config.mode}'"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def getAll(self) -> List[Detection]:
|
|
51
51
|
return self.director.detections
|
|
52
|
-
|
|
53
|
-
def getChanges(self, target_branch:str)->List[Detection]:
|
|
52
|
+
|
|
53
|
+
def getChanges(self, target_branch: str) -> List[Detection]:
|
|
54
54
|
repo = pygit2.Repository(path=str(self.config.path))
|
|
55
55
|
|
|
56
56
|
try:
|
|
57
57
|
target_tree = repo.revparse_single(target_branch).tree
|
|
58
58
|
self.gitHash = target_tree.id
|
|
59
59
|
diffs = repo.index.diff_to_tree(target_tree)
|
|
60
|
-
except Exception
|
|
61
|
-
raise Exception(
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
except Exception:
|
|
61
|
+
raise Exception(
|
|
62
|
+
f"Error parsing diff target_branch '{target_branch}'. Are you certain that it exists?"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Get the uncommitted changes in the current directory
|
|
64
66
|
diffs2 = repo.index.diff_to_workdir()
|
|
65
|
-
|
|
66
|
-
#Combine the uncommitted changes with the committed changes
|
|
67
|
+
|
|
68
|
+
# Combine the uncommitted changes with the committed changes
|
|
67
69
|
all_diffs = list(diffs) + list(diffs2)
|
|
68
70
|
|
|
69
|
-
#Make a filename to content map
|
|
70
|
-
filepath_to_content_map = {
|
|
71
|
+
# Make a filename to content map
|
|
72
|
+
filepath_to_content_map = {
|
|
73
|
+
obj.file_path: obj for (_, obj) in self.director.name_to_content_map.items()
|
|
74
|
+
}
|
|
71
75
|
|
|
72
76
|
updated_detections: set[Detection] = set()
|
|
73
77
|
updated_macros: set[Macro] = set()
|
|
74
78
|
updated_lookups: set[Lookup] = set()
|
|
75
79
|
updated_datasources: set[DataSource] = set()
|
|
76
80
|
|
|
77
|
-
|
|
78
81
|
for diff in all_diffs:
|
|
79
82
|
if type(diff) == pygit2.Patch:
|
|
80
|
-
if diff.delta.status in (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
if diff.delta.status in (
|
|
84
|
+
DeltaStatus.ADDED,
|
|
85
|
+
DeltaStatus.MODIFIED,
|
|
86
|
+
DeltaStatus.RENAMED,
|
|
87
|
+
):
|
|
88
|
+
# print(f"{DeltaStatus(diff.delta.status).name:<8}:{diff.delta.new_file.raw_path}")
|
|
89
|
+
decoded_path = pathlib.Path(
|
|
90
|
+
diff.delta.new_file.raw_path.decode("utf-8")
|
|
91
|
+
)
|
|
83
92
|
# Note that we only handle updates to detections, lookups, and macros at this time. All other changes are ignored.
|
|
84
|
-
if
|
|
85
|
-
|
|
93
|
+
if (
|
|
94
|
+
decoded_path.is_relative_to(self.config.path / "detections")
|
|
95
|
+
and decoded_path.suffix == ".yml"
|
|
96
|
+
):
|
|
97
|
+
detectionObject = filepath_to_content_map.get(
|
|
98
|
+
decoded_path, None
|
|
99
|
+
)
|
|
86
100
|
if isinstance(detectionObject, Detection):
|
|
87
101
|
updated_detections.add(detectionObject)
|
|
88
102
|
else:
|
|
89
|
-
raise Exception(
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
raise Exception(
|
|
104
|
+
f"Error getting detection object for file {str(decoded_path)}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
elif (
|
|
108
|
+
decoded_path.is_relative_to(self.config.path / "macros")
|
|
109
|
+
and decoded_path.suffix == ".yml"
|
|
110
|
+
):
|
|
92
111
|
macroObject = filepath_to_content_map.get(decoded_path, None)
|
|
93
112
|
if isinstance(macroObject, Macro):
|
|
94
113
|
updated_macros.add(macroObject)
|
|
95
114
|
else:
|
|
96
|
-
raise Exception(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
raise Exception(
|
|
116
|
+
f"Error getting macro object for file {str(decoded_path)}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
elif (
|
|
120
|
+
decoded_path.is_relative_to(self.config.path / "data_sources")
|
|
121
|
+
and decoded_path.suffix == ".yml"
|
|
122
|
+
):
|
|
123
|
+
datasourceObject = filepath_to_content_map.get(
|
|
124
|
+
decoded_path, None
|
|
125
|
+
)
|
|
100
126
|
if isinstance(datasourceObject, DataSource):
|
|
101
127
|
updated_datasources.add(datasourceObject)
|
|
102
128
|
else:
|
|
103
|
-
raise Exception(
|
|
129
|
+
raise Exception(
|
|
130
|
+
f"Error getting data source object for file {str(decoded_path)}"
|
|
131
|
+
)
|
|
104
132
|
|
|
105
|
-
elif decoded_path.is_relative_to(self.config.path/"lookups"):
|
|
133
|
+
elif decoded_path.is_relative_to(self.config.path / "lookups"):
|
|
106
134
|
# We need to convert this to a yml. This means we will catch
|
|
107
135
|
# both changes to a csv AND changes to the YML that uses it
|
|
108
136
|
if decoded_path.suffix == ".yml":
|
|
109
|
-
updatedLookup = filepath_to_content_map.get(
|
|
110
|
-
|
|
111
|
-
|
|
137
|
+
updatedLookup = filepath_to_content_map.get(
|
|
138
|
+
decoded_path, None
|
|
139
|
+
)
|
|
140
|
+
if not isinstance(updatedLookup, Lookup):
|
|
141
|
+
raise Exception(
|
|
142
|
+
f"Expected {decoded_path} to be type {type(Lookup)}, but instead if was {(type(updatedLookup))}"
|
|
143
|
+
)
|
|
112
144
|
updated_lookups.add(updatedLookup)
|
|
113
145
|
|
|
114
146
|
elif decoded_path.suffix == ".csv":
|
|
115
|
-
# If the CSV was updated, we want to make sure that we
|
|
147
|
+
# If the CSV was updated, we want to make sure that we
|
|
116
148
|
# add the correct corresponding Lookup object.
|
|
117
|
-
#Filter to find the Lookup Object the references this CSV
|
|
118
|
-
matched = list(
|
|
149
|
+
# Filter to find the Lookup Object the references this CSV
|
|
150
|
+
matched = list(
|
|
151
|
+
filter(
|
|
152
|
+
lambda x: isinstance(x, CSVLookup)
|
|
153
|
+
and x.filename == decoded_path,
|
|
154
|
+
self.director.lookups,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
119
157
|
if len(matched) == 0:
|
|
120
|
-
raise Exception(
|
|
158
|
+
raise Exception(
|
|
159
|
+
f"Failed to find any lookups that reference the modified CSV file '{decoded_path}'"
|
|
160
|
+
)
|
|
121
161
|
elif len(matched) > 1:
|
|
122
|
-
raise Exception(
|
|
162
|
+
raise Exception(
|
|
163
|
+
f"More than 1 Lookup reference the modified CSV file '{decoded_path}': {[match.file_path for match in matched]}"
|
|
164
|
+
)
|
|
123
165
|
else:
|
|
124
166
|
updatedLookup = matched[0]
|
|
125
167
|
elif decoded_path.suffix == ".mlmodel":
|
|
126
|
-
# Detected a changed .mlmodel file. However, since we do not have testing for these detections at
|
|
168
|
+
# Detected a changed .mlmodel file. However, since we do not have testing for these detections at
|
|
127
169
|
# this time, we will ignore this change.
|
|
128
170
|
updatedLookup = None
|
|
129
171
|
|
|
130
172
|
else:
|
|
131
|
-
raise Exception(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
raise Exception(
|
|
174
|
+
f"Detected a changed file in the lookups/ directory '{str(decoded_path)}'.\n"
|
|
175
|
+
"Only files ending in .csv, .yml, or .mlmodel are supported in this "
|
|
176
|
+
"directory. This file must be removed from the lookups/ directory."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
updatedLookup is not None
|
|
181
|
+
and updatedLookup not in updated_lookups
|
|
182
|
+
):
|
|
136
183
|
# It is possible that both the CSV and YML have been modified for the same lookup,
|
|
137
|
-
# and we do not want to add it twice.
|
|
184
|
+
# and we do not want to add it twice.
|
|
138
185
|
updated_lookups.add(updatedLookup)
|
|
139
186
|
|
|
140
187
|
else:
|
|
141
188
|
pass
|
|
142
|
-
#print(f"Ignore changes to file {decoded_path} since it is not a detection, macro, or lookup.")
|
|
189
|
+
# print(f"Ignore changes to file {decoded_path} since it is not a detection, macro, or lookup.")
|
|
143
190
|
else:
|
|
144
191
|
raise Exception(f"Unrecognized diff type {type(diff)}")
|
|
145
192
|
|
|
146
|
-
|
|
147
193
|
# If a detection has at least one dependency on changed content,
|
|
148
194
|
# then we must test it again
|
|
149
195
|
|
|
150
|
-
changed_macros_and_lookups_and_datasources:set[
|
|
151
|
-
|
|
196
|
+
changed_macros_and_lookups_and_datasources: set[Macro | Lookup | DataSource] = (
|
|
197
|
+
updated_macros.union(updated_lookups, updated_datasources)
|
|
198
|
+
)
|
|
199
|
+
|
|
152
200
|
for detection in self.director.detections:
|
|
153
201
|
if detection in updated_detections:
|
|
154
|
-
# we are already planning to test it, don't need
|
|
202
|
+
# we are already planning to test it, don't need
|
|
155
203
|
# to add it again
|
|
156
204
|
continue
|
|
157
205
|
|
|
158
206
|
for obj in changed_macros_and_lookups_and_datasources:
|
|
159
207
|
if obj in detection.get_content_dependencies():
|
|
160
|
-
|
|
161
|
-
|
|
208
|
+
updated_detections.add(detection)
|
|
209
|
+
break
|
|
162
210
|
|
|
163
|
-
#Print out the names of all modified/new content
|
|
164
|
-
modifiedAndNewContentString = "\n - ".join(
|
|
211
|
+
# Print out the names of all modified/new content
|
|
212
|
+
modifiedAndNewContentString = "\n - ".join(
|
|
213
|
+
sorted([d.name for d in updated_detections])
|
|
214
|
+
)
|
|
165
215
|
|
|
166
|
-
print(
|
|
216
|
+
print(
|
|
217
|
+
f"[{len(updated_detections)}] Pieces of modifed and new content (this may include experimental/deprecated/manual_test content):\n - {modifiedAndNewContentString}"
|
|
218
|
+
)
|
|
167
219
|
return sorted(list(updated_detections))
|
|
168
220
|
|
|
169
221
|
def getSelected(self, detectionFilenames: List[FilePath]) -> List[Detection]:
|
|
170
222
|
filepath_to_content_map: dict[FilePath, SecurityContentObject] = {
|
|
171
|
-
|
|
172
|
-
|
|
223
|
+
obj.file_path: obj
|
|
224
|
+
for (_, obj) in self.director.name_to_content_map.items()
|
|
225
|
+
if obj.file_path is not None
|
|
226
|
+
}
|
|
173
227
|
errors = []
|
|
174
228
|
detections: List[Detection] = []
|
|
175
229
|
for name in detectionFilenames:
|
|
176
230
|
obj = filepath_to_content_map.get(name, None)
|
|
177
231
|
if obj is None:
|
|
178
|
-
errors.append(
|
|
232
|
+
errors.append(
|
|
233
|
+
f"There is no detection file or security_content_object at '{name}'"
|
|
234
|
+
)
|
|
179
235
|
elif not isinstance(obj, Detection):
|
|
180
|
-
errors.append(
|
|
236
|
+
errors.append(
|
|
237
|
+
f"The security_content_object at '{name}' is of type '{type(obj).__name__}', NOT '{Detection.__name__}'"
|
|
238
|
+
)
|
|
181
239
|
else:
|
|
182
240
|
detections.append(obj)
|
|
183
241
|
|
|
184
242
|
if errors:
|
|
185
243
|
errorsString = "\n - ".join(errors)
|
|
186
|
-
raise Exception(
|
|
187
|
-
|
|
244
|
+
raise Exception(
|
|
245
|
+
f"The following errors were encountered while getting selected detections to test:\n - {errorsString}"
|
|
246
|
+
)
|
|
247
|
+
return detections
|