contentctl 4.4.6__py3-none-any.whl → 5.0.0a0__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/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 -79
- 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 +26 -45
- 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.6.dist-info → contentctl-5.0.0a0.dist-info}/METADATA +6 -5
- {contentctl-4.4.6.dist-info → contentctl-5.0.0a0.dist-info}/RECORD +65 -68
- {contentctl-4.4.6.dist-info → contentctl-5.0.0a0.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.6.dist-info → contentctl-5.0.0a0.dist-info}/LICENSE.md +0 -0
- {contentctl-4.4.6.dist-info → contentctl-5.0.0a0.dist-info}/entry_points.txt +0 -0
contentctl/actions/test.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import List
|
|
3
3
|
|
|
4
|
-
from contentctl.objects.config import test_common
|
|
4
|
+
from contentctl.objects.config import test_common, Selected, Changes
|
|
5
5
|
from contentctl.objects.enums import DetectionTestingMode, DetectionStatus, AnalyticsType
|
|
6
6
|
from contentctl.objects.detection import Detection
|
|
7
7
|
|
|
@@ -78,10 +78,9 @@ class Test:
|
|
|
78
78
|
input_dto=manager_input_dto, output_dto=output_dto
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
-
mode = input_dto.config.getModeName()
|
|
82
81
|
if len(input_dto.detections) == 0:
|
|
83
82
|
print(
|
|
84
|
-
f"With Detection Testing Mode '{mode}', there were [0] detections found to test."
|
|
83
|
+
f"With Detection Testing Mode '{input_dto.config.mode.mode_name}', there were [0] detections found to test."
|
|
85
84
|
"\nAs such, we will quit immediately."
|
|
86
85
|
)
|
|
87
86
|
# Directly call stop so that the summary.yml will be generated. Of course it will not
|
|
@@ -89,8 +88,8 @@ class Test:
|
|
|
89
88
|
# detections were tested.
|
|
90
89
|
file.stop()
|
|
91
90
|
else:
|
|
92
|
-
print(f"MODE: [{mode}] - Test [{len(input_dto.detections)}] detections")
|
|
93
|
-
if mode
|
|
91
|
+
print(f"MODE: [{input_dto.config.mode.mode_name}] - Test [{len(input_dto.detections)}] detections")
|
|
92
|
+
if isinstance(input_dto.config.mode, Selected) or isinstance(input_dto.config.mode, Changes):
|
|
94
93
|
files_string = '\n- '.join(
|
|
95
94
|
[str(pathlib.Path(detection.file_path).relative_to(input_dto.config.path)) for detection in input_dto.detections]
|
|
96
95
|
)
|
contentctl/actions/validate.py
CHANGED
|
@@ -6,6 +6,7 @@ from contentctl.objects.config import validate
|
|
|
6
6
|
from contentctl.enrichments.attack_enrichment import AttackEnrichment
|
|
7
7
|
from contentctl.enrichments.cve_enrichment import CveEnrichment
|
|
8
8
|
from contentctl.objects.atomic import AtomicEnrichment
|
|
9
|
+
from contentctl.objects.lookup import FileBackedLookup
|
|
9
10
|
from contentctl.helper.utils import Utils
|
|
10
11
|
from contentctl.objects.data_source import DataSource
|
|
11
12
|
from contentctl.helper.splunk_app import SplunkApp
|
|
@@ -64,7 +65,7 @@ class Validate:
|
|
|
64
65
|
lookupsDirectory = repo_path/"lookups"
|
|
65
66
|
|
|
66
67
|
# Get all of the files referneced by Lookups
|
|
67
|
-
usedLookupFiles:list[pathlib.Path] = [lookup.filename for lookup in director_output_dto.lookups if lookup
|
|
68
|
+
usedLookupFiles:list[pathlib.Path] = [lookup.filename for lookup in director_output_dto.lookups if isinstance(lookup, FileBackedLookup)] + [lookup.file_path for lookup in director_output_dto.lookups if lookup.file_path is not None]
|
|
68
69
|
|
|
69
70
|
# Get all of the mlmodel and csv files in the lookups directory
|
|
70
71
|
csvAndMlmodelFiles = Utils.get_security_content_files_from_directory(lookupsDirectory, allowedFileExtensions=[".yml",".csv",".mlmodel"], fileExtensionsToReturn=[".csv",".mlmodel"])
|
contentctl/contentctl.py
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pathlib
|
|
2
2
|
import sys
|
|
3
|
+
import traceback
|
|
3
4
|
import warnings
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
import tyro
|
|
6
7
|
|
|
7
|
-
from contentctl.actions.
|
|
8
|
-
from contentctl.
|
|
9
|
-
from contentctl.actions.validate import Validate
|
|
10
|
-
from contentctl.actions.new_content import NewContent
|
|
8
|
+
from contentctl.actions.build import Build, BuildInputDto, DirectorOutputDto
|
|
9
|
+
from contentctl.actions.deploy_acs import Deploy
|
|
11
10
|
from contentctl.actions.detection_testing.GitService import GitService
|
|
12
|
-
from contentctl.actions.
|
|
13
|
-
BuildInputDto,
|
|
14
|
-
DirectorOutputDto,
|
|
15
|
-
Build,
|
|
16
|
-
)
|
|
17
|
-
from contentctl.actions.test import Test
|
|
18
|
-
from contentctl.actions.test import TestInputDto
|
|
19
|
-
from contentctl.actions.reporting import ReportingInputDto, Reporting
|
|
11
|
+
from contentctl.actions.initialize import Initialize
|
|
20
12
|
from contentctl.actions.inspect import Inspect
|
|
21
|
-
from contentctl.
|
|
22
|
-
from contentctl.actions.deploy_acs import Deploy
|
|
13
|
+
from contentctl.actions.new_content import NewContent
|
|
23
14
|
from contentctl.actions.release_notes import ReleaseNotes
|
|
15
|
+
from contentctl.actions.reporting import Reporting, ReportingInputDto
|
|
16
|
+
from contentctl.actions.test import Test, TestInputDto
|
|
17
|
+
from contentctl.actions.validate import Validate
|
|
18
|
+
from contentctl.input.yml_reader import YmlReader
|
|
19
|
+
from contentctl.objects.config import (
|
|
20
|
+
build,
|
|
21
|
+
deploy_acs,
|
|
22
|
+
init,
|
|
23
|
+
inspect,
|
|
24
|
+
new,
|
|
25
|
+
release_notes,
|
|
26
|
+
report,
|
|
27
|
+
test,
|
|
28
|
+
test_common,
|
|
29
|
+
test_servers,
|
|
30
|
+
validate,
|
|
31
|
+
)
|
|
24
32
|
|
|
25
33
|
# def print_ascii_art():
|
|
26
34
|
# print(
|
|
27
35
|
# """
|
|
28
|
-
# Running Splunk Security Content Control Tool (contentctl)
|
|
36
|
+
# Running Splunk Security Content Control Tool (contentctl)
|
|
29
37
|
# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
30
38
|
# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢶⠛⡇⠀⠀⠀⠀⠀⠀⣠⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
31
39
|
# ⠀⠀⠀⠀⠀⠀⠀⠀⣀⠼⠖⠛⠋⠉⠉⠓⠢⣴⡻⣾⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
@@ -53,114 +61,137 @@ from contentctl.actions.release_notes import ReleaseNotes
|
|
|
53
61
|
# )
|
|
54
62
|
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def init_func(config:test):
|
|
64
|
+
def init_func(config: test):
|
|
59
65
|
Initialize().execute(config)
|
|
60
66
|
|
|
61
67
|
|
|
62
|
-
def validate_func(config:validate)->DirectorOutputDto:
|
|
68
|
+
def validate_func(config: validate) -> DirectorOutputDto:
|
|
63
69
|
validate = Validate()
|
|
64
70
|
return validate.execute(config)
|
|
65
71
|
|
|
66
|
-
|
|
72
|
+
|
|
73
|
+
def report_func(config: report) -> None:
|
|
67
74
|
# First, perform validation. Remember that the validate
|
|
68
75
|
# configuration is actually a subset of the build configuration
|
|
69
76
|
director_output_dto = validate_func(config)
|
|
70
|
-
|
|
71
|
-
r = Reporting()
|
|
72
|
-
return r.execute(ReportingInputDto(director_output_dto=director_output_dto,
|
|
73
|
-
config=config))
|
|
74
|
-
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
r = Reporting()
|
|
79
|
+
return r.execute(
|
|
80
|
+
ReportingInputDto(director_output_dto=director_output_dto, config=config)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_func(config: build) -> DirectorOutputDto:
|
|
77
85
|
# First, perform validation. Remember that the validate
|
|
78
86
|
# configuration is actually a subset of the build configuration
|
|
79
87
|
director_output_dto = validate_func(config)
|
|
80
88
|
builder = Build()
|
|
81
89
|
return builder.execute(BuildInputDto(director_output_dto, config))
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
|
|
92
|
+
def inspect_func(config: inspect) -> str:
|
|
93
|
+
# Make sure that we have built the most recent version of the app
|
|
85
94
|
_ = build_func(config)
|
|
86
95
|
inspect_token = Inspect().execute(config)
|
|
87
96
|
return inspect_token
|
|
88
|
-
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
|
|
99
|
+
def release_notes_func(config: release_notes) -> None:
|
|
91
100
|
ReleaseNotes().release_notes(config)
|
|
92
101
|
|
|
93
|
-
def new_func(config:new):
|
|
94
|
-
NewContent().execute(config)
|
|
95
102
|
|
|
103
|
+
def new_func(config: new):
|
|
104
|
+
NewContent().execute(config)
|
|
96
105
|
|
|
97
106
|
|
|
98
|
-
def deploy_acs_func(config:deploy_acs):
|
|
107
|
+
def deploy_acs_func(config: deploy_acs):
|
|
99
108
|
print("Building and inspecting app...")
|
|
100
109
|
token = inspect_func(config)
|
|
101
110
|
print("App successfully built and inspected.")
|
|
102
111
|
print("Deploying app...")
|
|
103
112
|
Deploy().execute(config, token)
|
|
104
113
|
|
|
105
|
-
|
|
114
|
+
|
|
115
|
+
def test_common_func(config: test_common):
|
|
106
116
|
if type(config) == test:
|
|
107
|
-
#construct the container Infrastructure objects
|
|
117
|
+
# construct the container Infrastructure objects
|
|
108
118
|
config.getContainerInfrastructureObjects()
|
|
109
|
-
#otherwise, they have already been passed as servers
|
|
119
|
+
# otherwise, they have already been passed as servers
|
|
110
120
|
|
|
111
121
|
director_output_dto = build_func(config)
|
|
112
|
-
gitServer = GitService(director=director_output_dto,config=config)
|
|
122
|
+
gitServer = GitService(director=director_output_dto, config=config)
|
|
113
123
|
detections_to_test = gitServer.getContent()
|
|
114
124
|
|
|
115
|
-
|
|
116
|
-
|
|
117
125
|
test_input_dto = TestInputDto(detections_to_test, config)
|
|
118
|
-
|
|
126
|
+
|
|
119
127
|
t = Test()
|
|
120
128
|
t.filter_tests(test_input_dto)
|
|
121
|
-
|
|
129
|
+
|
|
122
130
|
if config.plan_only:
|
|
123
|
-
#Emit the test plan and quit. Do not actually run the test
|
|
124
|
-
config.dumpCICDPlanAndQuit(gitServer.getHash(),test_input_dto.detections)
|
|
125
|
-
return
|
|
126
|
-
|
|
131
|
+
# Emit the test plan and quit. Do not actually run the test
|
|
132
|
+
config.dumpCICDPlanAndQuit(gitServer.getHash(), test_input_dto.detections)
|
|
133
|
+
return
|
|
134
|
+
|
|
127
135
|
success = t.execute(test_input_dto)
|
|
128
|
-
|
|
136
|
+
|
|
129
137
|
if success:
|
|
130
|
-
#Everything passed!
|
|
138
|
+
# Everything passed!
|
|
131
139
|
print("All tests have run successfully or been marked as 'skipped'")
|
|
132
140
|
return
|
|
133
141
|
raise Exception("There was at least one unsuccessful test")
|
|
134
142
|
|
|
143
|
+
|
|
144
|
+
CONTENTCTL_5_WARNING = """
|
|
145
|
+
*****************************************************************************
|
|
146
|
+
WARNING - THIS IS AN ALPHA BUILD OF CONTENTCTL 5.
|
|
147
|
+
THERE HAVE BEEN NUMEROUS CHANGES IN CONTENTCTL (ESPECIALLY TO YML FORMATS).
|
|
148
|
+
YOU ALMOST CERTAINLY DO NOT WANT TO USE THIS BUILD.
|
|
149
|
+
IF YOU ENCOUNTER ERRORS, PLEASE USE THE LATEST CURRENTYLY SUPPORTED RELEASE:
|
|
150
|
+
|
|
151
|
+
CONTENTCTL==4.4.7
|
|
152
|
+
|
|
153
|
+
YOU HAVE BEEN WARNED!
|
|
154
|
+
*****************************************************************************
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
|
|
135
158
|
def main():
|
|
159
|
+
print(CONTENTCTL_5_WARNING)
|
|
136
160
|
try:
|
|
137
161
|
configFile = pathlib.Path("contentctl.yml")
|
|
138
|
-
|
|
162
|
+
|
|
139
163
|
# We MUST load a config (with testing info) object so that we can
|
|
140
164
|
# properly construct the command line, including 'contentctl test' parameters.
|
|
141
165
|
if not configFile.is_file():
|
|
142
|
-
if
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
if (
|
|
167
|
+
"init" not in sys.argv
|
|
168
|
+
and "--help" not in sys.argv
|
|
169
|
+
and "-h" not in sys.argv
|
|
170
|
+
):
|
|
171
|
+
raise Exception(
|
|
172
|
+
f"'{configFile}' not found in the current directory.\n"
|
|
173
|
+
"Please ensure you are in the correct directory or run 'contentctl init' to create a new content pack."
|
|
174
|
+
)
|
|
175
|
+
|
|
146
176
|
if "--help" in sys.argv or "-h" in sys.argv:
|
|
147
|
-
print(
|
|
148
|
-
|
|
177
|
+
print(
|
|
178
|
+
"Warning - contentctl.yml is missing from this directory. The configuration values showed at the default and are informational only.\n"
|
|
179
|
+
"Please ensure that contentctl.yml exists by manually creating it or running 'contentctl init'"
|
|
180
|
+
)
|
|
149
181
|
# Otherwise generate a stub config file.
|
|
150
182
|
# It will be used during init workflow
|
|
151
183
|
|
|
152
184
|
t = test()
|
|
153
185
|
config_obj = t.model_dump()
|
|
154
|
-
|
|
186
|
+
|
|
155
187
|
else:
|
|
156
|
-
#The file exists, so load it up!
|
|
157
|
-
config_obj = YmlReader().load_file(configFile)
|
|
188
|
+
# The file exists, so load it up!
|
|
189
|
+
config_obj = YmlReader().load_file(configFile, add_fields=False)
|
|
158
190
|
t = test.model_validate(config_obj)
|
|
159
191
|
except Exception as e:
|
|
160
192
|
print(f"Error validating 'contentctl.yml':\n{str(e)}")
|
|
161
193
|
sys.exit(1)
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
|
|
164
195
|
# For ease of generating the constructor, we want to allow construction
|
|
165
196
|
# of an object from default values WITHOUT requiring all fields to be declared
|
|
166
197
|
# with defaults OR in the config file. As such, we construct the model rather
|
|
@@ -169,22 +200,19 @@ def main():
|
|
|
169
200
|
|
|
170
201
|
models = tyro.extras.subcommand_type_from_defaults(
|
|
171
202
|
{
|
|
172
|
-
"init":init.model_validate(config_obj),
|
|
203
|
+
"init": init.model_validate(config_obj),
|
|
173
204
|
"validate": validate.model_validate(config_obj),
|
|
174
205
|
"report": report.model_validate(config_obj),
|
|
175
|
-
"build":build.model_validate(config_obj),
|
|
206
|
+
"build": build.model_validate(config_obj),
|
|
176
207
|
"inspect": inspect.model_construct(**t.__dict__),
|
|
177
|
-
"new":new.model_validate(config_obj),
|
|
178
|
-
"test":test.model_validate(config_obj),
|
|
179
|
-
"test_servers":test_servers.model_construct(**t.__dict__),
|
|
208
|
+
"new": new.model_validate(config_obj),
|
|
209
|
+
"test": test.model_validate(config_obj),
|
|
210
|
+
"test_servers": test_servers.model_construct(**t.__dict__),
|
|
180
211
|
"release_notes": release_notes.model_construct(**config_obj),
|
|
181
|
-
"deploy_acs": deploy_acs.model_construct(**t.__dict__)
|
|
212
|
+
"deploy_acs": deploy_acs.model_construct(**t.__dict__),
|
|
182
213
|
}
|
|
183
214
|
)
|
|
184
|
-
|
|
185
|
-
|
|
186
215
|
|
|
187
|
-
|
|
188
216
|
config = None
|
|
189
217
|
try:
|
|
190
218
|
# Since some model(s) were constructed and not model_validated, we have to catch
|
|
@@ -192,7 +220,6 @@ def main():
|
|
|
192
220
|
with warnings.catch_warnings(action="ignore"):
|
|
193
221
|
config = tyro.cli(models)
|
|
194
222
|
|
|
195
|
-
|
|
196
223
|
if type(config) == init:
|
|
197
224
|
t.__dict__.update(config.__dict__)
|
|
198
225
|
init_func(t)
|
|
@@ -219,21 +246,29 @@ def main():
|
|
|
219
246
|
print(e)
|
|
220
247
|
sys.exit(1)
|
|
221
248
|
except Exception as e:
|
|
249
|
+
print(CONTENTCTL_5_WARNING)
|
|
250
|
+
|
|
222
251
|
if config is None:
|
|
223
|
-
print(
|
|
224
|
-
|
|
252
|
+
print(
|
|
253
|
+
"There was a serious issue where the config file could not be created.\n"
|
|
254
|
+
"The entire stack trace is provided below (please include it if filing a bug report).\n"
|
|
255
|
+
)
|
|
225
256
|
traceback.print_exc()
|
|
226
257
|
elif config.verbose:
|
|
227
|
-
print(
|
|
228
|
-
|
|
258
|
+
print(
|
|
259
|
+
"Verbose error logging is ENABLED.\n"
|
|
260
|
+
"The entire stack trace has been provided below (please include it if filing a bug report):\n"
|
|
261
|
+
)
|
|
229
262
|
traceback.print_exc()
|
|
230
263
|
else:
|
|
231
|
-
print(
|
|
232
|
-
|
|
264
|
+
print(
|
|
265
|
+
"Verbose error logging is DISABLED.\n"
|
|
266
|
+
"Please use the --verbose command line argument if you need more context for your error or file a bug report."
|
|
267
|
+
)
|
|
233
268
|
print(e)
|
|
234
|
-
|
|
269
|
+
|
|
235
270
|
sys.exit(1)
|
|
236
271
|
|
|
237
272
|
|
|
238
273
|
if __name__ == "__main__":
|
|
239
|
-
main()
|
|
274
|
+
main()
|
contentctl/helper/utils.py
CHANGED
|
@@ -247,20 +247,6 @@ class Utils:
|
|
|
247
247
|
|
|
248
248
|
return hash
|
|
249
249
|
|
|
250
|
-
# @staticmethod
|
|
251
|
-
# def check_required_fields(
|
|
252
|
-
# thisField: str, definedFields: dict, requiredFields: list[str]
|
|
253
|
-
# ):
|
|
254
|
-
# missing_fields = [
|
|
255
|
-
# field for field in requiredFields if field not in definedFields
|
|
256
|
-
# ]
|
|
257
|
-
# if len(missing_fields) > 0:
|
|
258
|
-
# raise (
|
|
259
|
-
# ValueError(
|
|
260
|
-
# f"Could not validate - please resolve other errors resulting in missing fields {missing_fields}"
|
|
261
|
-
# )
|
|
262
|
-
# )
|
|
263
|
-
|
|
264
250
|
@staticmethod
|
|
265
251
|
def verify_file_exists(
|
|
266
252
|
file_path: str, verbose_print=False, timeout_seconds: int = 10
|
contentctl/input/director.py
CHANGED
|
@@ -14,7 +14,7 @@ from contentctl.objects.investigation import Investigation
|
|
|
14
14
|
from contentctl.objects.playbook import Playbook
|
|
15
15
|
from contentctl.objects.deployment import Deployment
|
|
16
16
|
from contentctl.objects.macro import Macro
|
|
17
|
-
from contentctl.objects.lookup import Lookup
|
|
17
|
+
from contentctl.objects.lookup import LookupAdapter, Lookup
|
|
18
18
|
from contentctl.objects.atomic import AtomicEnrichment
|
|
19
19
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
20
20
|
from contentctl.objects.data_source import DataSource
|
|
@@ -58,13 +58,12 @@ class DirectorOutputDto:
|
|
|
58
58
|
f" - {content.file_path}\n"
|
|
59
59
|
f" - {self.name_to_content_map[content_name].file_path}"
|
|
60
60
|
)
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
if content.id in self.uuid_to_content_map:
|
|
63
63
|
raise ValueError(
|
|
64
64
|
f"Duplicate id '{content.id}' with paths:\n"
|
|
65
65
|
f" - {content.file_path}\n"
|
|
66
|
-
f" - {self.uuid_to_content_map[content.id].file_path}"
|
|
67
|
-
)
|
|
66
|
+
f" - {self.uuid_to_content_map[content.id].file_path}")
|
|
68
67
|
|
|
69
68
|
if isinstance(content, Lookup):
|
|
70
69
|
self.lookups.append(content)
|
|
@@ -157,7 +156,8 @@ class Director():
|
|
|
157
156
|
modelDict = YmlReader.load_file(file)
|
|
158
157
|
|
|
159
158
|
if contentType == SecurityContentType.lookups:
|
|
160
|
-
lookup =
|
|
159
|
+
lookup = LookupAdapter.validate_python(modelDict, context={"output_dto":self.output_dto, "config":self.input_dto})
|
|
160
|
+
#lookup = Lookup.model_validate(modelDict, context={"output_dto":self.output_dto, "config":self.input_dto})
|
|
161
161
|
self.output_dto.addContentToDictMappings(lookup)
|
|
162
162
|
|
|
163
163
|
elif contentType == SecurityContentType.macros:
|
|
@@ -48,7 +48,7 @@ class NewContentQuestions:
|
|
|
48
48
|
{
|
|
49
49
|
'type': 'checkbox',
|
|
50
50
|
'message': 'Your data source',
|
|
51
|
-
'name': '
|
|
51
|
+
'name': 'data_sources',
|
|
52
52
|
#In the future, we should dynamically populate this from the DataSource Objects we have parsed from the data_sources directory
|
|
53
53
|
'choices': sorted(DataSource._value2member_map_ )
|
|
54
54
|
|
|
@@ -57,7 +57,7 @@ class NewContentQuestions:
|
|
|
57
57
|
"type": "text",
|
|
58
58
|
"message": "enter search (spl)",
|
|
59
59
|
"name": "detection_search",
|
|
60
|
-
"default": "|
|
|
60
|
+
"default": "| __UPDATE__ SPL",
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"type": "text",
|
contentctl/input/yml_reader.py
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
from typing import Dict, Any
|
|
2
|
-
|
|
3
2
|
import yaml
|
|
4
|
-
|
|
5
|
-
|
|
6
3
|
import sys
|
|
7
4
|
import pathlib
|
|
8
5
|
|
|
9
6
|
class YmlReader():
|
|
10
7
|
|
|
11
8
|
@staticmethod
|
|
12
|
-
def load_file(file_path: pathlib.Path, add_fields=True, STRICT_YML_CHECKING=False) -> Dict[str,Any]:
|
|
9
|
+
def load_file(file_path: pathlib.Path, add_fields:bool=True, STRICT_YML_CHECKING:bool=False) -> Dict[str,Any]:
|
|
13
10
|
try:
|
|
14
11
|
file_handler = open(file_path, 'r', encoding="utf-8")
|
|
15
12
|
|
|
@@ -27,8 +24,16 @@ class YmlReader():
|
|
|
27
24
|
print(f"Error loading YML file {file_path}: {str(e)}")
|
|
28
25
|
sys.exit(1)
|
|
29
26
|
try:
|
|
30
|
-
#
|
|
31
|
-
|
|
27
|
+
#Ideally we should use
|
|
28
|
+
# from contentctl.actions.new_content import NewContent
|
|
29
|
+
# and use NewContent.UPDATE_PREFIX,
|
|
30
|
+
# but there is a circular dependency right now which makes that difficult.
|
|
31
|
+
# We have instead hardcoded UPDATE_PREFIX
|
|
32
|
+
UPDATE_PREFIX = "__UPDATE__"
|
|
33
|
+
data = file_handler.read()
|
|
34
|
+
if UPDATE_PREFIX in data:
|
|
35
|
+
raise Exception(f"The file {file_path} contains the value '{UPDATE_PREFIX}'. Please fill out any unpopulated fields as required.")
|
|
36
|
+
yml_obj = yaml.load(data, Loader=yaml.CSafeLoader)
|
|
32
37
|
except yaml.YAMLError as exc:
|
|
33
38
|
print(exc)
|
|
34
39
|
sys.exit(1)
|