contentctl 5.0.2__tar.gz → 5.0.4__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.
Files changed (169) hide show
  1. {contentctl-5.0.2 → contentctl-5.0.4}/PKG-INFO +1 -1
  2. contentctl-5.0.4/contentctl/objects/annotated_types.py +9 -0
  3. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/constants.py +2 -0
  4. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/dashboard.py +15 -9
  5. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/detection_tags.py +57 -4
  6. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/mitre_attack_enrichment.py +16 -3
  7. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/story_tags.py +6 -5
  8. contentctl-5.0.4/contentctl/templates/app_template/default/data/ui/nav/default.xml +7 -0
  9. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -1
  10. {contentctl-5.0.2 → contentctl-5.0.4}/pyproject.toml +1 -1
  11. contentctl-5.0.2/contentctl/objects/annotated_types.py +0 -6
  12. contentctl-5.0.2/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -7
  13. {contentctl-5.0.2 → contentctl-5.0.4}/LICENSE.md +0 -0
  14. {contentctl-5.0.2 → contentctl-5.0.4}/README.md +0 -0
  15. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/__init__.py +0 -0
  16. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/build.py +0 -0
  17. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/deploy_acs.py +0 -0
  18. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
  19. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/GitService.py +0 -0
  20. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
  21. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -0
  22. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
  23. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
  24. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/progress_bar.py +0 -0
  25. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
  26. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
  27. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
  28. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
  29. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/doc_gen.py +0 -0
  30. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/initialize.py +0 -0
  31. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/inspect.py +0 -0
  32. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/new_content.py +0 -0
  33. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/release_notes.py +0 -0
  34. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/reporting.py +0 -0
  35. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/test.py +0 -0
  36. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/actions/validate.py +0 -0
  37. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/api.py +0 -0
  38. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/contentctl.py +0 -0
  39. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/enrichments/attack_enrichment.py +0 -0
  40. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/enrichments/cve_enrichment.py +0 -0
  41. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
  42. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/helper/link_validator.py +0 -0
  43. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/helper/logger.py +0 -0
  44. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/helper/splunk_app.py +0 -0
  45. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/helper/utils.py +0 -0
  46. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/input/director.py +0 -0
  47. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/input/new_content_questions.py +0 -0
  48. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/input/yml_reader.py +0 -0
  49. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +0 -0
  50. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +0 -0
  51. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/alert_action.py +0 -0
  52. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/atomic.py +0 -0
  53. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/base_test.py +0 -0
  54. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/base_test_result.py +0 -0
  55. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/baseline.py +0 -0
  56. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/baseline_tags.py +0 -0
  57. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/config.py +0 -0
  58. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/correlation_search.py +0 -0
  59. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/data_source.py +0 -0
  60. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/deployment.py +0 -0
  61. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/deployment_email.py +0 -0
  62. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/deployment_notable.py +0 -0
  63. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/deployment_phantom.py +0 -0
  64. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/deployment_rba.py +0 -0
  65. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/deployment_scheduling.py +0 -0
  66. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/deployment_slack.py +0 -0
  67. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/detection.py +0 -0
  68. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/detection_metadata.py +0 -0
  69. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/detection_stanza.py +0 -0
  70. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/drilldown.py +0 -0
  71. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/enums.py +0 -0
  72. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/errors.py +0 -0
  73. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/integration_test.py +0 -0
  74. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/integration_test_result.py +0 -0
  75. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/investigation.py +0 -0
  76. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/investigation_tags.py +0 -0
  77. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/lookup.py +0 -0
  78. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/macro.py +0 -0
  79. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/manual_test.py +0 -0
  80. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/manual_test_result.py +0 -0
  81. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/notable_action.py +0 -0
  82. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/notable_event.py +0 -0
  83. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/playbook.py +0 -0
  84. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/playbook_tags.py +0 -0
  85. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/rba.py +0 -0
  86. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/risk_analysis_action.py +0 -0
  87. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/risk_event.py +0 -0
  88. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/risk_object.py +0 -0
  89. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/savedsearches_conf.py +0 -0
  90. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/security_content_object.py +0 -0
  91. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/story.py +0 -0
  92. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/test_attack_data.py +0 -0
  93. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/test_group.py +0 -0
  94. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/threat_object.py +0 -0
  95. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/throttling.py +0 -0
  96. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/unit_test.py +0 -0
  97. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/unit_test_baseline.py +0 -0
  98. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/objects/unit_test_result.py +0 -0
  99. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/api_json_output.py +0 -0
  100. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/attack_nav_output.py +0 -0
  101. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/attack_nav_writer.py +0 -0
  102. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/conf_output.py +0 -0
  103. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/conf_writer.py +0 -0
  104. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/data_source_writer.py +0 -0
  105. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/doc_md_output.py +0 -0
  106. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/jinja_writer.py +0 -0
  107. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/json_writer.py +0 -0
  108. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/svg_output.py +0 -0
  109. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
  110. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
  111. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
  112. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/app.conf.j2 +0 -0
  113. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/app.manifest.j2 +0 -0
  114. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/collections.j2 +0 -0
  115. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/content-version.j2 +0 -0
  116. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/detection_count.j2 +0 -0
  117. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/detection_coverage.j2 +0 -0
  118. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_detection_page.j2 +0 -0
  119. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_detections.j2 +0 -0
  120. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_navigation.j2 +0 -0
  121. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
  122. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_playbooks.j2 +0 -0
  123. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
  124. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_stories.j2 +0 -0
  125. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/doc_story_page.j2 +0 -0
  126. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
  127. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
  128. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/header.j2 +0 -0
  129. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/macros.j2 +0 -0
  130. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/panel.j2 +0 -0
  131. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
  132. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/savedsearches_detections.j2 +0 -0
  133. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
  134. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/server.conf.j2 +0 -0
  135. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/transforms.j2 +0 -0
  136. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/templates/workflow_actions.j2 +0 -0
  137. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/output/yml_writer.py +0 -0
  138. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/README.md +0 -0
  139. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_default.yml +0 -0
  140. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
  141. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
  142. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
  143. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/README.md +0 -0
  144. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
  145. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/default/commands.conf +0 -0
  146. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
  147. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
  148. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
  149. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
  150. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/metadata/default.meta +0 -0
  151. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/static/appIcon.png +0 -0
  152. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
  153. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
  154. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
  155. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
  156. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/datamodels_cim.conf +0 -0
  157. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/datamodels_custom.conf +0 -0
  158. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
  159. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
  160. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
  161. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
  162. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
  163. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/detections/application/.gitkeep +0 -0
  164. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/detections/cloud/.gitkeep +0 -0
  165. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/detections/network/.gitkeep +0 -0
  166. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/detections/web/.gitkeep +0 -0
  167. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/macros/security_content_ctime.yml +0 -0
  168. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
  169. {contentctl-5.0.2 → contentctl-5.0.4}/contentctl/templates/stories/cobalt_strike.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: contentctl
3
- Version: 5.0.2
3
+ Version: 5.0.4
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -0,0 +1,9 @@
1
+ from typing import Annotated
2
+
3
+ from pydantic import Field
4
+
5
+ CVE_TYPE = Annotated[str, Field(pattern=r"^CVE-[1|2]\d{3}-\d+$")]
6
+ MITRE_ATTACK_ID_TYPE_PARENT = Annotated[str, Field(pattern=r"^T\d{4}$")]
7
+ MITRE_ATTACK_ID_TYPE_SUBTYPE = Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})$")]
8
+ MITRE_ATTACK_ID_TYPE = MITRE_ATTACK_ID_TYPE_PARENT | MITRE_ATTACK_ID_TYPE_SUBTYPE
9
+ APPID_TYPE = Annotated[str, Field(pattern="^[a-zA-Z0-9_-]+$")]
@@ -123,6 +123,8 @@ ES_MAX_STANZA_LENGTH = 99
123
123
  CONTENTCTL_DETECTION_STANZA_NAME_FORMAT_TEMPLATE = (
124
124
  "{app_label} - {detection_name} - Rule"
125
125
  )
126
+
127
+ CONTENTCTL_DASHBOARD_LABEL_TEMPLATE = "{app_label} - {dashboard_name}"
126
128
  CONTENTCTL_BASELINE_STANZA_NAME_FORMAT_TEMPLATE = "{app_label} - {detection_name}"
127
129
  CONTENTCTL_RESPONSE_TASK_NAME_FORMAT_TEMPLATE = (
128
130
  "{app_label} - {detection_name} - Response Task"
@@ -1,14 +1,16 @@
1
+ import json
2
+ import pathlib
3
+ from enum import StrEnum
1
4
  from typing import Any
2
- from pydantic import Field, Json, model_validator
3
5
 
4
- import pathlib
5
6
  from jinja2 import Environment
6
- import json
7
- from contentctl.objects.security_content_object import SecurityContentObject
7
+ from pydantic import Field, Json, model_validator
8
+
8
9
  from contentctl.objects.config import build
9
- from enum import StrEnum
10
+ from contentctl.objects.constants import CONTENTCTL_DASHBOARD_LABEL_TEMPLATE
11
+ from contentctl.objects.security_content_object import SecurityContentObject
10
12
 
11
- DEFAULT_DASHBAORD_JINJA2_TEMPLATE = """<dashboard version="2" theme="{{ dashboard.theme }}">
13
+ DEFAULT_DASHBOARD_JINJA2_TEMPLATE = """<dashboard version="2" theme="{{ dashboard.theme }}">
12
14
  <label>{{ dashboard.label(config) }}</label>
13
15
  <description></description>
14
16
  <definition><![CDATA[
@@ -31,7 +33,7 @@ class DashboardTheme(StrEnum):
31
33
 
32
34
  class Dashboard(SecurityContentObject):
33
35
  j2_template: str = Field(
34
- default=DEFAULT_DASHBAORD_JINJA2_TEMPLATE,
36
+ default=DEFAULT_DASHBOARD_JINJA2_TEMPLATE,
35
37
  description="Jinja2 Template used to construct the dashboard",
36
38
  )
37
39
  description: str = Field(
@@ -49,7 +51,9 @@ class Dashboard(SecurityContentObject):
49
51
  )
50
52
 
51
53
  def label(self, config: build) -> str:
52
- return f"{config.app.label} - {self.name}"
54
+ return CONTENTCTL_DASHBOARD_LABEL_TEMPLATE.format(
55
+ app_label=config.app.label, dashboard_name=self.name
56
+ )
53
57
 
54
58
  @model_validator(mode="before")
55
59
  @classmethod
@@ -98,7 +102,9 @@ class Dashboard(SecurityContentObject):
98
102
  return json.dumps(self.json_obj, indent=4)
99
103
 
100
104
  def getOutputFilepathRelativeToAppRoot(self, config: build) -> pathlib.Path:
101
- filename = f"{self.file_path.stem}.xml".lower()
105
+ # for clarity, the name of the dashboard file will follow the same convention
106
+ # as we use for detections, prefixing it with app_name -
107
+ filename = f"{self.label(config)}.xml"
102
108
  return pathlib.Path("default/data/ui/views") / filename
103
109
 
104
110
  def writeDashboardFile(self, j2_env: Environment, config: build):
@@ -33,7 +33,10 @@ from contentctl.objects.enums import (
33
33
  SecurityContentProductName,
34
34
  SecurityDomain,
35
35
  )
36
- from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
36
+ from contentctl.objects.mitre_attack_enrichment import (
37
+ MitreAttackEnrichment,
38
+ MitreAttackGroup,
39
+ )
37
40
 
38
41
 
39
42
  class DetectionTags(BaseModel):
@@ -44,7 +47,7 @@ class DetectionTags(BaseModel):
44
47
  asset_type: AssetType = Field(...)
45
48
  group: list[str] = []
46
49
 
47
- mitre_attack_id: List[MITRE_ATTACK_ID_TYPE] = []
50
+ mitre_attack_id: list[MITRE_ATTACK_ID_TYPE] = []
48
51
  nist: list[NistCategory] = []
49
52
 
50
53
  product: list[SecurityContentProductName] = Field(..., min_length=1)
@@ -68,6 +71,15 @@ class DetectionTags(BaseModel):
68
71
  phases.add(phase)
69
72
  return sorted(list(phases))
70
73
 
74
+ # We do not want this to be included in serialization. By default, @property
75
+ # objects are not included in dumps
76
+ @property
77
+ def unique_mitre_attack_groups(self) -> list[MitreAttackGroup]:
78
+ group_set: set[MitreAttackGroup] = set()
79
+ for enrichment in self.mitre_attack_enrichments:
80
+ group_set.update(set(enrichment.mitre_attack_group_objects))
81
+ return sorted(group_set, key=lambda k: k.group)
82
+
71
83
  # enum is intentionally Cis18 even though field is named cis20 for legacy reasons
72
84
  @computed_field
73
85
  @property
@@ -134,8 +146,8 @@ class DetectionTags(BaseModel):
134
146
 
135
147
  if len(missing_tactics) > 0:
136
148
  raise ValueError(f"Missing Mitre Attack IDs. {missing_tactics} not found.")
137
- else:
138
- self.mitre_attack_enrichments = mitre_enrichments
149
+
150
+ self.mitre_attack_enrichments = mitre_enrichments
139
151
 
140
152
  return self
141
153
 
@@ -159,6 +171,44 @@ class DetectionTags(BaseModel):
159
171
  return enrichments
160
172
  """
161
173
 
174
+ @field_validator("mitre_attack_id", mode="after")
175
+ @classmethod
176
+ def sameTypeAndSubtypeNotPresent(
177
+ cls, techniques_and_subtechniques: list[MITRE_ATTACK_ID_TYPE]
178
+ ) -> list[MITRE_ATTACK_ID_TYPE]:
179
+ techniques: list[str] = [
180
+ f"{unknown_technique}."
181
+ for unknown_technique in techniques_and_subtechniques
182
+ if "." not in unknown_technique
183
+ ]
184
+ subtechniques: list[MITRE_ATTACK_ID_TYPE] = [
185
+ unknown_technique
186
+ for unknown_technique in techniques_and_subtechniques
187
+ if "." in unknown_technique
188
+ ]
189
+ subtype_and_parent_exist_exceptions: list[ValueError] = []
190
+
191
+ for subtechnique in subtechniques:
192
+ for technique in techniques:
193
+ if subtechnique.startswith(technique):
194
+ subtype_and_parent_exist_exceptions.append(
195
+ ValueError(
196
+ f" Technique : {technique.split('.')[0]}\n"
197
+ f" SubTechnique: {subtechnique}\n"
198
+ )
199
+ )
200
+
201
+ if len(subtype_and_parent_exist_exceptions):
202
+ error_string = "\n".join(
203
+ str(e) for e in subtype_and_parent_exist_exceptions
204
+ )
205
+ raise ValueError(
206
+ "Overlapping MITRE Attack ID Techniques and Subtechniques may not be defined. "
207
+ f"Remove the Technique and keep the Subtechnique:\n{error_string}"
208
+ )
209
+
210
+ return techniques_and_subtechniques
211
+
162
212
  @field_validator("analytic_story", mode="before")
163
213
  @classmethod
164
214
  def mapStoryNamesToStoryObjects(
@@ -238,3 +288,6 @@ class DetectionTags(BaseModel):
238
288
  return matched_tests + [
239
289
  AtomicTest.AtomicTestWhenTestIsMissing(test) for test in missing_tests
240
290
  ]
291
+ return matched_tests + [
292
+ AtomicTest.AtomicTestWhenTestIsMissing(test) for test in missing_tests
293
+ ]
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
- from pydantic import BaseModel, Field, ConfigDict, HttpUrl, field_validator
3
- from typing import List
4
- from enum import StrEnum
2
+
5
3
  import datetime
4
+ from enum import StrEnum
5
+ from typing import List
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator
8
+
6
9
  from contentctl.objects.annotated_types import MITRE_ATTACK_ID_TYPE
7
10
 
8
11
 
@@ -84,6 +87,16 @@ class MitreAttackGroup(BaseModel):
84
87
  return []
85
88
  return contributors
86
89
 
90
+ def __lt__(self, other: MitreAttackGroup) -> bool:
91
+ if not isinstance(object, MitreAttackGroup):
92
+ raise Exception(
93
+ f"Cannot compare object of type MitreAttackGroup to object of type [{type(object).__name__}]"
94
+ )
95
+ return self.group < other.group
96
+
97
+ def __hash__(self) -> int:
98
+ return hash(self.group)
99
+
87
100
 
88
101
  class MitreAttackEnrichment(BaseModel):
89
102
  ConfigDict(extra="forbid")
@@ -1,17 +1,18 @@
1
1
  from __future__ import annotations
2
- from pydantic import BaseModel, Field, model_serializer, ConfigDict
3
- from typing import List, Set, Optional
4
2
 
5
3
  from enum import Enum
4
+ from typing import List, Optional, Set
6
5
 
7
- from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
6
+ from pydantic import BaseModel, ConfigDict, Field, model_serializer
7
+
8
+ from contentctl.objects.annotated_types import CVE_TYPE, MITRE_ATTACK_ID_TYPE
8
9
  from contentctl.objects.enums import (
9
- StoryCategory,
10
10
  DataModel,
11
11
  KillChainPhase,
12
12
  SecurityContentProductName,
13
+ StoryCategory,
13
14
  )
14
- from contentctl.objects.annotated_types import CVE_TYPE, MITRE_ATTACK_ID_TYPE
15
+ from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
15
16
 
16
17
 
17
18
  class StoryUseCase(str, Enum):
@@ -0,0 +1,7 @@
1
+ <nav search_view="search" color="#65A637">
2
+ <view name="escu_summary" default="true"/>
3
+ <view name="search"/>
4
+ <collection label="Dashboards">
5
+ <view source="unclassified" match=" - "/>
6
+ </collection>
7
+ </nav>
@@ -60,7 +60,6 @@ tags:
60
60
  asset_type: Endpoint
61
61
  mitre_attack_id:
62
62
  - T1560.001
63
- - T1560
64
63
  product:
65
64
  - Splunk Enterprise
66
65
  - Splunk Enterprise Security
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "contentctl"
3
- version = "5.0.2"
3
+ version = "5.0.4"
4
4
 
5
5
  description = "Splunk Content Control Tool"
6
6
  authors = ["STRT <research@splunk.com>"]
@@ -1,6 +0,0 @@
1
- from pydantic import Field
2
- from typing import Annotated
3
-
4
- CVE_TYPE = Annotated[str, Field(pattern=r"^CVE-[1|2]\d{3}-\d+$")]
5
- MITRE_ATTACK_ID_TYPE = Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})?$")]
6
- APPID_TYPE = Annotated[str, Field(pattern="^[a-zA-Z0-9_-]+$")]
@@ -1,7 +0,0 @@
1
- <nav search_view="search" color="#65A637">
2
- <view name="escu_summary" default="true"/>
3
- <view name="feedback"/>
4
- <view name="search"/>
5
- <view name="dashboards"/>
6
- <a href="http://docs.splunk.com/Documentation/ESSOC">Docs</a>
7
- </nav>
File without changes
File without changes
File without changes