contentctl 5.5.8__py3-none-any.whl → 5.5.10__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 +1 -0
- contentctl/objects/content_versioning_service.py +185 -31
- contentctl/objects/correlation_search.py +2 -2
- contentctl/objects/playbook_tags.py +2 -0
- contentctl/output/conf_output.py +61 -0
- contentctl/output/templates/savedsearches_fbds.j2 +9 -0
- {contentctl-5.5.8.dist-info → contentctl-5.5.10.dist-info}/METADATA +3 -2
- {contentctl-5.5.8.dist-info → contentctl-5.5.10.dist-info}/RECORD +11 -10
- {contentctl-5.5.8.dist-info → contentctl-5.5.10.dist-info}/WHEEL +1 -1
- {contentctl-5.5.8.dist-info → contentctl-5.5.10.dist-info}/entry_points.txt +0 -0
- {contentctl-5.5.8.dist-info → contentctl-5.5.10.dist-info/licenses}/LICENSE.md +0 -0
contentctl/actions/build.py
CHANGED
|
@@ -30,6 +30,7 @@ class Build:
|
|
|
30
30
|
updated_conf_files.update(
|
|
31
31
|
conf_output.writeDetections(input_dto.director_output_dto.detections)
|
|
32
32
|
)
|
|
33
|
+
updated_conf_files.update(conf_output.writeFbds())
|
|
33
34
|
updated_conf_files.update(
|
|
34
35
|
conf_output.writeStories(input_dto.director_output_dto.stories)
|
|
35
36
|
)
|
|
@@ -7,7 +7,14 @@ from functools import cached_property
|
|
|
7
7
|
from typing import Any, Callable
|
|
8
8
|
|
|
9
9
|
import splunklib.client as splunklib # type: ignore
|
|
10
|
-
from pydantic import
|
|
10
|
+
from pydantic import (
|
|
11
|
+
BaseModel,
|
|
12
|
+
Field,
|
|
13
|
+
PrivateAttr,
|
|
14
|
+
computed_field,
|
|
15
|
+
model_validator,
|
|
16
|
+
)
|
|
17
|
+
from semantic_version import Version
|
|
11
18
|
from splunklib.binding import HTTPError, ResponseReader # type: ignore
|
|
12
19
|
from splunklib.data import Record # type: ignore
|
|
13
20
|
|
|
@@ -21,6 +28,58 @@ ENABLE_LOGGING = False
|
|
|
21
28
|
LOG_LEVEL = logging.DEBUG
|
|
22
29
|
LOG_PATH = "content_versioning_service.log"
|
|
23
30
|
|
|
31
|
+
# The app name of ES; needed to check ES version
|
|
32
|
+
ES_APP_NAME = "SplunkEnterpriseSecuritySuite"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CMSEvent(BaseModel):
|
|
36
|
+
"""
|
|
37
|
+
A model representing a CMS event. This is used to validate that detections have been installed
|
|
38
|
+
in a way that is compatible with content versioning.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
content: str # JSON string
|
|
42
|
+
|
|
43
|
+
# The app name of the detection
|
|
44
|
+
app_name: str
|
|
45
|
+
|
|
46
|
+
# The detection id of the detection
|
|
47
|
+
detection_id: str
|
|
48
|
+
|
|
49
|
+
# The version of the detection
|
|
50
|
+
version: str
|
|
51
|
+
|
|
52
|
+
# The saved search name of the detection
|
|
53
|
+
action_correlationsearch_label: str
|
|
54
|
+
|
|
55
|
+
@model_validator(mode="before")
|
|
56
|
+
@classmethod
|
|
57
|
+
def extract_from_content(cls, data):
|
|
58
|
+
"""Extract fields from content JSON if not already provided"""
|
|
59
|
+
if isinstance(data, dict) and "content" in data:
|
|
60
|
+
try:
|
|
61
|
+
content_str = data.get("content")
|
|
62
|
+
parsed = json.loads(content_str)
|
|
63
|
+
|
|
64
|
+
# Extract metadata fields - note the key has dots in it
|
|
65
|
+
metadata_str = parsed.get("action.correlationsearch.metadata", {})
|
|
66
|
+
metadata = (
|
|
67
|
+
json.loads(metadata_str)
|
|
68
|
+
if isinstance(metadata_str, str)
|
|
69
|
+
else metadata_str
|
|
70
|
+
)
|
|
71
|
+
data.setdefault("app_name", metadata.get("app_name"))
|
|
72
|
+
data.setdefault("detection_id", metadata.get("detection_id"))
|
|
73
|
+
data.setdefault("version", metadata.get("version"))
|
|
74
|
+
data.setdefault(
|
|
75
|
+
"action_correlationsearch_label",
|
|
76
|
+
parsed.get("action.correlationsearch.label"),
|
|
77
|
+
)
|
|
78
|
+
except (json.JSONDecodeError, AttributeError, KeyError, TypeError):
|
|
79
|
+
# If parsing fails, let Pydantic handle validation errors
|
|
80
|
+
raise ValueError("Failed to parse content JSON {}".format(data))
|
|
81
|
+
return data
|
|
82
|
+
|
|
24
83
|
|
|
25
84
|
class ContentVersioningService(BaseModel):
|
|
26
85
|
"""
|
|
@@ -77,6 +136,47 @@ class ContentVersioningService(BaseModel):
|
|
|
77
136
|
(self.validate_content_against_cms, "Validating Against CMS"),
|
|
78
137
|
]
|
|
79
138
|
|
|
139
|
+
@cached_property
|
|
140
|
+
def es_version(self) -> Version | None:
|
|
141
|
+
"""
|
|
142
|
+
Returns the version of Enterprise Security installed on the instance; None if not installed.
|
|
143
|
+
|
|
144
|
+
:return: the version of ES, as a semver aware object
|
|
145
|
+
:rtype: :class:`semantic_version.Version`
|
|
146
|
+
"""
|
|
147
|
+
if ES_APP_NAME not in self.service.apps:
|
|
148
|
+
return None
|
|
149
|
+
return Version(self.service.apps[ES_APP_NAME]["version"]) # type: ignore
|
|
150
|
+
|
|
151
|
+
@cached_property
|
|
152
|
+
def kvstore_content_versioning(self) -> bool:
|
|
153
|
+
"""
|
|
154
|
+
Indicates whether we should test content versioning based on kvstore logic. Content versioning
|
|
155
|
+
should be tested with kvstore logic when ES is at least version 8.3.0.
|
|
156
|
+
|
|
157
|
+
:return: a bool indicating whether we should test content versioning with kvstore logic
|
|
158
|
+
:rtype: bool
|
|
159
|
+
"""
|
|
160
|
+
es_version = self.es_version
|
|
161
|
+
return es_version is not None and es_version >= Version("8.3.0")
|
|
162
|
+
|
|
163
|
+
@cached_property
|
|
164
|
+
def indexbased_content_versioning(self) -> bool:
|
|
165
|
+
"""
|
|
166
|
+
Indicates whether we should test content versioning based on indexbased logic. Content versioning
|
|
167
|
+
should be tested with indexbased logic when ES is less than version 8.3.0 but greater than or equal
|
|
168
|
+
to version 8.0.0.
|
|
169
|
+
|
|
170
|
+
:return: a bool indicating whether we should test content versioning with indexbased logic
|
|
171
|
+
:rtype: bool
|
|
172
|
+
"""
|
|
173
|
+
es_version = self.es_version
|
|
174
|
+
return (
|
|
175
|
+
es_version is not None
|
|
176
|
+
and es_version >= Version("8.0.0")
|
|
177
|
+
and es_version < Version("8.3.0")
|
|
178
|
+
)
|
|
179
|
+
|
|
80
180
|
def _query_content_versioning_service(
|
|
81
181
|
self, method: str, body: dict[str, Any] = {}
|
|
82
182
|
) -> Record:
|
|
@@ -97,6 +197,17 @@ class ContentVersioningService(BaseModel):
|
|
|
97
197
|
|
|
98
198
|
# Query the content versioning service
|
|
99
199
|
try:
|
|
200
|
+
# TODO: The comment out section is for validating versioning is enabled and ready to go. The validation
|
|
201
|
+
# workflow (whether to be part of wait_for_cms_main or a separate function) is planed to be implemented
|
|
202
|
+
# in later contentctl-ng.
|
|
203
|
+
# API endpoint for checking versioning status after ES 8.3.0
|
|
204
|
+
# if method == "GET" and self.kvstore_content_versioning:
|
|
205
|
+
# response = self.service.request(
|
|
206
|
+
# method=method,
|
|
207
|
+
# path_segment="content_versioning/versioning_apps",
|
|
208
|
+
# app="SA-ContentVersioning",
|
|
209
|
+
# )
|
|
210
|
+
# if self.indexbased_content_versioning:
|
|
100
211
|
response = self.service.request( # type: ignore
|
|
101
212
|
method=method,
|
|
102
213
|
path_segment="configs/conf-feature_flags/general",
|
|
@@ -141,6 +252,27 @@ class ContentVersioningService(BaseModel):
|
|
|
141
252
|
|
|
142
253
|
# Find the versioning_activated field and report any errors
|
|
143
254
|
try:
|
|
255
|
+
# TODO: The comment out section is for validating versioning is enabled and ready to go. The validation
|
|
256
|
+
# workflow (whether to be part of wait_for_cms_main or a separate function) is planed to be implemented
|
|
257
|
+
# in later contentctl-ng.
|
|
258
|
+
# Validating response by checking `status` field in `DA-ESS-ContentUpdate` app
|
|
259
|
+
# if self.kvstore_content_versioning:
|
|
260
|
+
# if "content" in data:
|
|
261
|
+
# for app in data["content"]:
|
|
262
|
+
# if app.get("name") == "DA-ESS-ContentUpdate":
|
|
263
|
+
# # If there is error message versioning is not activated properly
|
|
264
|
+
# if "message" in app:
|
|
265
|
+
# return False
|
|
266
|
+
|
|
267
|
+
# # If the installed verion is not the same as the test version
|
|
268
|
+
# if app.get("version") != self.global_config.app.version:
|
|
269
|
+
# return False
|
|
270
|
+
|
|
271
|
+
# if app.get("status") == "active":
|
|
272
|
+
# return True
|
|
273
|
+
# else:
|
|
274
|
+
# return False
|
|
275
|
+
# if self.indexbased_content_versioning:
|
|
144
276
|
for entry in data["entry"]:
|
|
145
277
|
if entry["name"] == "general":
|
|
146
278
|
return bool(int(entry["content"]["versioning_activated"]))
|
|
@@ -163,6 +295,18 @@ class ContentVersioningService(BaseModel):
|
|
|
163
295
|
method="POST", body={"versioning_activated": True}
|
|
164
296
|
)
|
|
165
297
|
|
|
298
|
+
# TODO: The comment out section is for validating versioning is enabled and ready to go. The validation
|
|
299
|
+
# workflow (whether to be part of wait_for_cms_main or a separate function) is planed to be implemented
|
|
300
|
+
# in later contentctl-ng.
|
|
301
|
+
# The versioning is expected to be ready within 10 minutes
|
|
302
|
+
# if self.kvstore_content_versioning:
|
|
303
|
+
# timeout = 600
|
|
304
|
+
# while not self.is_versioning_activated:
|
|
305
|
+
# time.sleep(60)
|
|
306
|
+
# timeout -= 60
|
|
307
|
+
# if timeout <= 0:
|
|
308
|
+
# break
|
|
309
|
+
|
|
166
310
|
# Confirm versioning has been enabled
|
|
167
311
|
if not self.is_versioning_activated:
|
|
168
312
|
raise Exception(
|
|
@@ -173,23 +317,6 @@ class ContentVersioningService(BaseModel):
|
|
|
173
317
|
f"[{self.infrastructure.instance_name}] Versioning service successfully activated"
|
|
174
318
|
)
|
|
175
319
|
|
|
176
|
-
@computed_field
|
|
177
|
-
@cached_property
|
|
178
|
-
def cms_fields(self) -> list[str]:
|
|
179
|
-
"""
|
|
180
|
-
Property listing the fields we want to pull from the cms_main index
|
|
181
|
-
|
|
182
|
-
:returns: a list of strings, the fields we want
|
|
183
|
-
:rtype: list[str]
|
|
184
|
-
"""
|
|
185
|
-
return [
|
|
186
|
-
"app_name",
|
|
187
|
-
"detection_id",
|
|
188
|
-
"version",
|
|
189
|
-
"action.correlationsearch.label",
|
|
190
|
-
"sourcetype",
|
|
191
|
-
]
|
|
192
|
-
|
|
193
320
|
@property
|
|
194
321
|
def is_cms_parser_enabled(self) -> bool:
|
|
195
322
|
"""
|
|
@@ -292,16 +419,37 @@ class ContentVersioningService(BaseModel):
|
|
|
292
419
|
)
|
|
293
420
|
|
|
294
421
|
# Construct the query looking for CMS events matching the content app name
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
422
|
+
if self.kvstore_content_versioning:
|
|
423
|
+
query = (
|
|
424
|
+
f"| inputlookup cms_content_lookup | search app_name={self.global_config.app.appid}"
|
|
425
|
+
f"| fields content"
|
|
426
|
+
)
|
|
427
|
+
elif self.indexbased_content_versioning:
|
|
428
|
+
query = (
|
|
429
|
+
f"search index=cms_main sourcetype=stash_common_detection_model "
|
|
430
|
+
f'app_name="{self.global_config.app.appid}" | fields _raw'
|
|
431
|
+
)
|
|
432
|
+
else:
|
|
433
|
+
if self.kvstore_content_versioning:
|
|
434
|
+
raise Exception(
|
|
435
|
+
f"Unable to perform search to cms_content_lookup in ES version {self.es_version}"
|
|
436
|
+
)
|
|
437
|
+
elif self.indexbased_content_versioning:
|
|
438
|
+
raise Exception(
|
|
439
|
+
f"Unable to perform search to cms_main index in ES version {self.es_version}"
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
raise Exception(
|
|
443
|
+
f"Unable to determine content versioning method for ES version {self.es_version}. "
|
|
444
|
+
"Expected ES version >= 8.0.0."
|
|
445
|
+
)
|
|
299
446
|
self.logger.debug(
|
|
300
447
|
f"[{self.infrastructure.instance_name}] Query on cms_main: {query}"
|
|
301
448
|
)
|
|
302
449
|
|
|
303
450
|
# Get the job as a blocking operation, set the cache, and return
|
|
304
451
|
self._cms_main_job = self.service.search(query, exec_mode="blocking") # type: ignore
|
|
452
|
+
|
|
305
453
|
return self._cms_main_job
|
|
306
454
|
|
|
307
455
|
def get_num_cms_events(self, use_cache: bool = False) -> int:
|
|
@@ -375,8 +523,13 @@ class ContentVersioningService(BaseModel):
|
|
|
375
523
|
# Increment the offset for each result
|
|
376
524
|
offset += 1
|
|
377
525
|
|
|
526
|
+
if self.kvstore_content_versioning:
|
|
527
|
+
cms_event = CMSEvent(content=cms_event["content"])
|
|
528
|
+
elif self.indexbased_content_versioning:
|
|
529
|
+
cms_event = CMSEvent(content=cms_event["_raw"])
|
|
530
|
+
|
|
378
531
|
# Get the name of the search in the CMS event
|
|
379
|
-
cms_entry_name = cms_event
|
|
532
|
+
cms_entry_name = cms_event.action_correlationsearch_label
|
|
380
533
|
self.logger.info(
|
|
381
534
|
f"[{self.infrastructure.instance_name}] {offset}: Matching cms_main entry "
|
|
382
535
|
f"'{cms_entry_name}' against detections"
|
|
@@ -456,14 +609,14 @@ class ContentVersioningService(BaseModel):
|
|
|
456
609
|
)
|
|
457
610
|
|
|
458
611
|
def validate_detection_against_cms_event(
|
|
459
|
-
self, cms_event:
|
|
612
|
+
self, cms_event: CMSEvent, detection: Detection
|
|
460
613
|
) -> Exception | None:
|
|
461
614
|
"""
|
|
462
615
|
Given an event from the cms_main index and the matched detection, compare fields and look
|
|
463
616
|
for any inconsistencies
|
|
464
617
|
|
|
465
618
|
:param cms_event: The event from the cms_main index
|
|
466
|
-
:type cms_event:
|
|
619
|
+
:type cms_event: CMSEvent
|
|
467
620
|
:param detection: The matched detection
|
|
468
621
|
:type detection: :class:`contentctl.objects.detection.Detection`
|
|
469
622
|
|
|
@@ -472,17 +625,18 @@ class ContentVersioningService(BaseModel):
|
|
|
472
625
|
"""
|
|
473
626
|
# TODO (PEX-509): validate additional fields between the cms_event and the detection
|
|
474
627
|
|
|
475
|
-
cms_uuid = uuid.UUID(cms_event
|
|
628
|
+
cms_uuid = uuid.UUID(cms_event.detection_id)
|
|
476
629
|
rule_name_from_detection = detection.get_action_dot_correlationsearch_dot_label(
|
|
477
630
|
self.global_config.app
|
|
478
631
|
)
|
|
479
632
|
|
|
633
|
+
cms_entry_name = cms_event.action_correlationsearch_label
|
|
634
|
+
|
|
480
635
|
# Compare the correlation search label
|
|
481
|
-
if
|
|
636
|
+
if cms_entry_name != rule_name_from_detection:
|
|
482
637
|
msg = (
|
|
483
638
|
f"[{self.infrastructure.instance_name}][{detection.name}]: Correlation search "
|
|
484
|
-
f"label in cms_event ('{
|
|
485
|
-
"match detection name"
|
|
639
|
+
f"label in cms_event ('{cms_entry_name}') does not match detection name"
|
|
486
640
|
)
|
|
487
641
|
self.logger.error(msg)
|
|
488
642
|
return Exception(msg)
|
|
@@ -494,12 +648,12 @@ class ContentVersioningService(BaseModel):
|
|
|
494
648
|
)
|
|
495
649
|
self.logger.error(msg)
|
|
496
650
|
return Exception(msg)
|
|
497
|
-
elif cms_event
|
|
651
|
+
elif cms_event.version != f"{detection.version}.1":
|
|
498
652
|
# Compare the versions (we append '.1' to the detection version to be in line w/ the
|
|
499
653
|
# internal representation in ES)
|
|
500
654
|
msg = (
|
|
501
655
|
f"[{self.infrastructure.instance_name}] [{detection.name}]: Version in cms_event "
|
|
502
|
-
f"('{cms_event
|
|
656
|
+
f"('{cms_event.version}') does not match version in detection "
|
|
503
657
|
f"('{detection.version}.1')"
|
|
504
658
|
)
|
|
505
659
|
self.logger.error(msg)
|
|
@@ -10,6 +10,7 @@ from contentctl.objects.detection import Detection
|
|
|
10
10
|
|
|
11
11
|
class PlaybookProduct(str, enum.Enum):
|
|
12
12
|
SPLUNK_SOAR = "Splunk SOAR"
|
|
13
|
+
SPLUNK_ES = "Splunk Enterprise Security"
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class PlaybookUseCase(str, enum.Enum):
|
|
@@ -25,6 +26,7 @@ class PlaybookUseCase(str, enum.Enum):
|
|
|
25
26
|
class PlaybookType(str, enum.Enum):
|
|
26
27
|
INPUT = "Input"
|
|
27
28
|
AUTOMATION = "Automation"
|
|
29
|
+
ES = "Enterprise Security"
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class VpeType(str, enum.Enum):
|
contentctl/output/conf_output.py
CHANGED
|
@@ -110,6 +110,67 @@ class ConfOutput:
|
|
|
110
110
|
)
|
|
111
111
|
return written_files
|
|
112
112
|
|
|
113
|
+
def writeFbds(self) -> set[pathlib.Path]:
|
|
114
|
+
written_files: set[pathlib.Path] = set()
|
|
115
|
+
|
|
116
|
+
# Path to the static FBD configuration file
|
|
117
|
+
fbd_config_path = self.config.path / "static_configs" / "savedsearches_fbd.conf"
|
|
118
|
+
|
|
119
|
+
if not fbd_config_path.exists():
|
|
120
|
+
# If the file doesn't exist, just return empty set - no FBDs to write
|
|
121
|
+
print("No FBD configuration file found; skipping FBD writing to conf.")
|
|
122
|
+
return written_files
|
|
123
|
+
print(f"Reading FBD configuration from {fbd_config_path}")
|
|
124
|
+
|
|
125
|
+
# Read and parse the FBD configuration file
|
|
126
|
+
fbd_stanzas = self._parse_fbd_config_file(fbd_config_path)
|
|
127
|
+
|
|
128
|
+
if fbd_stanzas: # Only write if there are actual stanzas
|
|
129
|
+
written_files.add(
|
|
130
|
+
ConfWriter.writeConfFile(
|
|
131
|
+
pathlib.Path("default/savedsearches.conf"),
|
|
132
|
+
"savedsearches_fbds.j2",
|
|
133
|
+
self.config,
|
|
134
|
+
fbd_stanzas,
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return written_files
|
|
139
|
+
|
|
140
|
+
def _parse_fbd_config_file(self, file_path: pathlib.Path) -> list:
|
|
141
|
+
"""Parse the FBD configuration file into individual stanza objects."""
|
|
142
|
+
stanzas = []
|
|
143
|
+
current_stanza_lines = []
|
|
144
|
+
|
|
145
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
146
|
+
for line in f:
|
|
147
|
+
stripped_line = line.strip()
|
|
148
|
+
|
|
149
|
+
# Skip comment lines (lines starting with # after stripping whitespace)
|
|
150
|
+
if stripped_line.startswith("#"):
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# If we hit a blank line and have accumulated stanza content, finalize the current stanza
|
|
154
|
+
if not stripped_line:
|
|
155
|
+
if current_stanza_lines:
|
|
156
|
+
stanza_content = "\n".join(current_stanza_lines)
|
|
157
|
+
stanza_obj = type(
|
|
158
|
+
"FbdStanza", (), {"content": stanza_content}
|
|
159
|
+
)()
|
|
160
|
+
stanzas.append(stanza_obj)
|
|
161
|
+
current_stanza_lines = []
|
|
162
|
+
continue
|
|
163
|
+
|
|
164
|
+
# Accumulate non-empty, non-comment lines for the current stanza
|
|
165
|
+
current_stanza_lines.append(line.rstrip())
|
|
166
|
+
# Handle the last stanza if the file doesn't end with a blank line
|
|
167
|
+
if current_stanza_lines:
|
|
168
|
+
stanza_content = "\n".join(current_stanza_lines)
|
|
169
|
+
stanza_obj = type("FbdStanza", (), {"content": stanza_content})()
|
|
170
|
+
stanzas.append(stanza_obj)
|
|
171
|
+
|
|
172
|
+
return stanzas
|
|
173
|
+
|
|
113
174
|
def writeStories(self, objects: list[Story]) -> set[pathlib.Path]:
|
|
114
175
|
written_files: set[pathlib.Path] = set()
|
|
115
176
|
written_files.add(
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: contentctl
|
|
3
|
-
Version: 5.5.
|
|
3
|
+
Version: 5.5.10
|
|
4
4
|
Summary: Splunk Content Control Tool
|
|
5
5
|
License: Apache 2.0
|
|
6
|
+
License-File: LICENSE.md
|
|
6
7
|
Author: STRT
|
|
7
8
|
Author-email: research@splunk.com
|
|
8
9
|
Requires-Python: >=3.11,<3.14
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
contentctl/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
-
contentctl/actions/build.py,sha256=
|
|
2
|
+
contentctl/actions/build.py,sha256=q4TakZm1Kcurvr9yIrZ54VxNOmNSgJoVK5I8q1Iu2Qk,4099
|
|
3
3
|
contentctl/actions/deploy_acs.py,sha256=TtDIrpZm3mMNpbs8yaritnS-kLPnOtJ2T2M9r2QwKL0,3263
|
|
4
4
|
contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=zdJM_4LP31PlexLdoaKLf_XBrVFoGBwpVm566uAO1d4,10965
|
|
5
5
|
contentctl/actions/detection_testing/GitService.py,sha256=sp2ZyV_gpyTjonh48obNzbCC1LDni8F2nN7ZPn2LqnY,11333
|
|
@@ -44,8 +44,8 @@ contentctl/objects/baseline.py,sha256=EMcuz_9sVgOFh3YCj871GSAA6v3FIkRTf90-LAHq-J
|
|
|
44
44
|
contentctl/objects/baseline_tags.py,sha256=SkGlsfigaARss3itHOgWnKhRDEB6NX8bMhfovrBUmhk,1609
|
|
45
45
|
contentctl/objects/config.py,sha256=la0mUk1183ZD0gav7bGekhJxj4AOjn8hF5p7jwNqdhM,57938
|
|
46
46
|
contentctl/objects/constants.py,sha256=VwwQtJBGC_zb3ukjb3A7P0CwAlyhacWiXczwAW5Jiog,5466
|
|
47
|
-
contentctl/objects/content_versioning_service.py,sha256=
|
|
48
|
-
contentctl/objects/correlation_search.py,sha256=
|
|
47
|
+
contentctl/objects/content_versioning_service.py,sha256=KZs0DRo5AX6TzVIUDvlTiYKaKB6nZqx1WPko9-JLqP0,27341
|
|
48
|
+
contentctl/objects/correlation_search.py,sha256=cTFpdcBXmQ9AhOkNK2EK4xOafsIRktsJKNVy4e1WAns,51056
|
|
49
49
|
contentctl/objects/dashboard.py,sha256=wdNCIC1MExvpsB_EyPY9ZDo9Xu9V5WDI6wkunW0fTdk,4995
|
|
50
50
|
contentctl/objects/data_source.py,sha256=O58GArXVlflz3dCtVOn96Ubyi5_ekSC1N9LuveQNws4,2019
|
|
51
51
|
contentctl/objects/deployment.py,sha256=OctNayxFPRvrQtTklAKgfjCXFKOspD19swLj0hi6dWE,3323
|
|
@@ -74,7 +74,7 @@ contentctl/objects/mitre_attack_enrichment.py,sha256=PCakRksW5qrTENIZ7JirEZplE9x
|
|
|
74
74
|
contentctl/objects/notable_action.py,sha256=sW5XlpGznMHqyBmGXtXrl22hWLiCoKkfGCasGtK3rGo,1607
|
|
75
75
|
contentctl/objects/notable_event.py,sha256=jMmD1sGtTvOFNfjAfienWD2-sVL67axzdLrLZSGQ8Sw,421
|
|
76
76
|
contentctl/objects/playbook.py,sha256=veG2luPfFrOMdzl99D8gsO85HYSJ8kZMYWj3GG64HKk,2879
|
|
77
|
-
contentctl/objects/playbook_tags.py,sha256=
|
|
77
|
+
contentctl/objects/playbook_tags.py,sha256=0FCgRZKReNcY2Ek0Fl6OiHyq3G5QHQWn1NMcDw7ouZg,6856
|
|
78
78
|
contentctl/objects/rba.py,sha256=2xE_DXhQvG6tVLJTXYaFEBm9owePE4QG0NVgdcVgoiY,3547
|
|
79
79
|
contentctl/objects/removed_security_content_object.py,sha256=bx-gVCqzT81E5jKncMD3-yKawTnl3tWsuzRBmsAqeqQ,1852
|
|
80
80
|
contentctl/objects/risk_analysis_action.py,sha256=8hE8-0YdMXJslxTGNGUN0DAQONwOsyhf5vPP3whZQ2o,4370
|
|
@@ -94,7 +94,7 @@ contentctl/objects/unit_test_result.py,sha256=69VEAmajeRnWIdl6Xc90LU6hboEjJI9szF
|
|
|
94
94
|
contentctl/output/api_json_output.py,sha256=AwuXFVzg3bY0DUsYaEGM73LAr9mJ5nxkOmUdVJgTzRs,8563
|
|
95
95
|
contentctl/output/attack_nav_output.py,sha256=-zK9zxBFWQooLjfLeCJaKARemA1BhoiEYLYYT2Or9PQ,7088
|
|
96
96
|
contentctl/output/attack_nav_writer.py,sha256=AiQU3q8hzz_lJECI-sjyqOsWx64HUugg3aAHEeZl-qM,2750
|
|
97
|
-
contentctl/output/conf_output.py,sha256=
|
|
97
|
+
contentctl/output/conf_output.py,sha256=lWEGKETO-d59_UyNEMqvXiYi92Mm5zn37RzywLdQNJ8,13575
|
|
98
98
|
contentctl/output/conf_writer.py,sha256=3bo_VIFH3Qd96LLaYFJ1uwYQQW0UFPB0Rf3Bs9_N2W4,15110
|
|
99
99
|
contentctl/output/doc_md_output.py,sha256=jHOiuO8IWQ4adR0ekj-Af0JI9XhOGI-zYfKqV34f5Vw,3551
|
|
100
100
|
contentctl/output/jinja_writer.py,sha256=k3yVRnz8KnboodME09oTHkP_54uF-YUGiKuJHZ9XTxo,1205
|
|
@@ -125,6 +125,7 @@ contentctl/output/templates/macros.j2,sha256=SLcQQ5X7TZS8j-2qP06BTXqdIcnwoYqTAaB
|
|
|
125
125
|
contentctl/output/templates/panel.j2,sha256=Cw_W6p-14n6UivVfpS75KKJiJ2VpdGsSBceYsUYe9gk,221
|
|
126
126
|
contentctl/output/templates/savedsearches_baselines.j2,sha256=WHZB4e0vmeym8832VxRmuUfDJ-YRYt6emcYaJrghI58,1709
|
|
127
127
|
contentctl/output/templates/savedsearches_detections.j2,sha256=xb4G7ikTD89cK7FF2TxwuFO1t9AQ43-DmT2LaemUMP4,5326
|
|
128
|
+
contentctl/output/templates/savedsearches_fbds.j2,sha256=iYjuEESKzSVyornnBfcR_JPafHHAlff_G2LeXQRGMzc,132
|
|
128
129
|
contentctl/output/templates/savedsearches_investigations.j2,sha256=KH2r8SgyAMiettSHypSbA2-1XmQ_8_8xzk3BkbZ1Re4,1196
|
|
129
130
|
contentctl/output/templates/server.conf.j2,sha256=sPZUkiuJNGm9R8rpjfRKyuAvmmQb0C4w9Q6hpmvmPeU,127
|
|
130
131
|
contentctl/output/templates/transforms.j2,sha256=TEKZi8DWpcCysRTNvuLEgAwx-g1SZ2E0CkLiu6v6AlU,1339
|
|
@@ -164,8 +165,8 @@ contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
164
165
|
contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
|
|
165
166
|
contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
|
|
166
167
|
contentctl/templates/stories/cobalt_strike.yml,sha256=uj8idtDNOAIqpZ9p8usQg6mop1CQkJ5TlB4Q7CJdTIE,3082
|
|
167
|
-
contentctl-5.5.
|
|
168
|
-
contentctl-5.5.
|
|
169
|
-
contentctl-5.5.
|
|
170
|
-
contentctl-5.5.
|
|
171
|
-
contentctl-5.5.
|
|
168
|
+
contentctl-5.5.10.dist-info/METADATA,sha256=pYZtGw4_iHIvpFISklMVMbofedrIpfFmiXpvt3y0ei0,5144
|
|
169
|
+
contentctl-5.5.10.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
170
|
+
contentctl-5.5.10.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
|
|
171
|
+
contentctl-5.5.10.dist-info/licenses/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
|
|
172
|
+
contentctl-5.5.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|