contentctl 4.1.4__tar.gz → 4.1.5__tar.gz
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-4.1.4 → contentctl-4.1.5}/PKG-INFO +1 -1
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/GitService.py +11 -3
- contentctl-4.1.5/contentctl/actions/validate.py +76 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/helper/utils.py +43 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/lookup.py +47 -8
- {contentctl-4.1.4 → contentctl-4.1.5}/pyproject.toml +1 -1
- contentctl-4.1.4/contentctl/actions/validate.py +0 -79
- {contentctl-4.1.4 → contentctl-4.1.5}/LICENSE.md +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/README.md +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/__init__.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/build.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/convert.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/deploy_acs.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/progress_bar.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/doc_gen.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/initialize.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/initialize_old.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/inspect.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/new_content.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/release_notes.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/reporting.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/actions/test.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/api.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/contentctl.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/enrichments/attack_enrichment.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/enrichments/cve_enrichment.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/helper/link_validator.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/helper/logger.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/input/backend_splunk_ba.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/input/director.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/input/new_content_questions.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/input/sigma_converter.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/input/ssa_detection_builder.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/input/yml_reader.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/alert_action.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/atomic.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/base_test.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/base_test_result.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/baseline.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/baseline_tags.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/config.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/constants.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/correlation_search.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/data_source.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/deployment.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/deployment_email.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/deployment_notable.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/deployment_phantom.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/deployment_rba.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/deployment_scheduling.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/deployment_slack.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/detection.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/detection_tags.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/enums.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/event_source.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/integration_test.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/integration_test_result.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/investigation.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/investigation_tags.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/macro.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/mitre_attack_enrichment.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/notable_action.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/observable.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/playbook.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/playbook_tags.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/risk_analysis_action.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/risk_object.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/security_content_object.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/ssa_detection.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/ssa_detection_tags.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/story.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/story_tags.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/test_group.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/threat_object.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/unit_test.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/unit_test_attack_data.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/unit_test_baseline.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/unit_test_old.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/unit_test_result.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/objects/unit_test_ssa.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/api_json_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/attack_nav_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/attack_nav_writer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/ba_yml_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/conf_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/conf_writer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/detection_writer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/doc_md_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/finding_report_writer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/jinja_writer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/json_writer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/new_content_yml_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/svg_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/app.conf.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/app.manifest.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/collections.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/content-version.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/detection_count.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/detection_coverage.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_detection_page.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_detections.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_navigation.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_playbooks.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_stories.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/doc_story_page.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/finding_report.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/header.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/macros.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/panel.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/savedsearches_detections.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/transforms.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/workflow_actions.j2 +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/yml_output.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/yml_writer.py +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/README +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_default.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/README.md +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/app.conf +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/commands.conf +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/content-version.conf +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/metadata/default.meta +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/static/appIcon.png +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/datamodels_cim.conf +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/datamodels_custom.conf +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/detections/application/.gitkeep +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/detections/cloud/.gitkeep +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/detections/network/.gitkeep +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/detections/web/.gitkeep +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/macros/security_content_ctime.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
- {contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/stories/cobalt_strike.yml +0 -0
|
@@ -111,11 +111,19 @@ class GitService(BaseModel):
|
|
|
111
111
|
raise Exception(f"More than 1 Lookup reference the modified CSV file '{decoded_path}': {[l.file_path for l in matched ]}")
|
|
112
112
|
else:
|
|
113
113
|
updatedLookup = matched[0]
|
|
114
|
+
elif decoded_path.suffix == ".mlmodel":
|
|
115
|
+
# Detected a changed .mlmodel file. However, since we do not have testing for these detections at
|
|
116
|
+
# this time, we will ignore this change.
|
|
117
|
+
updatedLookup = None
|
|
118
|
+
|
|
119
|
+
|
|
114
120
|
else:
|
|
115
|
-
raise Exception(f"
|
|
121
|
+
raise Exception(f"Detected a changed file in the lookups/ directory '{str(decoded_path)}'.\n"
|
|
122
|
+
"Only files ending in .csv, .yml, or .mlmodel are supported in this "
|
|
123
|
+
"directory. This file must be removed from the lookups/ directory.")
|
|
116
124
|
|
|
117
|
-
if updatedLookup not in updated_lookups:
|
|
118
|
-
# It is possible that both
|
|
125
|
+
if updatedLookup is not None and updatedLookup not in updated_lookups:
|
|
126
|
+
# It is possible that both the CSV and YML have been modified for the same lookup,
|
|
119
127
|
# and we do not want to add it twice.
|
|
120
128
|
updated_lookups.append(updatedLookup)
|
|
121
129
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
import pathlib
|
|
3
|
+
from contentctl.input.director import Director, DirectorOutputDto
|
|
4
|
+
from contentctl.objects.config import validate
|
|
5
|
+
from contentctl.enrichments.attack_enrichment import AttackEnrichment
|
|
6
|
+
from contentctl.enrichments.cve_enrichment import CveEnrichment
|
|
7
|
+
from contentctl.objects.atomic import AtomicTest
|
|
8
|
+
from contentctl.helper.utils import Utils
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Validate:
|
|
12
|
+
def execute(self, input_dto: validate) -> DirectorOutputDto:
|
|
13
|
+
|
|
14
|
+
director_output_dto = DirectorOutputDto(
|
|
15
|
+
AtomicTest.getAtomicTestsFromArtRepo(
|
|
16
|
+
repo_path=input_dto.getAtomicRedTeamRepoPath(),
|
|
17
|
+
enabled=input_dto.enrichments,
|
|
18
|
+
),
|
|
19
|
+
AttackEnrichment.getAttackEnrichment(input_dto),
|
|
20
|
+
CveEnrichment.getCveEnrichment(input_dto),
|
|
21
|
+
[],
|
|
22
|
+
[],
|
|
23
|
+
[],
|
|
24
|
+
[],
|
|
25
|
+
[],
|
|
26
|
+
[],
|
|
27
|
+
[],
|
|
28
|
+
[],
|
|
29
|
+
[],
|
|
30
|
+
[],
|
|
31
|
+
[],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
director = Director(director_output_dto)
|
|
35
|
+
director.execute(input_dto)
|
|
36
|
+
self.ensure_no_orphaned_files_in_lookups(input_dto.path, director_output_dto)
|
|
37
|
+
return director_output_dto
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def ensure_no_orphaned_files_in_lookups(self, repo_path:pathlib.Path, director_output_dto:DirectorOutputDto):
|
|
41
|
+
"""
|
|
42
|
+
This function ensures that only files which are relevant to lookups are included in the lookups folder.
|
|
43
|
+
This means that a file must be either:
|
|
44
|
+
1. A lookup YML (.yml)
|
|
45
|
+
2. A lookup CSV (.csv) which is referenced by a YML
|
|
46
|
+
3. A lookup MLMODEL (.mlmodel) which is referenced by a YML.
|
|
47
|
+
|
|
48
|
+
All other files, includes CSV and MLMODEL files which are NOT
|
|
49
|
+
referenced by a YML, will generate an exception from this function.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
repo_path (pathlib.Path): path to the root of the app
|
|
53
|
+
director_output_dto (DirectorOutputDto): director object with all constructed content
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
Exception: An Exception will be raised if there are any non .yml, .csv, or .mlmodel
|
|
57
|
+
files in this directory. Additionally, an exception will be raised if there
|
|
58
|
+
exists one or more .csv or .mlmodel files that are not referenced by at least 1
|
|
59
|
+
detection .yml file in this directory.
|
|
60
|
+
This avoids having additional, unused files in this directory that may be copied into
|
|
61
|
+
the app when it is built (which can cause appinspect errors or larger app size.)
|
|
62
|
+
"""
|
|
63
|
+
lookupsDirectory = repo_path/"lookups"
|
|
64
|
+
|
|
65
|
+
# Get all of the files referneced by Lookups
|
|
66
|
+
usedLookupFiles:list[pathlib.Path] = [lookup.filename for lookup in director_output_dto.lookups if lookup.filename is not None] + [lookup.file_path for lookup in director_output_dto.lookups if lookup.file_path is not None]
|
|
67
|
+
|
|
68
|
+
# Get all of the mlmodel and csv files in the lookups directory
|
|
69
|
+
csvAndMlmodelFiles = Utils.get_security_content_files_from_directory(lookupsDirectory, allowedFileExtensions=[".yml",".csv",".mlmodel"], fileExtensionsToReturn=[".csv",".mlmodel"])
|
|
70
|
+
|
|
71
|
+
# Generate an exception of any csv or mlmodel files exist but are not used
|
|
72
|
+
unusedLookupFiles:list[pathlib.Path] = [testFile for testFile in csvAndMlmodelFiles if testFile not in usedLookupFiles]
|
|
73
|
+
if len(unusedLookupFiles) > 0:
|
|
74
|
+
raise Exception(f"The following .csv or .mlmodel files exist in '{lookupsDirectory}', but are not referenced by a lookup file: {[str(path) for path in unusedLookupFiles]}")
|
|
75
|
+
return
|
|
76
|
+
|
|
@@ -34,6 +34,49 @@ class Utils:
|
|
|
34
34
|
listOfFiles.append(pathlib.Path(os.path.join(dirpath, file)))
|
|
35
35
|
|
|
36
36
|
return sorted(listOfFiles)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def get_security_content_files_from_directory(path: pathlib.Path, allowedFileExtensions:list[str]=[".yml"], fileExtensionsToReturn:list[str]=[".yml"]) -> list[pathlib.Path]:
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
Get all of the Security Content Object Files rooted in a given directory. These will almost
|
|
43
|
+
certain be YML files, but could be other file types as specified by the user
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
path (pathlib.Path): The root path at which to enumerate all Security Content Files. All directories will be traversed.
|
|
47
|
+
allowedFileExtensions (set[str], optional): File extensions which are allowed to be present in this directory. In most cases, we do not want to allow the presence of non-YML files. Defaults to [".yml"].
|
|
48
|
+
fileExtensionsToReturn (set[str], optional): Filenames with extensions that should be returned from this function. For example, the lookups/ directory contains YML, CSV, and MLMODEL directories, but only the YMLs are Security Content Objects for constructing Lookyps. Defaults to[".yml"].
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
Exception: Will raise an exception if allowedFileExtensions is not a subset of fileExtensionsToReturn.
|
|
52
|
+
Exception: Will raise an exception if the path passed to the function does not exist or is not a directory
|
|
53
|
+
Exception: Will raise an exception if there are any files rooted in the directory which are not in allowedFileExtensions
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
list[pathlib.Path]: list of files with an extension in fileExtensionsToReturn found in path
|
|
57
|
+
"""
|
|
58
|
+
if not set(fileExtensionsToReturn).issubset(set(allowedFileExtensions)):
|
|
59
|
+
raise Exception(f"allowedFileExtensions {allowedFileExtensions} MUST be a subset of fileExtensionsToReturn {fileExtensionsToReturn}, but it is not")
|
|
60
|
+
|
|
61
|
+
if not path.exists() or not path.is_dir():
|
|
62
|
+
raise Exception(f"Unable to get security_content files, required directory '{str(path)}' does not exist or is not a directory")
|
|
63
|
+
|
|
64
|
+
allowedFiles:list[pathlib.Path] = []
|
|
65
|
+
erroneousFiles:list[pathlib.Path] = []
|
|
66
|
+
#Get every single file extension
|
|
67
|
+
for filePath in path.glob("**/*.*"):
|
|
68
|
+
if filePath.suffix in allowedFileExtensions:
|
|
69
|
+
# Yes these are allowed
|
|
70
|
+
allowedFiles.append(filePath)
|
|
71
|
+
else:
|
|
72
|
+
# No these have not been allowed
|
|
73
|
+
erroneousFiles.append(filePath)
|
|
74
|
+
|
|
75
|
+
if len(erroneousFiles):
|
|
76
|
+
raise Exception(f"The following files are not allowed in the directory '{path}'. Only files with the extensions {allowedFileExtensions} are allowed:{[str(filePath) for filePath in erroneousFiles]}")
|
|
77
|
+
|
|
78
|
+
# There were no errorneous files, so return the requested files
|
|
79
|
+
return sorted([filePath for filePath in allowedFiles if filePath.suffix in fileExtensionsToReturn])
|
|
37
80
|
|
|
38
81
|
@staticmethod
|
|
39
82
|
def get_all_yml_files_from_directory_one_layer_deep(path: str) -> list[pathlib.Path]:
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
from pydantic import field_validator, ValidationInfo, model_validator, FilePath, model_serializer
|
|
3
3
|
from typing import TYPE_CHECKING, Optional, Any, Union
|
|
4
4
|
import re
|
|
5
|
+
import csv
|
|
5
6
|
if TYPE_CHECKING:
|
|
6
7
|
from contentctl.input.director import DirectorOutputDto
|
|
7
8
|
from contentctl.objects.config import validate
|
|
@@ -61,15 +62,53 @@ class Lookup(SecurityContentObject):
|
|
|
61
62
|
raise ValueError("config required for constructing lookup filename, but it was not")
|
|
62
63
|
return data
|
|
63
64
|
|
|
64
|
-
@field_validator('filename')
|
|
65
|
-
@classmethod
|
|
66
|
-
def lookup_file_valid(cls, v: Union[FilePath,None], info: ValidationInfo):
|
|
67
|
-
if not v:
|
|
68
|
-
return v
|
|
69
|
-
if not (v.name.endswith(".csv") or v.name.endswith(".mlmodel")):
|
|
70
|
-
raise ValueError(f"All Lookup files must be CSV files and end in .csv. The following file does not: '{v}'")
|
|
71
65
|
|
|
72
|
-
|
|
66
|
+
def model_post_init(self, ctx:dict[str,Any]):
|
|
67
|
+
if not self.filename:
|
|
68
|
+
return
|
|
69
|
+
import pathlib
|
|
70
|
+
filenamePath = pathlib.Path(self.filename)
|
|
71
|
+
|
|
72
|
+
if filenamePath.suffix not in [".csv", ".mlmodel"]:
|
|
73
|
+
raise ValueError(f"All Lookup files must be CSV files and end in .csv. The following file does not: '{filenamePath}'")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if filenamePath.suffix == ".mlmodel":
|
|
78
|
+
# Do not need any additional checks for an mlmodel file
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# https://docs.python.org/3/library/csv.html#csv.DictReader
|
|
82
|
+
# Column Names (fieldnames) determine by the number of columns in the first row.
|
|
83
|
+
# If a row has MORE fields than fieldnames, they will be dumped in a list under the key 'restkey' - this should throw an Exception
|
|
84
|
+
# If a row has LESS fields than fieldnames, then the field should contain None by default. This should also throw an exception.
|
|
85
|
+
csv_errors:list[str] = []
|
|
86
|
+
with open(filenamePath, "r") as csv_fp:
|
|
87
|
+
RESTKEY = "extra_fields_in_a_row"
|
|
88
|
+
csv_dict = csv.DictReader(csv_fp, restkey=RESTKEY)
|
|
89
|
+
if csv_dict.fieldnames is None:
|
|
90
|
+
raise ValueError(f"Error validating the CSV referenced by the lookup: {filenamePath}:\n\t"
|
|
91
|
+
"Unable to read fieldnames from CSV. Is the CSV empty?\n"
|
|
92
|
+
" Please try opening the file with a CSV Editor to ensure that it is correct.")
|
|
93
|
+
# Remember that row 1 has the headers and we do not iterate over it in the loop below
|
|
94
|
+
# CSVs are typically indexed starting a row 1 for the header.
|
|
95
|
+
for row_index, data_row in enumerate(csv_dict):
|
|
96
|
+
row_index+=2
|
|
97
|
+
if len(data_row.get(RESTKEY,[])) > 0:
|
|
98
|
+
csv_errors.append(f"row [{row_index}] should have [{len(csv_dict.fieldnames)}] columns,"
|
|
99
|
+
f" but instead had [{len(csv_dict.fieldnames) + len(data_row.get(RESTKEY,[]))}].")
|
|
100
|
+
|
|
101
|
+
for column_index, column_name in enumerate(data_row):
|
|
102
|
+
if data_row[column_name] is None:
|
|
103
|
+
csv_errors.append(f"row [{row_index}] should have [{len(csv_dict.fieldnames)}] columns, "
|
|
104
|
+
f"but instead had [{column_index}].")
|
|
105
|
+
if len(csv_errors) > 0:
|
|
106
|
+
err_string = '\n\t'.join(csv_errors)
|
|
107
|
+
raise ValueError(f"Error validating the CSV referenced by the lookup: {filenamePath}:\n\t{err_string}\n"
|
|
108
|
+
f" Please try opening the file with a CSV Editor to ensure that it is correct.")
|
|
109
|
+
|
|
110
|
+
return
|
|
111
|
+
|
|
73
112
|
|
|
74
113
|
@field_validator('match_type')
|
|
75
114
|
@classmethod
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
from pydantic import ValidationError
|
|
6
|
-
from typing import Union
|
|
7
|
-
|
|
8
|
-
from contentctl.objects.enums import SecurityContentProduct
|
|
9
|
-
from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
|
|
10
|
-
SecurityContentObject_Abstract,
|
|
11
|
-
)
|
|
12
|
-
from contentctl.input.director import Director, DirectorOutputDto
|
|
13
|
-
|
|
14
|
-
from contentctl.objects.config import validate
|
|
15
|
-
from contentctl.enrichments.attack_enrichment import AttackEnrichment
|
|
16
|
-
from contentctl.enrichments.cve_enrichment import CveEnrichment
|
|
17
|
-
from contentctl.objects.atomic import AtomicTest
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Validate:
|
|
21
|
-
def execute(self, input_dto: validate) -> DirectorOutputDto:
|
|
22
|
-
|
|
23
|
-
director_output_dto = DirectorOutputDto(
|
|
24
|
-
AtomicTest.getAtomicTestsFromArtRepo(
|
|
25
|
-
repo_path=input_dto.getAtomicRedTeamRepoPath(),
|
|
26
|
-
enabled=input_dto.enrichments,
|
|
27
|
-
),
|
|
28
|
-
AttackEnrichment.getAttackEnrichment(input_dto),
|
|
29
|
-
CveEnrichment.getCveEnrichment(input_dto),
|
|
30
|
-
[],
|
|
31
|
-
[],
|
|
32
|
-
[],
|
|
33
|
-
[],
|
|
34
|
-
[],
|
|
35
|
-
[],
|
|
36
|
-
[],
|
|
37
|
-
[],
|
|
38
|
-
[],
|
|
39
|
-
[],
|
|
40
|
-
[],
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
director = Director(director_output_dto)
|
|
44
|
-
director.execute(input_dto)
|
|
45
|
-
return director_output_dto
|
|
46
|
-
|
|
47
|
-
def validate_duplicate_uuids(
|
|
48
|
-
self, security_content_objects: list[SecurityContentObject_Abstract]
|
|
49
|
-
):
|
|
50
|
-
all_uuids = set()
|
|
51
|
-
duplicate_uuids = set()
|
|
52
|
-
for elem in security_content_objects:
|
|
53
|
-
if elem.id in all_uuids:
|
|
54
|
-
# The uuid has been found more than once
|
|
55
|
-
duplicate_uuids.add(elem.id)
|
|
56
|
-
else:
|
|
57
|
-
# This is the first time the uuid has been found
|
|
58
|
-
all_uuids.add(elem.id)
|
|
59
|
-
|
|
60
|
-
if len(duplicate_uuids) == 0:
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
# At least once duplicate uuid has been found. Enumerate all
|
|
64
|
-
# the pieces of content that use duplicate uuids
|
|
65
|
-
duplicate_messages = []
|
|
66
|
-
for uuid in duplicate_uuids:
|
|
67
|
-
duplicate_uuid_content = [
|
|
68
|
-
str(content.file_path)
|
|
69
|
-
for content in security_content_objects
|
|
70
|
-
if content.id in duplicate_uuids
|
|
71
|
-
]
|
|
72
|
-
duplicate_messages.append(
|
|
73
|
-
f"Duplicate UUID [{uuid}] in {duplicate_uuid_content}"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
raise ValueError(
|
|
77
|
-
"ERROR: Duplicate ID(s) found in objects:\n"
|
|
78
|
-
+ "\n - ".join(duplicate_messages)
|
|
79
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/analyticstories_detections.j2
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/analyticstories_investigations.j2
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/analyticstories_stories.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/es_investigations_stories.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/savedsearches_baselines.j2
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/savedsearches_detections.j2
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/output/templates/savedsearches_investigations.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/README/essoc_summary.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/commands.conf
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/default/content-version.conf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/lookups/mitre_enrichment.csv
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/metadata/default.meta
RENAMED
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/static/appIconAlt.png
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/static/appIconAlt_2x.png
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/app_template/static/appIcon_2x.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/macros/security_content_ctime.yml
RENAMED
|
File without changes
|
{contentctl-4.1.4 → contentctl-4.1.5}/contentctl/templates/macros/security_content_summariesonly.yml
RENAMED
|
File without changes
|
|
File without changes
|