contentctl 4.4.6__tar.gz → 5.0.0__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.4.6 → contentctl-5.0.0}/PKG-INFO +6 -5
- contentctl-5.0.0/contentctl/__init__.py +1 -0
- contentctl-5.0.0/contentctl/actions/build.py +136 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/deploy_acs.py +29 -24
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/DetectionTestingManager.py +66 -42
- contentctl-5.0.0/contentctl/actions/detection_testing/GitService.py +245 -0
- contentctl-5.0.0/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +83 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +192 -147
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +45 -32
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/progress_bar.py +9 -6
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/views/DetectionTestingView.py +16 -19
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +1 -5
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +2 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +1 -4
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/doc_gen.py +9 -5
- contentctl-5.0.0/contentctl/actions/initialize.py +75 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/inspect.py +118 -61
- contentctl-5.0.0/contentctl/actions/new_content.py +180 -0
- contentctl-5.0.0/contentctl/actions/release_notes.py +370 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/reporting.py +23 -19
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/test.py +33 -28
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/validate.py +55 -34
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/api.py +54 -45
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/contentctl.py +124 -90
- contentctl-5.0.0/contentctl/enrichments/attack_enrichment.py +161 -0
- contentctl-5.0.0/contentctl/enrichments/cve_enrichment.py +70 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/enrichments/splunk_app_enrichment.py +38 -36
- contentctl-5.0.0/contentctl/helper/link_validator.py +195 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/helper/splunk_app.py +69 -41
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/helper/utils.py +58 -53
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/input/director.py +68 -36
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/input/new_content_questions.py +27 -35
- contentctl-5.0.0/contentctl/input/yml_reader.py +57 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +303 -259
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +115 -52
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/alert_action.py +10 -9
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/annotated_types.py +1 -1
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/atomic.py +65 -54
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/base_test.py +5 -3
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/base_test_result.py +19 -11
- contentctl-5.0.0/contentctl/objects/baseline.py +89 -0
- contentctl-5.0.0/contentctl/objects/baseline_tags.py +49 -0
- contentctl-5.0.0/contentctl/objects/config.py +1238 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/constants.py +33 -56
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/correlation_search.py +150 -136
- contentctl-5.0.0/contentctl/objects/dashboard.py +114 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/data_source.py +16 -17
- contentctl-5.0.0/contentctl/objects/deployment.py +84 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/deployment_email.py +3 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/deployment_notable.py +4 -2
- contentctl-5.0.0/contentctl/objects/deployment_phantom.py +11 -0
- contentctl-5.0.0/contentctl/objects/deployment_rba.py +7 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/deployment_scheduling.py +3 -2
- contentctl-5.0.0/contentctl/objects/deployment_slack.py +8 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/detection.py +5 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/detection_metadata.py +1 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/detection_stanza.py +7 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/detection_tags.py +58 -103
- contentctl-5.0.0/contentctl/objects/drilldown.py +102 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/enums.py +81 -100
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/errors.py +16 -24
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/integration_test.py +3 -3
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/integration_test_result.py +1 -0
- contentctl-5.0.0/contentctl/objects/investigation.py +108 -0
- contentctl-5.0.0/contentctl/objects/investigation_tags.py +45 -0
- contentctl-5.0.0/contentctl/objects/lookup.py +356 -0
- contentctl-5.0.0/contentctl/objects/macro.py +90 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/manual_test.py +3 -3
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/manual_test_result.py +1 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/mitre_attack_enrichment.py +17 -16
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/notable_action.py +2 -1
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/notable_event.py +1 -3
- contentctl-5.0.0/contentctl/objects/playbook.py +68 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/playbook_tags.py +23 -13
- contentctl-5.0.0/contentctl/objects/rba.py +96 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/risk_analysis_action.py +15 -11
- contentctl-5.0.0/contentctl/objects/risk_event.py +284 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/risk_object.py +1 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/savedsearches_conf.py +9 -7
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/security_content_object.py +5 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/story.py +54 -49
- contentctl-5.0.0/contentctl/objects/story_tags.py +66 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/test_attack_data.py +2 -1
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/test_group.py +5 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/threat_object.py +1 -0
- contentctl-5.0.0/contentctl/objects/throttling.py +55 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/unit_test.py +3 -4
- contentctl-5.0.0/contentctl/objects/unit_test_baseline.py +11 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/objects/unit_test_result.py +6 -6
- contentctl-5.0.0/contentctl/output/api_json_output.py +259 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/attack_nav_output.py +21 -21
- contentctl-5.0.0/contentctl/output/attack_nav_writer.py +67 -0
- contentctl-5.0.0/contentctl/output/conf_output.py +276 -0
- contentctl-5.0.0/contentctl/output/conf_writer.py +413 -0
- contentctl-5.0.0/contentctl/output/data_source_writer.py +52 -0
- contentctl-5.0.0/contentctl/output/doc_md_output.py +96 -0
- contentctl-5.0.0/contentctl/output/jinja_writer.py +37 -0
- contentctl-5.0.0/contentctl/output/json_writer.py +31 -0
- contentctl-5.0.0/contentctl/output/svg_output.py +73 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/analyticstories_detections.j2 +2 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/analyticstories_stories.j2 +1 -1
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/collections.j2 +1 -1
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_detections.j2 +0 -5
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/es_investigations_investigations.j2 +1 -1
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/es_investigations_stories.j2 +1 -1
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/savedsearches_baselines.j2 +2 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/savedsearches_detections.j2 +10 -11
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/savedsearches_investigations.j2 +2 -2
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/transforms.j2 +6 -8
- contentctl-5.0.0/contentctl/output/yml_writer.py +58 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/stories/cobalt_strike.yml +1 -0
- contentctl-5.0.0/pyproject.toml +117 -0
- contentctl-4.4.6/contentctl/__init__.py +0 -1
- contentctl-4.4.6/contentctl/actions/build.py +0 -91
- contentctl-4.4.6/contentctl/actions/detection_testing/GitService.py +0 -187
- contentctl-4.4.6/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -65
- contentctl-4.4.6/contentctl/actions/initialize.py +0 -63
- contentctl-4.4.6/contentctl/actions/initialize_old.py +0 -245
- contentctl-4.4.6/contentctl/actions/new_content.py +0 -133
- contentctl-4.4.6/contentctl/actions/release_notes.py +0 -240
- contentctl-4.4.6/contentctl/enrichments/attack_enrichment.py +0 -121
- contentctl-4.4.6/contentctl/enrichments/cve_enrichment.py +0 -64
- contentctl-4.4.6/contentctl/helper/link_validator.py +0 -172
- contentctl-4.4.6/contentctl/input/yml_reader.py +0 -47
- contentctl-4.4.6/contentctl/objects/baseline.py +0 -57
- contentctl-4.4.6/contentctl/objects/baseline_tags.py +0 -43
- contentctl-4.4.6/contentctl/objects/config.py +0 -1045
- contentctl-4.4.6/contentctl/objects/dashboard.py +0 -100
- contentctl-4.4.6/contentctl/objects/deployment.py +0 -85
- contentctl-4.4.6/contentctl/objects/deployment_phantom.py +0 -10
- contentctl-4.4.6/contentctl/objects/deployment_rba.py +0 -6
- contentctl-4.4.6/contentctl/objects/deployment_slack.py +0 -7
- contentctl-4.4.6/contentctl/objects/drilldown.py +0 -70
- contentctl-4.4.6/contentctl/objects/event_source.py +0 -11
- contentctl-4.4.6/contentctl/objects/investigation.py +0 -85
- contentctl-4.4.6/contentctl/objects/investigation_tags.py +0 -34
- contentctl-4.4.6/contentctl/objects/lookup.py +0 -153
- contentctl-4.4.6/contentctl/objects/macro.py +0 -74
- contentctl-4.4.6/contentctl/objects/observable.py +0 -37
- contentctl-4.4.6/contentctl/objects/playbook.py +0 -66
- contentctl-4.4.6/contentctl/objects/risk_event.py +0 -334
- contentctl-4.4.6/contentctl/objects/story_tags.py +0 -55
- contentctl-4.4.6/contentctl/objects/throttling.py +0 -46
- contentctl-4.4.6/contentctl/objects/unit_test_baseline.py +0 -11
- contentctl-4.4.6/contentctl/output/api_json_output.py +0 -246
- contentctl-4.4.6/contentctl/output/attack_nav_writer.py +0 -75
- contentctl-4.4.6/contentctl/output/conf_output.py +0 -213
- contentctl-4.4.6/contentctl/output/conf_writer.py +0 -337
- contentctl-4.4.6/contentctl/output/data_source_writer.py +0 -40
- contentctl-4.4.6/contentctl/output/detection_writer.py +0 -28
- contentctl-4.4.6/contentctl/output/doc_md_output.py +0 -70
- contentctl-4.4.6/contentctl/output/jinja_writer.py +0 -33
- contentctl-4.4.6/contentctl/output/json_writer.py +0 -21
- contentctl-4.4.6/contentctl/output/new_content_yml_output.py +0 -56
- contentctl-4.4.6/contentctl/output/svg_output.py +0 -55
- contentctl-4.4.6/contentctl/output/yml_output.py +0 -66
- contentctl-4.4.6/contentctl/output/yml_writer.py +0 -49
- contentctl-4.4.6/pyproject.toml +0 -36
- {contentctl-4.4.6 → contentctl-5.0.0}/LICENSE.md +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/README.md +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/helper/logger.py +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/app.conf.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/app.manifest.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/content-version.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/detection_count.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/detection_coverage.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_detection_page.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_navigation.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_playbooks.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_stories.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/doc_story_page.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/header.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/macros.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/panel.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/server.conf.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/output/templates/workflow_actions.j2 +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/README.md +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_default.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/README.md +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/default/commands.conf +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/metadata/default.meta +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/static/appIcon.png +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/datamodels_cim.conf +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/datamodels_custom.conf +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/detections/application/.gitkeep +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/detections/cloud/.gitkeep +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/detections/network/.gitkeep +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/detections/web/.gitkeep +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/macros/security_content_ctime.yml +0 -0
- {contentctl-4.4.6 → contentctl-5.0.0}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: contentctl
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Splunk Content Control Tool
|
|
5
5
|
License: Apache 2.0
|
|
6
6
|
Author: STRT
|
|
7
7
|
Author-email: research@splunk.com
|
|
8
|
-
Requires-Python: >=3.11,<3.
|
|
8
|
+
Requires-Python: >=3.11,<3.14
|
|
9
9
|
Classifier: License :: Other/Proprietary License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
14
|
Requires-Dist: Jinja2 (>=3.1.4,<4.0.0)
|
|
14
15
|
Requires-Dist: PyYAML (>=6.0.2,<7.0.0)
|
|
15
16
|
Requires-Dist: attackcti (>=0.4.0,<0.5.0)
|
|
@@ -17,7 +18,7 @@ Requires-Dist: bottle (>=0.12.25,<0.14.0)
|
|
|
17
18
|
Requires-Dist: docker (>=7.1.0,<8.0.0)
|
|
18
19
|
Requires-Dist: gitpython (>=3.1.43,<4.0.0)
|
|
19
20
|
Requires-Dist: pycvesearch (>=1.2,<2.0)
|
|
20
|
-
Requires-Dist: pydantic (>=2.
|
|
21
|
+
Requires-Dist: pydantic (>=2.9.2,<2.10.0)
|
|
21
22
|
Requires-Dist: pygit2 (>=1.15.1,<2.0.0)
|
|
22
23
|
Requires-Dist: questionary (>=2.0.1,<3.0.0)
|
|
23
24
|
Requires-Dist: requests (>=2.32.3,<2.33.0)
|
|
@@ -25,7 +26,7 @@ Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
|
|
|
25
26
|
Requires-Dist: setuptools (>=69.5.1,<76.0.0)
|
|
26
27
|
Requires-Dist: splunk-sdk (>=2.0.2,<3.0.0)
|
|
27
28
|
Requires-Dist: tqdm (>=4.66.5,<5.0.0)
|
|
28
|
-
Requires-Dist: tyro (>=0.
|
|
29
|
+
Requires-Dist: tyro (>=0.9.2,<0.10.0)
|
|
29
30
|
Requires-Dist: xmltodict (>=0.13,<0.15)
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
31
32
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from contentctl.input.director import DirectorOutputDto
|
|
6
|
+
from contentctl.output.conf_output import ConfOutput
|
|
7
|
+
from contentctl.output.conf_writer import ConfWriter
|
|
8
|
+
from contentctl.output.api_json_output import ApiJsonOutput
|
|
9
|
+
from contentctl.output.data_source_writer import DataSourceWriter
|
|
10
|
+
from contentctl.objects.lookup import CSVLookup, Lookup_Type
|
|
11
|
+
import pathlib
|
|
12
|
+
import json
|
|
13
|
+
import datetime
|
|
14
|
+
import uuid
|
|
15
|
+
|
|
16
|
+
from contentctl.objects.config import build
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class BuildInputDto:
|
|
21
|
+
director_output_dto: DirectorOutputDto
|
|
22
|
+
config: build
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Build:
|
|
26
|
+
def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
|
|
27
|
+
if input_dto.config.build_app:
|
|
28
|
+
updated_conf_files: set[pathlib.Path] = set()
|
|
29
|
+
conf_output = ConfOutput(input_dto.config)
|
|
30
|
+
|
|
31
|
+
# Construct a path to a YML that does not actually exist.
|
|
32
|
+
# We mock this "fake" path since the YML does not exist.
|
|
33
|
+
# This ensures the checking for the existence of the CSV is correct
|
|
34
|
+
data_sources_fake_yml_path = (
|
|
35
|
+
input_dto.config.getPackageDirectoryPath()
|
|
36
|
+
/ "lookups"
|
|
37
|
+
/ "data_sources.yml"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Construct a special lookup whose CSV is created at runtime and
|
|
41
|
+
# written directly into the lookups folder. We will delete this after a build,
|
|
42
|
+
# assuming that it is successful.
|
|
43
|
+
data_sources_lookup_csv_path = (
|
|
44
|
+
input_dto.config.getPackageDirectoryPath()
|
|
45
|
+
/ "lookups"
|
|
46
|
+
/ "data_sources.csv"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
DataSourceWriter.writeDataSourceCsv(
|
|
50
|
+
input_dto.director_output_dto.data_sources, data_sources_lookup_csv_path
|
|
51
|
+
)
|
|
52
|
+
input_dto.director_output_dto.addContentToDictMappings(
|
|
53
|
+
CSVLookup.model_construct(
|
|
54
|
+
name="data_sources",
|
|
55
|
+
id=uuid.UUID("b45c1403-6e09-47b0-824f-cf6e44f15ac8"),
|
|
56
|
+
version=1,
|
|
57
|
+
author=input_dto.config.app.author_name,
|
|
58
|
+
date=datetime.date.today(),
|
|
59
|
+
description="A lookup file that will contain the data source objects for detections.",
|
|
60
|
+
lookup_type=Lookup_Type.csv,
|
|
61
|
+
file_path=data_sources_fake_yml_path,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
updated_conf_files.update(conf_output.writeHeaders())
|
|
65
|
+
updated_conf_files.update(
|
|
66
|
+
conf_output.writeLookups(input_dto.director_output_dto.lookups)
|
|
67
|
+
)
|
|
68
|
+
updated_conf_files.update(
|
|
69
|
+
conf_output.writeDetections(input_dto.director_output_dto.detections)
|
|
70
|
+
)
|
|
71
|
+
updated_conf_files.update(
|
|
72
|
+
conf_output.writeStories(input_dto.director_output_dto.stories)
|
|
73
|
+
)
|
|
74
|
+
updated_conf_files.update(
|
|
75
|
+
conf_output.writeBaselines(input_dto.director_output_dto.baselines)
|
|
76
|
+
)
|
|
77
|
+
updated_conf_files.update(
|
|
78
|
+
conf_output.writeInvestigations(
|
|
79
|
+
input_dto.director_output_dto.investigations
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
updated_conf_files.update(
|
|
83
|
+
conf_output.writeMacros(input_dto.director_output_dto.macros)
|
|
84
|
+
)
|
|
85
|
+
updated_conf_files.update(
|
|
86
|
+
conf_output.writeDashboards(input_dto.director_output_dto.dashboards)
|
|
87
|
+
)
|
|
88
|
+
updated_conf_files.update(conf_output.writeMiscellaneousAppFiles())
|
|
89
|
+
|
|
90
|
+
# Ensure that the conf file we just generated/update is syntactically valid
|
|
91
|
+
for conf_file in updated_conf_files:
|
|
92
|
+
ConfWriter.validateConfFile(conf_file)
|
|
93
|
+
|
|
94
|
+
conf_output.packageApp()
|
|
95
|
+
|
|
96
|
+
print(
|
|
97
|
+
f"Build of '{input_dto.config.app.title}' APP successful to {input_dto.config.getPackageFilePath()}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if input_dto.config.build_api:
|
|
101
|
+
shutil.rmtree(input_dto.config.getAPIPath(), ignore_errors=True)
|
|
102
|
+
input_dto.config.getAPIPath().mkdir(parents=True)
|
|
103
|
+
api_json_output = ApiJsonOutput(
|
|
104
|
+
input_dto.config.getAPIPath(), input_dto.config.app.label
|
|
105
|
+
)
|
|
106
|
+
api_json_output.writeDetections(input_dto.director_output_dto.detections)
|
|
107
|
+
api_json_output.writeStories(input_dto.director_output_dto.stories)
|
|
108
|
+
api_json_output.writeBaselines(input_dto.director_output_dto.baselines)
|
|
109
|
+
api_json_output.writeInvestigations(
|
|
110
|
+
input_dto.director_output_dto.investigations
|
|
111
|
+
)
|
|
112
|
+
api_json_output.writeLookups(input_dto.director_output_dto.lookups)
|
|
113
|
+
api_json_output.writeMacros(input_dto.director_output_dto.macros)
|
|
114
|
+
api_json_output.writeDeployments(input_dto.director_output_dto.deployments)
|
|
115
|
+
|
|
116
|
+
# create version file for sse api
|
|
117
|
+
version_file = input_dto.config.getAPIPath() / "version.json"
|
|
118
|
+
utc_time = (
|
|
119
|
+
datetime.datetime.now(datetime.timezone.utc)
|
|
120
|
+
.replace(microsecond=0, tzinfo=None)
|
|
121
|
+
.isoformat()
|
|
122
|
+
)
|
|
123
|
+
version_dict = {
|
|
124
|
+
"version": {
|
|
125
|
+
"name": f"v{input_dto.config.app.version}",
|
|
126
|
+
"published_at": f"{utc_time}Z",
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
with open(version_file, "w") as version_f:
|
|
130
|
+
json.dump(version_dict, version_f)
|
|
131
|
+
|
|
132
|
+
print(
|
|
133
|
+
f"Build of '{input_dto.config.app.title}' API successful to {input_dto.config.getAPIPath()}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return input_dto.director_output_dto
|
|
@@ -4,52 +4,57 @@ import pprint
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class Deploy:
|
|
7
|
-
def execute(self, config: deploy_acs, appinspect_token:str) -> None:
|
|
8
|
-
|
|
9
|
-
#The following common headers are used by both Clasic and Victoria
|
|
7
|
+
def execute(self, config: deploy_acs, appinspect_token: str) -> None:
|
|
8
|
+
# The following common headers are used by both Clasic and Victoria
|
|
10
9
|
headers = {
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
"Authorization": f"Bearer {config.splunk_cloud_jwt_token}",
|
|
11
|
+
"ACS-Legal-Ack": "Y",
|
|
13
12
|
}
|
|
14
13
|
try:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
with open(
|
|
15
|
+
config.getPackageFilePath(include_version=False), "rb"
|
|
16
|
+
) as app_data:
|
|
17
|
+
# request_data = app_data.read()
|
|
18
18
|
if config.stack_type == StackType.classic:
|
|
19
19
|
# Classic instead uses a form to store token and package
|
|
20
20
|
# https://docs.splunk.com/Documentation/SplunkCloud/9.1.2308/Config/ManageApps#Manage_private_apps_using_the_ACS_API_on_Classic_Experience
|
|
21
21
|
address = f"https://admin.splunk.com/{config.splunk_cloud_stack}/adminconfig/v2/apps"
|
|
22
|
-
|
|
23
|
-
form_data = {
|
|
24
|
-
|
|
25
|
-
'package': app_data
|
|
26
|
-
}
|
|
27
|
-
res = post(address, headers=headers, files = form_data)
|
|
22
|
+
|
|
23
|
+
form_data = {"token": (None, appinspect_token), "package": app_data}
|
|
24
|
+
res = post(address, headers=headers, files=form_data)
|
|
28
25
|
elif config.stack_type == StackType.victoria:
|
|
29
26
|
# Victoria uses the X-Splunk-Authorization Header
|
|
30
27
|
# It also uses --data-binary for the app content
|
|
31
28
|
# https://docs.splunk.com/Documentation/SplunkCloud/9.1.2308/Config/ManageApps#Manage_private_apps_using_the_ACS_API_on_Victoria_Experience
|
|
32
|
-
headers.update({
|
|
29
|
+
headers.update({"X-Splunk-Authorization": appinspect_token})
|
|
33
30
|
address = f"https://admin.splunk.com/{config.splunk_cloud_stack}/adminconfig/v2/apps/victoria"
|
|
34
31
|
res = post(address, headers=headers, data=app_data.read())
|
|
35
32
|
else:
|
|
36
33
|
raise Exception(f"Unsupported stack type: '{config.stack_type}'")
|
|
37
34
|
except Exception as e:
|
|
38
|
-
raise Exception(
|
|
39
|
-
|
|
35
|
+
raise Exception(
|
|
36
|
+
f"Error installing to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS:\n{str(e)}"
|
|
37
|
+
)
|
|
38
|
+
|
|
40
39
|
try:
|
|
41
40
|
# Request went through and completed, but may have returned a non-successful error code.
|
|
42
41
|
# This likely includes a more verbose response describing the error
|
|
43
42
|
res.raise_for_status()
|
|
44
43
|
print(res.json())
|
|
45
|
-
except Exception
|
|
44
|
+
except Exception:
|
|
46
45
|
try:
|
|
47
46
|
error_text = res.json()
|
|
48
|
-
except Exception
|
|
47
|
+
except Exception:
|
|
49
48
|
error_text = "No error text - request failed"
|
|
50
49
|
formatted_error_text = pprint.pformat(error_text)
|
|
51
|
-
print(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
print(
|
|
51
|
+
"While this may not be the cause of your error, ensure that the uid and appid of your Private App does not exist in Splunkbase\n"
|
|
52
|
+
"ACS cannot deploy and app with the same uid or appid as one that exists in Splunkbase."
|
|
53
|
+
)
|
|
54
|
+
raise Exception(
|
|
55
|
+
f"Error installing to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS:\n{formatted_error_text}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
print(
|
|
59
|
+
f"'{config.getPackageFilePath(include_version=False)}' successfully installed to stack '{config.splunk_cloud_stack}' (stack_type='{config.stack_type}') via ACS!"
|
|
60
|
+
)
|
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
from typing import List,Union
|
|
2
|
-
from contentctl.objects.config import test, test_servers, Container,Infrastructure
|
|
3
|
-
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
from contentctl.objects.config import test, test_servers, Container, Infrastructure
|
|
3
|
+
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
|
|
4
|
+
DetectionTestingInfrastructure,
|
|
5
|
+
)
|
|
6
|
+
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureContainer import (
|
|
7
|
+
DetectionTestingInfrastructureContainer,
|
|
8
|
+
)
|
|
9
|
+
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureServer import (
|
|
10
|
+
DetectionTestingInfrastructureServer,
|
|
11
|
+
)
|
|
9
12
|
import signal
|
|
10
13
|
import datetime
|
|
14
|
+
|
|
11
15
|
# from queue import Queue
|
|
12
16
|
from dataclasses import dataclass
|
|
17
|
+
|
|
13
18
|
# import threading
|
|
14
|
-
import ctypes
|
|
15
19
|
from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
|
|
16
|
-
DetectionTestingInfrastructure,
|
|
17
20
|
DetectionTestingManagerOutputDto,
|
|
18
21
|
)
|
|
19
22
|
from contentctl.actions.detection_testing.views.DetectionTestingView import (
|
|
20
23
|
DetectionTestingView,
|
|
21
24
|
)
|
|
22
25
|
from contentctl.objects.enums import PostTestBehavior
|
|
23
|
-
from pydantic import BaseModel
|
|
26
|
+
from pydantic import BaseModel
|
|
24
27
|
from contentctl.objects.detection import Detection
|
|
25
28
|
import concurrent.futures
|
|
26
29
|
import docker
|
|
@@ -28,7 +31,7 @@ import docker
|
|
|
28
31
|
|
|
29
32
|
@dataclass(frozen=False)
|
|
30
33
|
class DetectionTestingManagerInputDto:
|
|
31
|
-
config: Union[test,test_servers]
|
|
34
|
+
config: Union[test, test_servers]
|
|
32
35
|
detections: List[Detection]
|
|
33
36
|
views: list[DetectionTestingView]
|
|
34
37
|
|
|
@@ -65,15 +68,18 @@ class DetectionTestingManager(BaseModel):
|
|
|
65
68
|
print("*******************************")
|
|
66
69
|
|
|
67
70
|
signal.signal(signal.SIGINT, sigint_handler)
|
|
68
|
-
|
|
69
|
-
with concurrent.futures.ThreadPoolExecutor(
|
|
70
|
-
max_workers=len(self.input_dto.config.test_instances),
|
|
71
|
-
) as instance_pool, concurrent.futures.ThreadPoolExecutor(
|
|
72
|
-
max_workers=len(self.input_dto.views)
|
|
73
|
-
) as view_runner, concurrent.futures.ThreadPoolExecutor(
|
|
74
|
-
max_workers=len(self.input_dto.config.test_instances),
|
|
75
|
-
) as view_shutdowner:
|
|
76
71
|
|
|
72
|
+
with (
|
|
73
|
+
concurrent.futures.ThreadPoolExecutor(
|
|
74
|
+
max_workers=len(self.input_dto.config.test_instances),
|
|
75
|
+
) as instance_pool,
|
|
76
|
+
concurrent.futures.ThreadPoolExecutor(
|
|
77
|
+
max_workers=len(self.input_dto.views)
|
|
78
|
+
) as view_runner,
|
|
79
|
+
concurrent.futures.ThreadPoolExecutor(
|
|
80
|
+
max_workers=len(self.input_dto.config.test_instances),
|
|
81
|
+
) as view_shutdowner,
|
|
82
|
+
):
|
|
77
83
|
# Start all the views
|
|
78
84
|
future_views = {
|
|
79
85
|
view_runner.submit(view.setup): view for view in self.input_dto.views
|
|
@@ -87,7 +93,7 @@ class DetectionTestingManager(BaseModel):
|
|
|
87
93
|
# Wait for all instances to be set up
|
|
88
94
|
for future in concurrent.futures.as_completed(future_instances_setup):
|
|
89
95
|
try:
|
|
90
|
-
|
|
96
|
+
future.result()
|
|
91
97
|
except Exception as e:
|
|
92
98
|
self.output_dto.terminate = True
|
|
93
99
|
print(f"Error setting up container: {str(e)}")
|
|
@@ -102,7 +108,7 @@ class DetectionTestingManager(BaseModel):
|
|
|
102
108
|
# Wait for execution to finish
|
|
103
109
|
for future in concurrent.futures.as_completed(future_instances_execute):
|
|
104
110
|
try:
|
|
105
|
-
|
|
111
|
+
future.result()
|
|
106
112
|
except Exception as e:
|
|
107
113
|
self.output_dto.terminate = True
|
|
108
114
|
print(f"Error running in container: {str(e)}")
|
|
@@ -115,34 +121,43 @@ class DetectionTestingManager(BaseModel):
|
|
|
115
121
|
}
|
|
116
122
|
for future in concurrent.futures.as_completed(future_views_shutdowner):
|
|
117
123
|
try:
|
|
118
|
-
|
|
124
|
+
future.result()
|
|
119
125
|
except Exception as e:
|
|
120
126
|
print(f"Error stopping view: {str(e)}")
|
|
121
127
|
|
|
122
128
|
# Wait for original view-related threads to complete
|
|
123
129
|
for future in concurrent.futures.as_completed(future_views):
|
|
124
130
|
try:
|
|
125
|
-
|
|
131
|
+
future.result()
|
|
126
132
|
except Exception as e:
|
|
127
133
|
print(f"Error running container: {str(e)}")
|
|
128
134
|
|
|
129
135
|
return self.output_dto
|
|
130
136
|
|
|
131
137
|
def create_DetectionTestingInfrastructureObjects(self):
|
|
132
|
-
#Make sure that, if we need to, we pull the appropriate container
|
|
138
|
+
# Make sure that, if we need to, we pull the appropriate container
|
|
133
139
|
for infrastructure in self.input_dto.config.test_instances:
|
|
134
|
-
if
|
|
140
|
+
if isinstance(self.input_dto.config, test) and isinstance(
|
|
141
|
+
infrastructure, Container
|
|
142
|
+
):
|
|
135
143
|
try:
|
|
136
144
|
client = docker.from_env()
|
|
137
|
-
except Exception
|
|
138
|
-
raise Exception(
|
|
145
|
+
except Exception:
|
|
146
|
+
raise Exception(
|
|
147
|
+
"Unable to connect to docker. Are you sure that docker is running on this host?"
|
|
148
|
+
)
|
|
139
149
|
try:
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
parts = (
|
|
151
|
+
self.input_dto.config.container_settings.full_image_path.split(
|
|
152
|
+
":"
|
|
153
|
+
)
|
|
154
|
+
)
|
|
142
155
|
if len(parts) != 2:
|
|
143
|
-
raise Exception(
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
raise Exception(
|
|
157
|
+
f"Expected to find a name:tag in {self.input_dto.config.container_settings.full_image_path}, "
|
|
158
|
+
f"but instead found {parts}. Note that this path MUST include the tag, which is separated by ':'"
|
|
159
|
+
)
|
|
160
|
+
|
|
146
161
|
print(
|
|
147
162
|
f"Getting the latest version of the container image [{self.input_dto.config.container_settings.full_image_path}]...",
|
|
148
163
|
end="",
|
|
@@ -152,12 +167,15 @@ class DetectionTestingManager(BaseModel):
|
|
|
152
167
|
print("done!")
|
|
153
168
|
break
|
|
154
169
|
except Exception as e:
|
|
155
|
-
raise Exception(
|
|
170
|
+
raise Exception(
|
|
171
|
+
f"Failed to pull docker container image [{self.input_dto.config.container_settings.full_image_path}]: {str(e)}"
|
|
172
|
+
)
|
|
156
173
|
|
|
157
174
|
already_staged_container_files = False
|
|
158
175
|
for infrastructure in self.input_dto.config.test_instances:
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
if isinstance(self.input_dto.config, test) and isinstance(
|
|
177
|
+
infrastructure, Container
|
|
178
|
+
):
|
|
161
179
|
# Stage the files in the apps dir so that they can be passed directly to
|
|
162
180
|
# subsequent containers. Do this here, instead of inside each container, to
|
|
163
181
|
# avoid duplicate downloads/moves/copies
|
|
@@ -167,18 +185,24 @@ class DetectionTestingManager(BaseModel):
|
|
|
167
185
|
|
|
168
186
|
self.detectionTestingInfrastructureObjects.append(
|
|
169
187
|
DetectionTestingInfrastructureContainer(
|
|
170
|
-
global_config=self.input_dto.config,
|
|
188
|
+
global_config=self.input_dto.config,
|
|
189
|
+
infrastructure=infrastructure,
|
|
190
|
+
sync_obj=self.output_dto,
|
|
171
191
|
)
|
|
172
192
|
)
|
|
173
193
|
|
|
174
|
-
elif
|
|
194
|
+
elif isinstance(self.input_dto.config, test_servers) and isinstance(
|
|
195
|
+
infrastructure, Infrastructure
|
|
196
|
+
):
|
|
175
197
|
self.detectionTestingInfrastructureObjects.append(
|
|
176
198
|
DetectionTestingInfrastructureServer(
|
|
177
|
-
global_config=self.input_dto.config,
|
|
199
|
+
global_config=self.input_dto.config,
|
|
200
|
+
infrastructure=infrastructure,
|
|
201
|
+
sync_obj=self.output_dto,
|
|
178
202
|
)
|
|
179
203
|
)
|
|
180
204
|
|
|
181
205
|
else:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
raise Exception(
|
|
207
|
+
f"Unsupported target infrastructure '{infrastructure}' and config type {self.input_dto.config}"
|
|
208
|
+
)
|