contentctl 5.5.6__py3-none-any.whl → 5.5.8__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/deploy_acs.py +5 -3
- contentctl/actions/detection_testing/DetectionTestingManager.py +3 -3
- contentctl/actions/detection_testing/GitService.py +4 -4
- contentctl/actions/detection_testing/generate_detection_coverage_badge.py +3 -3
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +15 -17
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +9 -8
- contentctl/actions/detection_testing/progress_bar.py +2 -1
- contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +4 -3
- contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +4 -2
- contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +7 -7
- contentctl/actions/doc_gen.py +1 -2
- contentctl/actions/release_notes.py +2 -2
- contentctl/actions/reporting.py +3 -3
- contentctl/actions/test.py +2 -3
- contentctl/actions/validate.py +1 -1
- contentctl/api.py +7 -6
- contentctl/contentctl.py +1 -1
- contentctl/enrichments/attack_enrichment.py +1 -1
- contentctl/enrichments/cve_enrichment.py +9 -6
- contentctl/enrichments/splunk_app_enrichment.py +5 -4
- contentctl/helper/link_validator.py +7 -7
- contentctl/helper/splunk_app.py +6 -6
- contentctl/helper/utils.py +8 -8
- contentctl/input/director.py +3 -2
- contentctl/input/new_content_questions.py +1 -0
- contentctl/input/yml_reader.py +2 -2
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +1 -1
- contentctl/objects/alert_action.py +4 -2
- contentctl/objects/atomic.py +8 -5
- contentctl/objects/base_test.py +1 -1
- contentctl/objects/base_test_result.py +2 -2
- contentctl/objects/baseline_tags.py +7 -6
- contentctl/objects/config.py +5 -5
- contentctl/objects/correlation_search.py +156 -139
- contentctl/objects/dashboard.py +1 -1
- contentctl/objects/deployment_email.py +1 -0
- contentctl/objects/deployment_notable.py +3 -1
- contentctl/objects/deployment_phantom.py +1 -0
- contentctl/objects/deployment_rba.py +1 -0
- contentctl/objects/deployment_scheduling.py +1 -0
- contentctl/objects/deployment_slack.py +1 -0
- contentctl/objects/detection_stanza.py +1 -1
- contentctl/objects/integration_test.py +2 -2
- contentctl/objects/investigation_tags.py +6 -3
- contentctl/objects/manual_test.py +2 -2
- contentctl/objects/playbook_tags.py +247 -10
- contentctl/objects/risk_analysis_action.py +1 -1
- contentctl/objects/savedsearches_conf.py +3 -3
- contentctl/objects/story_tags.py +1 -1
- contentctl/objects/test_group.py +2 -2
- contentctl/objects/throttling.py +2 -1
- contentctl/objects/unit_test.py +2 -2
- contentctl/objects/unit_test_baseline.py +2 -1
- contentctl/objects/unit_test_result.py +4 -2
- contentctl/output/conf_output.py +2 -2
- contentctl/output/conf_writer.py +5 -5
- contentctl/output/doc_md_output.py +0 -1
- contentctl/output/jinja_writer.py +1 -0
- contentctl/output/json_writer.py +1 -1
- contentctl/output/yml_writer.py +3 -2
- {contentctl-5.5.6.dist-info → contentctl-5.5.8.dist-info}/METADATA +3 -3
- {contentctl-5.5.6.dist-info → contentctl-5.5.8.dist-info}/RECORD +65 -65
- {contentctl-5.5.6.dist-info → contentctl-5.5.8.dist-info}/LICENSE.md +0 -0
- {contentctl-5.5.6.dist-info → contentctl-5.5.8.dist-info}/WHEEL +0 -0
- {contentctl-5.5.6.dist-info → contentctl-5.5.8.dist-info}/entry_points.txt +0 -0
contentctl/helper/utils.py
CHANGED
|
@@ -60,7 +60,7 @@ class Utils:
|
|
|
60
60
|
|
|
61
61
|
if not path.exists() or not path.is_dir():
|
|
62
62
|
raise Exception(
|
|
63
|
-
f"Unable to get security_content files, required directory '{
|
|
63
|
+
f"Unable to get security_content files, required directory '{path!s}' does not exist or is not a directory"
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
allowedFiles: list[pathlib.Path] = []
|
|
@@ -275,7 +275,7 @@ class Utils:
|
|
|
275
275
|
# This is a file and we know it exists
|
|
276
276
|
return None
|
|
277
277
|
except Exception as e:
|
|
278
|
-
print(f"Could not copy local file {file_path} the file because {
|
|
278
|
+
print(f"Could not copy local file {file_path} the file because {e!s}")
|
|
279
279
|
|
|
280
280
|
# Try to make a head request to verify existence of the file
|
|
281
281
|
try:
|
|
@@ -285,7 +285,7 @@ class Utils:
|
|
|
285
285
|
if req.status_code > 400:
|
|
286
286
|
raise (Exception(f"Return code={req.status_code}"))
|
|
287
287
|
except Exception as e:
|
|
288
|
-
raise (Exception(f"HTTP Resolution Failed: {
|
|
288
|
+
raise (Exception(f"HTTP Resolution Failed: {e!s}"))
|
|
289
289
|
|
|
290
290
|
@staticmethod
|
|
291
291
|
def copy_local_file(
|
|
@@ -326,7 +326,7 @@ class Utils:
|
|
|
326
326
|
except Exception as e:
|
|
327
327
|
raise (
|
|
328
328
|
Exception(
|
|
329
|
-
f"Error: Could not copy local file [{sourcePath}] to [{destPath}]: [{
|
|
329
|
+
f"Error: Could not copy local file [{sourcePath}] to [{destPath}]: [{e!s}]"
|
|
330
330
|
)
|
|
331
331
|
)
|
|
332
332
|
if verbose_print:
|
|
@@ -417,26 +417,26 @@ class Utils:
|
|
|
417
417
|
except requests.exceptions.ConnectionError as e:
|
|
418
418
|
raise (
|
|
419
419
|
Exception(
|
|
420
|
-
f"Error: Could not download file [{file_path}] to [{destinationPath}] (Unable to connect to server. Are you sure the server exists and you have connectivity to it?): [{
|
|
420
|
+
f"Error: Could not download file [{file_path}] to [{destinationPath}] (Unable to connect to server. Are you sure the server exists and you have connectivity to it?): [{e!s}]"
|
|
421
421
|
)
|
|
422
422
|
)
|
|
423
423
|
|
|
424
424
|
except requests.exceptions.HTTPError as e:
|
|
425
425
|
raise (
|
|
426
426
|
Exception(
|
|
427
|
-
f"Error: Could not download file [{file_path}] to [{destinationPath}] (The file was probably not found on the server): [{
|
|
427
|
+
f"Error: Could not download file [{file_path}] to [{destinationPath}] (The file was probably not found on the server): [{e!s}]"
|
|
428
428
|
)
|
|
429
429
|
)
|
|
430
430
|
except requests.exceptions.Timeout as e:
|
|
431
431
|
raise (
|
|
432
432
|
Exception(
|
|
433
|
-
f"Error: Could not download file [{file_path}] to [{destinationPath}] (Timeout getting file): [{
|
|
433
|
+
f"Error: Could not download file [{file_path}] to [{destinationPath}] (Timeout getting file): [{e!s}]"
|
|
434
434
|
)
|
|
435
435
|
)
|
|
436
436
|
except Exception as e:
|
|
437
437
|
raise (
|
|
438
438
|
Exception(
|
|
439
|
-
f"Error: Could not download file [{file_path}] to [{destinationPath}] (Unknown Reason): [{
|
|
439
|
+
f"Error: Could not download file [{file_path}] to [{destinationPath}] (Unknown Reason): [{e!s}]"
|
|
440
440
|
)
|
|
441
441
|
)
|
|
442
442
|
finally:
|
contentctl/input/director.py
CHANGED
|
@@ -200,6 +200,7 @@ class Director:
|
|
|
200
200
|
author=self.input_dto.app.author_name,
|
|
201
201
|
description="A lookup file that contains the data source objects for detections.",
|
|
202
202
|
lookup_type=Lookup_Type.csv,
|
|
203
|
+
case_sensitive_match=False,
|
|
203
204
|
contents=RuntimeCsvWriter.generateDatasourceCSVContent(
|
|
204
205
|
self.output_dto.data_sources
|
|
205
206
|
),
|
|
@@ -315,7 +316,7 @@ class Director:
|
|
|
315
316
|
print(f"{Colors.BOLD}{Colors.BRIGHT_MAGENTA}╚{'═' * 60}╝{Colors.END}\n")
|
|
316
317
|
|
|
317
318
|
print(
|
|
318
|
-
f"{Colors.BOLD}{Colors.GREEN}{Colors.SPARKLE} Validation Completed{Colors.END}
|
|
319
|
+
f"{Colors.BOLD}{Colors.GREEN}{Colors.SPARKLE} Validation Completed{Colors.END} - Issues detected in {Colors.RED}{Colors.BOLD}{len(validation_errors)}{Colors.END} files.\n"
|
|
319
320
|
)
|
|
320
321
|
|
|
321
322
|
for index, entry in enumerate(validation_errors, 1):
|
|
@@ -371,7 +372,7 @@ class Director:
|
|
|
371
372
|
f" {Colors.RED}{Colors.ERROR} {error_msg}{Colors.END}"
|
|
372
373
|
)
|
|
373
374
|
else:
|
|
374
|
-
print(f" {Colors.RED}{Colors.ERROR} {
|
|
375
|
+
print(f" {Colors.RED}{Colors.ERROR} {error!s}{Colors.END}")
|
|
375
376
|
print("")
|
|
376
377
|
|
|
377
378
|
# Clean footer with next steps
|
contentctl/input/yml_reader.py
CHANGED
|
@@ -16,7 +16,7 @@ class YmlReader:
|
|
|
16
16
|
file_handler = open(file_path, "r", encoding="utf-8")
|
|
17
17
|
except OSError as exc:
|
|
18
18
|
print(
|
|
19
|
-
f"\nThere was an unrecoverable error when opening the file '{file_path}' - we will exit immediately:\n{
|
|
19
|
+
f"\nThere was an unrecoverable error when opening the file '{file_path}' - we will exit immediately:\n{exc!s}"
|
|
20
20
|
)
|
|
21
21
|
sys.exit(1)
|
|
22
22
|
|
|
@@ -57,7 +57,7 @@ class YmlReader:
|
|
|
57
57
|
)
|
|
58
58
|
except yaml.YAMLError as exc:
|
|
59
59
|
print(
|
|
60
|
-
f"\nThere was an unrecoverable YML Parsing error when reading or parsing the file '{file_path}' - we will exit immediately:\n{
|
|
60
|
+
f"\nThere was an unrecoverable YML Parsing error when reading or parsing the file '{file_path}' - we will exit immediately:\n{exc!s}"
|
|
61
61
|
)
|
|
62
62
|
sys.exit(1)
|
|
63
63
|
|
|
@@ -463,7 +463,7 @@ class Detection_Abstract(SecurityContentObject):
|
|
|
463
463
|
)
|
|
464
464
|
"""
|
|
465
465
|
action.risk.param._risk
|
|
466
|
-
of the conf file only contains a list of dicts. We do not eant to
|
|
466
|
+
of the conf file only contains a list of dicts. We do not eant to
|
|
467
467
|
include the message here, so we do not return it.
|
|
468
468
|
"""
|
|
469
469
|
rba_dict = self.rba.model_dump()
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, model_serializer
|
|
6
|
+
|
|
5
7
|
from contentctl.objects.deployment_email import DeploymentEmail
|
|
6
8
|
from contentctl.objects.deployment_notable import DeploymentNotable
|
|
9
|
+
from contentctl.objects.deployment_phantom import DeploymentPhantom
|
|
7
10
|
from contentctl.objects.deployment_rba import DeploymentRBA
|
|
8
11
|
from contentctl.objects.deployment_slack import DeploymentSlack
|
|
9
|
-
from contentctl.objects.deployment_phantom import DeploymentPhantom
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class AlertAction(BaseModel):
|
contentctl/objects/atomic.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from typing import TYPE_CHECKING
|
|
3
4
|
|
|
4
5
|
if TYPE_CHECKING:
|
|
5
6
|
from contentctl.objects.config import validate
|
|
6
7
|
|
|
7
|
-
from contentctl.input.yml_reader import YmlReader
|
|
8
|
-
from pydantic import BaseModel, model_validator, ConfigDict, FilePath, UUID4
|
|
9
8
|
import dataclasses
|
|
10
|
-
from typing import List, Optional, Dict, Union, Self
|
|
11
9
|
import pathlib
|
|
12
|
-
from enum import StrEnum, auto
|
|
13
10
|
import uuid
|
|
11
|
+
from enum import StrEnum, auto
|
|
12
|
+
from typing import Dict, List, Optional, Self, Union
|
|
13
|
+
|
|
14
|
+
from pydantic import UUID4, BaseModel, ConfigDict, FilePath, model_validator
|
|
15
|
+
|
|
16
|
+
from contentctl.input.yml_reader import YmlReader
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
class SupportedPlatform(StrEnum):
|
|
@@ -125,7 +128,7 @@ class AtomicTest(BaseModel):
|
|
|
125
128
|
try:
|
|
126
129
|
atomic_files.append(cls.constructAtomicFile(obj_path))
|
|
127
130
|
except Exception as e:
|
|
128
|
-
error_messages.append(f"File [{obj_path}]\n{
|
|
131
|
+
error_messages.append(f"File [{obj_path}]\n{e!s}")
|
|
129
132
|
|
|
130
133
|
if len(error_messages) > 0:
|
|
131
134
|
exceptions_string = "\n\n".join(error_messages)
|
contentctl/objects/base_test.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from typing import Union, Any
|
|
2
1
|
from enum import StrEnum
|
|
2
|
+
from typing import Any, Union
|
|
3
3
|
|
|
4
|
-
from pydantic import
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
5
|
from splunklib.data import Record # type: ignore
|
|
6
6
|
|
|
7
7
|
from contentctl.helper.utils import Utils
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Union
|
|
4
|
+
|
|
2
5
|
from pydantic import (
|
|
3
6
|
BaseModel,
|
|
7
|
+
ConfigDict,
|
|
4
8
|
Field,
|
|
5
|
-
field_validator,
|
|
6
9
|
ValidationInfo,
|
|
10
|
+
field_validator,
|
|
7
11
|
model_serializer,
|
|
8
|
-
ConfigDict,
|
|
9
12
|
)
|
|
10
|
-
from typing import List, Any, Union
|
|
11
13
|
|
|
12
|
-
from contentctl.objects.story import Story
|
|
13
14
|
from contentctl.objects.detection import Detection
|
|
14
|
-
from contentctl.objects.enums import SecurityContentProductName
|
|
15
|
-
from contentctl.objects.
|
|
15
|
+
from contentctl.objects.enums import SecurityContentProductName, SecurityDomain
|
|
16
|
+
from contentctl.objects.story import Story
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class BaselineTags(BaseModel):
|
contentctl/objects/config.py
CHANGED
|
@@ -200,7 +200,7 @@ class CustomApp(App_Base):
|
|
|
200
200
|
raise (
|
|
201
201
|
ValueError(
|
|
202
202
|
"The specified version does not follow the semantic versioning spec "
|
|
203
|
-
f"(https://semver.org/). {
|
|
203
|
+
f"(https://semver.org/). {e!s}"
|
|
204
204
|
)
|
|
205
205
|
)
|
|
206
206
|
return v
|
|
@@ -1094,7 +1094,7 @@ class test_common(build):
|
|
|
1094
1094
|
f"Successfully wrote a test plan for [{len(self.mode.files)} detections] using [{len(self.apps)} apps] to [{output_file}]"
|
|
1095
1095
|
)
|
|
1096
1096
|
except Exception as e:
|
|
1097
|
-
raise Exception(f"Error writing test plan file [{output_file}]: {
|
|
1097
|
+
raise Exception(f"Error writing test plan file [{output_file}]: {e!s}")
|
|
1098
1098
|
|
|
1099
1099
|
def getLocalAppDir(self) -> pathlib.Path:
|
|
1100
1100
|
# docker really wants absolute paths
|
|
@@ -1189,7 +1189,7 @@ class test(test_common):
|
|
|
1189
1189
|
return self
|
|
1190
1190
|
|
|
1191
1191
|
except Exception as e:
|
|
1192
|
-
raise ValueError(f"Error constructing container test_instances: {
|
|
1192
|
+
raise ValueError(f"Error constructing container test_instances: {e!s}")
|
|
1193
1193
|
|
|
1194
1194
|
@model_validator(mode="after")
|
|
1195
1195
|
def ensureAppsAreGood(self) -> Self:
|
|
@@ -1213,7 +1213,7 @@ class test(test_common):
|
|
|
1213
1213
|
stage_file=False, include_custom_app=False
|
|
1214
1214
|
)
|
|
1215
1215
|
except Exception as e:
|
|
1216
|
-
raise Exception(f"Error validating test apps: {
|
|
1216
|
+
raise Exception(f"Error validating test apps: {e!s}")
|
|
1217
1217
|
return self
|
|
1218
1218
|
|
|
1219
1219
|
def getContainerEnvironmentString(
|
|
@@ -1363,7 +1363,7 @@ class release_notes(Config_Base):
|
|
|
1363
1363
|
p.mkdir(exist_ok=True, parents=True)
|
|
1364
1364
|
except Exception as e:
|
|
1365
1365
|
raise Exception(
|
|
1366
|
-
f"Error making the directory '{p}' to hold release_notes: {
|
|
1366
|
+
f"Error making the directory '{p}' to hold release_notes: {e!s}"
|
|
1367
1367
|
)
|
|
1368
1368
|
return p / filename
|
|
1369
1369
|
|