contentctl 5.0.0a0__tar.gz → 5.0.0a2__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-5.0.0a0 → contentctl-5.0.0a2}/PKG-INFO +1 -1
- contentctl-5.0.0a2/contentctl/actions/detection_testing/GitService.py +247 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/contentctl.py +2 -3
- contentctl-5.0.0a2/contentctl/objects/config.py +1238 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/savedsearches_detections.j2 +1 -1
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/pyproject.toml +1 -1
- contentctl-5.0.0a0/contentctl/actions/detection_testing/GitService.py +0 -187
- contentctl-5.0.0a0/contentctl/objects/config.py +0 -1026
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/LICENSE.md +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/README.md +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/__init__.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/build.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/deploy_acs.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/progress_bar.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/doc_gen.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/initialize.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/initialize_old.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/inspect.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/new_content.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/release_notes.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/reporting.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/test.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/actions/validate.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/api.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/enrichments/attack_enrichment.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/enrichments/cve_enrichment.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/helper/link_validator.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/helper/logger.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/helper/splunk_app.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/helper/utils.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/input/director.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/input/new_content_questions.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/input/yml_reader.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/alert_action.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/annotated_types.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/atomic.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/base_test.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/base_test_result.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/baseline.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/baseline_tags.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/constants.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/correlation_search.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/dashboard.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/data_source.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/deployment.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/deployment_email.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/deployment_notable.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/deployment_phantom.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/deployment_rba.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/deployment_scheduling.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/deployment_slack.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/detection.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/detection_metadata.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/detection_stanza.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/detection_tags.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/drilldown.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/enums.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/errors.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/integration_test.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/integration_test_result.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/investigation.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/investigation_tags.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/lookup.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/macro.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/manual_test.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/manual_test_result.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/mitre_attack_enrichment.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/notable_action.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/notable_event.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/observable.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/playbook.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/playbook_tags.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/rba.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/risk_analysis_action.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/risk_event.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/risk_object.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/savedsearches_conf.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/security_content_object.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/story.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/story_tags.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/test_attack_data.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/test_group.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/threat_object.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/throttling.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/unit_test.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/unit_test_baseline.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/objects/unit_test_result.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/api_json_output.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/attack_nav_output.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/attack_nav_writer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/conf_output.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/conf_writer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/data_source_writer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/doc_md_output.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/jinja_writer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/json_writer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/svg_output.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/app.conf.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/app.manifest.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/collections.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/content-version.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/detection_count.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/detection_coverage.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_detection_page.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_detections.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_navigation.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_playbooks.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_stories.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/doc_story_page.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/header.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/macros.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/panel.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/server.conf.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/transforms.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/templates/workflow_actions.j2 +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/output/yml_writer.py +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/README.md +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_default.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/README.md +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/commands.conf +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/metadata/default.meta +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIcon.png +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/datamodels_cim.conf +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/datamodels_custom.conf +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/detections/application/.gitkeep +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/detections/cloud/.gitkeep +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/detections/network/.gitkeep +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/detections/web/.gitkeep +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/macros/security_content_ctime.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
- {contentctl-5.0.0a0 → contentctl-5.0.0a2}/contentctl/templates/stories/cobalt_strike.yml +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
5
|
+
|
|
6
|
+
import pygit2
|
|
7
|
+
from pydantic import BaseModel, FilePath
|
|
8
|
+
from pygit2.enums import DeltaStatus
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from contentctl.input.director import DirectorOutputDto
|
|
12
|
+
|
|
13
|
+
from contentctl.objects.config import All, Changes, Selected, test_common
|
|
14
|
+
from contentctl.objects.data_source import DataSource
|
|
15
|
+
from contentctl.objects.detection import Detection
|
|
16
|
+
from contentctl.objects.lookup import CSVLookup, Lookup
|
|
17
|
+
from contentctl.objects.macro import Macro
|
|
18
|
+
from contentctl.objects.security_content_object import SecurityContentObject
|
|
19
|
+
|
|
20
|
+
# Logger
|
|
21
|
+
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
|
22
|
+
LOGGER = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
from contentctl.input.director import DirectorOutputDto
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GitService(BaseModel):
|
|
29
|
+
director: DirectorOutputDto
|
|
30
|
+
config: test_common
|
|
31
|
+
gitHash: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
def getHash(self) -> str:
|
|
34
|
+
if self.gitHash is None:
|
|
35
|
+
raise Exception("Cannot get hash of repo, it was not set")
|
|
36
|
+
return self.gitHash
|
|
37
|
+
|
|
38
|
+
def getContent(self) -> List[Detection]:
|
|
39
|
+
if isinstance(self.config.mode, Selected):
|
|
40
|
+
return self.getSelected(self.config.mode.files)
|
|
41
|
+
elif isinstance(self.config.mode, Changes):
|
|
42
|
+
return self.getChanges(self.config.mode.target_branch)
|
|
43
|
+
if isinstance(self.config.mode, All):
|
|
44
|
+
return self.getAll()
|
|
45
|
+
else:
|
|
46
|
+
raise Exception(
|
|
47
|
+
f"Could not get content to test. Unsupported test mode '{self.config.mode}'"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def getAll(self) -> List[Detection]:
|
|
51
|
+
return self.director.detections
|
|
52
|
+
|
|
53
|
+
def getChanges(self, target_branch: str) -> List[Detection]:
|
|
54
|
+
repo = pygit2.Repository(path=str(self.config.path))
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
target_tree = repo.revparse_single(target_branch).tree
|
|
58
|
+
self.gitHash = target_tree.id
|
|
59
|
+
diffs = repo.index.diff_to_tree(target_tree)
|
|
60
|
+
except Exception:
|
|
61
|
+
raise Exception(
|
|
62
|
+
f"Error parsing diff target_branch '{target_branch}'. Are you certain that it exists?"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Get the uncommitted changes in the current directory
|
|
66
|
+
diffs2 = repo.index.diff_to_workdir()
|
|
67
|
+
|
|
68
|
+
# Combine the uncommitted changes with the committed changes
|
|
69
|
+
all_diffs = list(diffs) + list(diffs2)
|
|
70
|
+
|
|
71
|
+
# Make a filename to content map
|
|
72
|
+
filepath_to_content_map = {
|
|
73
|
+
obj.file_path: obj for (_, obj) in self.director.name_to_content_map.items()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
updated_detections: set[Detection] = set()
|
|
77
|
+
updated_macros: set[Macro] = set()
|
|
78
|
+
updated_lookups: set[Lookup] = set()
|
|
79
|
+
updated_datasources: set[DataSource] = set()
|
|
80
|
+
|
|
81
|
+
for diff in all_diffs:
|
|
82
|
+
if type(diff) == pygit2.Patch:
|
|
83
|
+
if diff.delta.status in (
|
|
84
|
+
DeltaStatus.ADDED,
|
|
85
|
+
DeltaStatus.MODIFIED,
|
|
86
|
+
DeltaStatus.RENAMED,
|
|
87
|
+
):
|
|
88
|
+
# print(f"{DeltaStatus(diff.delta.status).name:<8}:{diff.delta.new_file.raw_path}")
|
|
89
|
+
decoded_path = pathlib.Path(
|
|
90
|
+
diff.delta.new_file.raw_path.decode("utf-8")
|
|
91
|
+
)
|
|
92
|
+
# Note that we only handle updates to detections, lookups, and macros at this time. All other changes are ignored.
|
|
93
|
+
if (
|
|
94
|
+
decoded_path.is_relative_to(self.config.path / "detections")
|
|
95
|
+
and decoded_path.suffix == ".yml"
|
|
96
|
+
):
|
|
97
|
+
detectionObject = filepath_to_content_map.get(
|
|
98
|
+
decoded_path, None
|
|
99
|
+
)
|
|
100
|
+
if isinstance(detectionObject, Detection):
|
|
101
|
+
updated_detections.add(detectionObject)
|
|
102
|
+
else:
|
|
103
|
+
raise Exception(
|
|
104
|
+
f"Error getting detection object for file {str(decoded_path)}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
elif (
|
|
108
|
+
decoded_path.is_relative_to(self.config.path / "macros")
|
|
109
|
+
and decoded_path.suffix == ".yml"
|
|
110
|
+
):
|
|
111
|
+
macroObject = filepath_to_content_map.get(decoded_path, None)
|
|
112
|
+
if isinstance(macroObject, Macro):
|
|
113
|
+
updated_macros.add(macroObject)
|
|
114
|
+
else:
|
|
115
|
+
raise Exception(
|
|
116
|
+
f"Error getting macro object for file {str(decoded_path)}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
elif (
|
|
120
|
+
decoded_path.is_relative_to(self.config.path / "data_sources")
|
|
121
|
+
and decoded_path.suffix == ".yml"
|
|
122
|
+
):
|
|
123
|
+
datasourceObject = filepath_to_content_map.get(
|
|
124
|
+
decoded_path, None
|
|
125
|
+
)
|
|
126
|
+
if isinstance(datasourceObject, DataSource):
|
|
127
|
+
updated_datasources.add(datasourceObject)
|
|
128
|
+
else:
|
|
129
|
+
raise Exception(
|
|
130
|
+
f"Error getting data source object for file {str(decoded_path)}"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
elif decoded_path.is_relative_to(self.config.path / "lookups"):
|
|
134
|
+
# We need to convert this to a yml. This means we will catch
|
|
135
|
+
# both changes to a csv AND changes to the YML that uses it
|
|
136
|
+
if decoded_path.suffix == ".yml":
|
|
137
|
+
updatedLookup = filepath_to_content_map.get(
|
|
138
|
+
decoded_path, None
|
|
139
|
+
)
|
|
140
|
+
if not isinstance(updatedLookup, Lookup):
|
|
141
|
+
raise Exception(
|
|
142
|
+
f"Expected {decoded_path} to be type {type(Lookup)}, but instead if was {(type(updatedLookup))}"
|
|
143
|
+
)
|
|
144
|
+
updated_lookups.add(updatedLookup)
|
|
145
|
+
|
|
146
|
+
elif decoded_path.suffix == ".csv":
|
|
147
|
+
# If the CSV was updated, we want to make sure that we
|
|
148
|
+
# add the correct corresponding Lookup object.
|
|
149
|
+
# Filter to find the Lookup Object the references this CSV
|
|
150
|
+
matched = list(
|
|
151
|
+
filter(
|
|
152
|
+
lambda x: isinstance(x, CSVLookup)
|
|
153
|
+
and x.filename == decoded_path,
|
|
154
|
+
self.director.lookups,
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
if len(matched) == 0:
|
|
158
|
+
raise Exception(
|
|
159
|
+
f"Failed to find any lookups that reference the modified CSV file '{decoded_path}'"
|
|
160
|
+
)
|
|
161
|
+
elif len(matched) > 1:
|
|
162
|
+
raise Exception(
|
|
163
|
+
f"More than 1 Lookup reference the modified CSV file '{decoded_path}': {[match.file_path for match in matched]}"
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
updatedLookup = matched[0]
|
|
167
|
+
elif decoded_path.suffix == ".mlmodel":
|
|
168
|
+
# Detected a changed .mlmodel file. However, since we do not have testing for these detections at
|
|
169
|
+
# this time, we will ignore this change.
|
|
170
|
+
updatedLookup = None
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
raise Exception(
|
|
174
|
+
f"Detected a changed file in the lookups/ directory '{str(decoded_path)}'.\n"
|
|
175
|
+
"Only files ending in .csv, .yml, or .mlmodel are supported in this "
|
|
176
|
+
"directory. This file must be removed from the lookups/ directory."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
updatedLookup is not None
|
|
181
|
+
and updatedLookup not in updated_lookups
|
|
182
|
+
):
|
|
183
|
+
# It is possible that both the CSV and YML have been modified for the same lookup,
|
|
184
|
+
# and we do not want to add it twice.
|
|
185
|
+
updated_lookups.add(updatedLookup)
|
|
186
|
+
|
|
187
|
+
else:
|
|
188
|
+
pass
|
|
189
|
+
# print(f"Ignore changes to file {decoded_path} since it is not a detection, macro, or lookup.")
|
|
190
|
+
else:
|
|
191
|
+
raise Exception(f"Unrecognized diff type {type(diff)}")
|
|
192
|
+
|
|
193
|
+
# If a detection has at least one dependency on changed content,
|
|
194
|
+
# then we must test it again
|
|
195
|
+
|
|
196
|
+
changed_macros_and_lookups_and_datasources: set[Macro | Lookup | DataSource] = (
|
|
197
|
+
updated_macros.union(updated_lookups, updated_datasources)
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
for detection in self.director.detections:
|
|
201
|
+
if detection in updated_detections:
|
|
202
|
+
# we are already planning to test it, don't need
|
|
203
|
+
# to add it again
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
for obj in changed_macros_and_lookups_and_datasources:
|
|
207
|
+
if obj in detection.get_content_dependencies():
|
|
208
|
+
updated_detections.add(detection)
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
# Print out the names of all modified/new content
|
|
212
|
+
modifiedAndNewContentString = "\n - ".join(
|
|
213
|
+
sorted([d.name for d in updated_detections])
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
print(
|
|
217
|
+
f"[{len(updated_detections)}] Pieces of modifed and new content (this may include experimental/deprecated/manual_test content):\n - {modifiedAndNewContentString}"
|
|
218
|
+
)
|
|
219
|
+
return sorted(list(updated_detections))
|
|
220
|
+
|
|
221
|
+
def getSelected(self, detectionFilenames: List[FilePath]) -> List[Detection]:
|
|
222
|
+
filepath_to_content_map: dict[FilePath, SecurityContentObject] = {
|
|
223
|
+
obj.file_path: obj
|
|
224
|
+
for (_, obj) in self.director.name_to_content_map.items()
|
|
225
|
+
if obj.file_path is not None
|
|
226
|
+
}
|
|
227
|
+
errors = []
|
|
228
|
+
detections: List[Detection] = []
|
|
229
|
+
for name in detectionFilenames:
|
|
230
|
+
obj = filepath_to_content_map.get(name, None)
|
|
231
|
+
if obj is None:
|
|
232
|
+
errors.append(
|
|
233
|
+
f"There is no detection file or security_content_object at '{name}'"
|
|
234
|
+
)
|
|
235
|
+
elif not isinstance(obj, Detection):
|
|
236
|
+
errors.append(
|
|
237
|
+
f"The security_content_object at '{name}' is of type '{type(obj).__name__}', NOT '{Detection.__name__}'"
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
detections.append(obj)
|
|
241
|
+
|
|
242
|
+
if errors:
|
|
243
|
+
errorsString = "\n - ".join(errors)
|
|
244
|
+
raise Exception(
|
|
245
|
+
f"The following errors were encountered while getting selected detections to test:\n - {errorsString}"
|
|
246
|
+
)
|
|
247
|
+
return detections
|
|
@@ -246,8 +246,6 @@ def main():
|
|
|
246
246
|
print(e)
|
|
247
247
|
sys.exit(1)
|
|
248
248
|
except Exception as e:
|
|
249
|
-
print(CONTENTCTL_5_WARNING)
|
|
250
|
-
|
|
251
249
|
if config is None:
|
|
252
250
|
print(
|
|
253
251
|
"There was a serious issue where the config file could not be created.\n"
|
|
@@ -265,8 +263,9 @@ def main():
|
|
|
265
263
|
"Verbose error logging is DISABLED.\n"
|
|
266
264
|
"Please use the --verbose command line argument if you need more context for your error or file a bug report."
|
|
267
265
|
)
|
|
268
|
-
print(e)
|
|
269
266
|
|
|
267
|
+
print(e)
|
|
268
|
+
print(CONTENTCTL_5_WARNING)
|
|
270
269
|
sys.exit(1)
|
|
271
270
|
|
|
272
271
|
|