contentctl 5.0.0a3__tar.gz → 5.0.1__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 (167) hide show
  1. {contentctl-5.0.0a3 → contentctl-5.0.1}/PKG-INFO +1 -1
  2. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/new_content.py +12 -10
  3. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +11 -15
  4. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +53 -10
  5. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/baseline.py +12 -11
  6. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/constants.py +3 -0
  7. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/investigation.py +14 -7
  8. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/rba.py +55 -4
  9. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/story.py +11 -7
  10. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/analyticstories_detections.j2 +1 -1
  11. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/analyticstories_stories.j2 +1 -1
  12. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/es_investigations_investigations.j2 +1 -1
  13. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/es_investigations_stories.j2 +1 -1
  14. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/savedsearches_baselines.j2 +2 -2
  15. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/savedsearches_detections.j2 +2 -8
  16. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/savedsearches_investigations.j2 +2 -2
  17. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/transforms.j2 +0 -2
  18. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/stories/cobalt_strike.yml +1 -0
  19. {contentctl-5.0.0a3 → contentctl-5.0.1}/pyproject.toml +1 -1
  20. {contentctl-5.0.0a3 → contentctl-5.0.1}/LICENSE.md +0 -0
  21. {contentctl-5.0.0a3 → contentctl-5.0.1}/README.md +0 -0
  22. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/__init__.py +0 -0
  23. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/build.py +0 -0
  24. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/deploy_acs.py +0 -0
  25. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
  26. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/GitService.py +0 -0
  27. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
  28. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -0
  29. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
  30. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
  31. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/progress_bar.py +0 -0
  32. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
  33. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
  34. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
  35. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
  36. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/doc_gen.py +0 -0
  37. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/initialize.py +0 -0
  38. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/inspect.py +0 -0
  39. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/release_notes.py +0 -0
  40. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/reporting.py +0 -0
  41. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/test.py +0 -0
  42. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/actions/validate.py +0 -0
  43. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/api.py +0 -0
  44. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/contentctl.py +0 -0
  45. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/enrichments/attack_enrichment.py +0 -0
  46. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/enrichments/cve_enrichment.py +0 -0
  47. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
  48. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/helper/link_validator.py +0 -0
  49. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/helper/logger.py +0 -0
  50. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/helper/splunk_app.py +0 -0
  51. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/helper/utils.py +0 -0
  52. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/input/director.py +0 -0
  53. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/input/new_content_questions.py +0 -0
  54. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/input/yml_reader.py +0 -0
  55. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/alert_action.py +0 -0
  56. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/annotated_types.py +0 -0
  57. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/atomic.py +0 -0
  58. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/base_test.py +0 -0
  59. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/base_test_result.py +0 -0
  60. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/baseline_tags.py +0 -0
  61. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/config.py +0 -0
  62. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/correlation_search.py +0 -0
  63. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/dashboard.py +0 -0
  64. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/data_source.py +0 -0
  65. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/deployment.py +0 -0
  66. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/deployment_email.py +0 -0
  67. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/deployment_notable.py +0 -0
  68. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/deployment_phantom.py +0 -0
  69. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/deployment_rba.py +0 -0
  70. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/deployment_scheduling.py +0 -0
  71. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/deployment_slack.py +0 -0
  72. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/detection.py +0 -0
  73. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/detection_metadata.py +0 -0
  74. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/detection_stanza.py +0 -0
  75. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/detection_tags.py +0 -0
  76. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/drilldown.py +0 -0
  77. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/enums.py +0 -0
  78. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/errors.py +0 -0
  79. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/integration_test.py +0 -0
  80. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/integration_test_result.py +0 -0
  81. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/investigation_tags.py +0 -0
  82. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/lookup.py +0 -0
  83. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/macro.py +0 -0
  84. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/manual_test.py +0 -0
  85. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/manual_test_result.py +0 -0
  86. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/mitre_attack_enrichment.py +0 -0
  87. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/notable_action.py +0 -0
  88. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/notable_event.py +0 -0
  89. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/playbook.py +0 -0
  90. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/playbook_tags.py +0 -0
  91. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/risk_analysis_action.py +0 -0
  92. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/risk_event.py +0 -0
  93. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/risk_object.py +0 -0
  94. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/savedsearches_conf.py +0 -0
  95. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/security_content_object.py +0 -0
  96. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/story_tags.py +0 -0
  97. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/test_attack_data.py +0 -0
  98. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/test_group.py +0 -0
  99. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/threat_object.py +0 -0
  100. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/throttling.py +0 -0
  101. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/unit_test.py +0 -0
  102. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/unit_test_baseline.py +0 -0
  103. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/objects/unit_test_result.py +0 -0
  104. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/api_json_output.py +0 -0
  105. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/attack_nav_output.py +0 -0
  106. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/attack_nav_writer.py +0 -0
  107. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/conf_output.py +0 -0
  108. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/conf_writer.py +0 -0
  109. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/data_source_writer.py +0 -0
  110. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/doc_md_output.py +0 -0
  111. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/jinja_writer.py +0 -0
  112. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/json_writer.py +0 -0
  113. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/svg_output.py +0 -0
  114. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
  115. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/app.conf.j2 +0 -0
  116. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/app.manifest.j2 +0 -0
  117. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/collections.j2 +0 -0
  118. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/content-version.j2 +0 -0
  119. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/detection_count.j2 +0 -0
  120. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/detection_coverage.j2 +0 -0
  121. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_detection_page.j2 +0 -0
  122. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_detections.j2 +0 -0
  123. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_navigation.j2 +0 -0
  124. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
  125. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_playbooks.j2 +0 -0
  126. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
  127. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_stories.j2 +0 -0
  128. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/doc_story_page.j2 +0 -0
  129. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/header.j2 +0 -0
  130. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/macros.j2 +0 -0
  131. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/panel.j2 +0 -0
  132. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/server.conf.j2 +0 -0
  133. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/templates/workflow_actions.j2 +0 -0
  134. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/output/yml_writer.py +0 -0
  135. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/README.md +0 -0
  136. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_default.yml +0 -0
  137. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
  138. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
  139. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
  140. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/README.md +0 -0
  141. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
  142. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/default/commands.conf +0 -0
  143. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
  144. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
  145. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
  146. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
  147. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
  148. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/metadata/default.meta +0 -0
  149. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/static/appIcon.png +0 -0
  150. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
  151. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
  152. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
  153. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
  154. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/datamodels_cim.conf +0 -0
  155. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/datamodels_custom.conf +0 -0
  156. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
  157. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
  158. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
  159. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
  160. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
  161. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/detections/application/.gitkeep +0 -0
  162. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/detections/cloud/.gitkeep +0 -0
  163. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -0
  164. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/detections/network/.gitkeep +0 -0
  165. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/detections/web/.gitkeep +0 -0
  166. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/macros/security_content_ctime.yml +0 -0
  167. {contentctl-5.0.0a3 → contentctl-5.0.1}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: contentctl
3
- Version: 5.0.0a3
3
+ Version: 5.0.1
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -32,6 +32,14 @@ class NewContent:
32
32
  },
33
33
  ]
34
34
 
35
+ DEFAULT_RBA = {
36
+ "message": "Risk Message goes here",
37
+ "risk_objects": [{"field": "dest", "type": "system", "score": 10}],
38
+ "threat_objects": [
39
+ {"field": "parent_process_name", "type": "parent_process_name"}
40
+ ],
41
+ }
42
+
35
43
  def buildDetection(self) -> tuple[dict[str, Any], str]:
36
44
  questions = NewContentQuestions.get_questions_detection()
37
45
  answers: dict[str, str] = questionary.prompt(
@@ -42,8 +50,8 @@ class NewContent:
42
50
  raise ValueError("User didn't answer one or more questions!")
43
51
 
44
52
  data_source_field = (
45
- answers["data_source"]
46
- if len(answers["data_source"]) > 0
53
+ answers["data_sources"]
54
+ if len(answers["data_sources"]) > 0
47
55
  else [f"{NewContent.UPDATE_PREFIX} zero or more data_sources"]
48
56
  )
49
57
  file_name = (
@@ -83,19 +91,12 @@ class NewContent:
83
91
  f"{NewContent.UPDATE_PREFIX} zero or more http references to provide more information about your search"
84
92
  ],
85
93
  "drilldown_searches": NewContent.DEFAULT_DRILLDOWN_DEF,
86
- "rba": {
87
- "message": "Risk Messages goes here",
88
- "risk_objects": [],
89
- "threat_objects": [],
90
- },
94
+ "rba": NewContent.DEFAULT_RBA,
91
95
  "tags": {
92
96
  "analytic_story": [
93
97
  f"{NewContent.UPDATE_PREFIX} by providing zero or more analytic stories"
94
98
  ],
95
99
  "asset_type": f"{NewContent.UPDATE_PREFIX} by providing and asset type from {list(AssetType._value2member_map_)}",
96
- "confidence": f"{NewContent.UPDATE_PREFIX} by providing a value between 1-100",
97
- "impact": f"{NewContent.UPDATE_PREFIX} by providing a value between 1-100",
98
- "message": f"{NewContent.UPDATE_PREFIX} by providing a risk message. Fields in your search results can be referenced using $fieldName$",
99
100
  "mitre_attack_id": mitre_attack_ids,
100
101
  "product": [
101
102
  "Splunk Enterprise",
@@ -139,6 +140,7 @@ class NewContent:
139
140
  del answers["story_name"]
140
141
  answers["id"] = str(uuid.uuid4())
141
142
  answers["version"] = 1
143
+ answers["status"] = "production"
142
144
  answers["date"] = datetime.today().strftime("%Y-%m-%d")
143
145
  answers["author"] = answers["story_author"]
144
146
  del answers["story_author"]
@@ -383,21 +383,17 @@ class Detection_Abstract(SecurityContentObject):
383
383
  @computed_field
384
384
  @property
385
385
  def risk(self) -> list[dict[str, Any]]:
386
- risk_objects: list[dict[str, str | int]] = []
387
-
388
- for entity in self.rba.risk_objects:
389
- risk_object: dict[str, str | int] = dict()
390
- risk_object["risk_object_type"] = entity.type
391
- risk_object["risk_object_field"] = entity.field
392
- risk_object["risk_score"] = entity.score
393
- risk_objects.append(risk_object)
394
-
395
- for entity in self.rba.threat_objects:
396
- threat_object: dict[str, str] = dict()
397
- threat_object["threat_object_field"] = entity.field
398
- threat_object["threat_object_type"] = entity.type
399
- risk_objects.append(threat_object)
400
- return risk_objects
386
+ if self.rba is None:
387
+ raise Exception(
388
+ f"Attempting to serialize rba section of [{self.name}], however RBA section is None"
389
+ )
390
+ """
391
+ action.risk.param._risk
392
+ of the conf file only contains a list of dicts. We do not eant to
393
+ include the message here, so we do not return it.
394
+ """
395
+ rba_dict = self.rba.model_dump()
396
+ return rba_dict["risk_objects"] + rba_dict["threat_objects"]
401
397
 
402
398
  @computed_field
403
399
  @property
@@ -1,31 +1,39 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING, Self, Any
2
+
3
+ from typing import TYPE_CHECKING, Any, Self
3
4
 
4
5
  if TYPE_CHECKING:
6
+ from contentctl.input.director import DirectorOutputDto
5
7
  from contentctl.objects.deployment import Deployment
6
8
  from contentctl.objects.security_content_object import SecurityContentObject
7
- from contentctl.input.director import DirectorOutputDto
8
9
 
9
- from contentctl.objects.enums import AnalyticsType
10
- from contentctl.objects.constants import CONTENTCTL_MAX_STANZA_LENGTH
11
10
  import abc
12
- import uuid
13
11
  import datetime
12
+ import pathlib
14
13
  import pprint
14
+ import uuid
15
+ from functools import cached_property
16
+ from typing import List, Optional, Tuple, Union
17
+
15
18
  from pydantic import (
16
19
  BaseModel,
17
- field_validator,
20
+ ConfigDict,
18
21
  Field,
19
- ValidationInfo,
20
22
  FilePath,
21
23
  HttpUrl,
22
24
  NonNegativeInt,
23
- ConfigDict,
25
+ ValidationInfo,
26
+ computed_field,
27
+ field_validator,
24
28
  model_serializer,
25
29
  )
26
- from typing import Tuple, Optional, List, Union
27
- import pathlib
28
30
 
31
+ from contentctl.objects.constants import (
32
+ CONTENTCTL_MAX_STANZA_LENGTH,
33
+ DEPRECATED_TEMPLATE,
34
+ EXPERIMENTAL_TEMPLATE,
35
+ )
36
+ from contentctl.objects.enums import AnalyticsType, DetectionStatus
29
37
 
30
38
  NO_FILE_NAME = "NO_FILE_NAME"
31
39
 
@@ -44,6 +52,41 @@ class SecurityContentObject_Abstract(BaseModel, abc.ABC):
44
52
  def model_post_init(self, __context: Any) -> None:
45
53
  self.ensureFileNameMatchesSearchName()
46
54
 
55
+ @computed_field
56
+ @cached_property
57
+ def status_aware_description(self) -> str:
58
+ """We need to be able to write out a description that includes information
59
+ about whether or not a detection has been deprecated or not. This is important
60
+ for providing information to the user as well as powering the deprecation
61
+ assistant dashboad(s). Make sure this information is output correctly, if
62
+ appropriate.
63
+ Otherwise, if a detection is not deprecated or experimental, just return th
64
+ unmodified description.
65
+
66
+ Raises:
67
+ NotImplementedError: This content type does not support status_aware_description.
68
+ This is because the object does not define a status field
69
+
70
+ Returns:
71
+ str: description, which may or may not be prefixed with the deprecation/experimental message
72
+ """
73
+ status = getattr(self, "status", None)
74
+
75
+ if not isinstance(status, DetectionStatus):
76
+ raise NotImplementedError(
77
+ f"Detection status is not implemented for [{self.name}] of type '{type(self).__name__}'"
78
+ )
79
+ if status == DetectionStatus.experimental:
80
+ return EXPERIMENTAL_TEMPLATE.format(
81
+ content_type=type(self).__name__, description=self.description
82
+ )
83
+ elif status == DetectionStatus.deprecated:
84
+ return DEPRECATED_TEMPLATE.format(
85
+ content_type=type(self).__name__, description=self.description
86
+ )
87
+ else:
88
+ return self.description
89
+
47
90
  @model_serializer
48
91
  def serialize_model(self):
49
92
  return {
@@ -1,28 +1,28 @@
1
1
  from __future__ import annotations
2
- from typing import Annotated, List, Any, TYPE_CHECKING
2
+
3
+ from typing import TYPE_CHECKING, Annotated, Any, List, Literal
3
4
 
4
5
  if TYPE_CHECKING:
5
6
  from contentctl.input.director import DirectorOutputDto
6
7
 
7
8
  from pydantic import (
8
- field_validator,
9
- ValidationInfo,
10
9
  Field,
11
- model_serializer,
10
+ ValidationInfo,
12
11
  computed_field,
12
+ field_validator,
13
+ model_serializer,
13
14
  )
14
- from contentctl.objects.deployment import Deployment
15
- from contentctl.objects.security_content_object import SecurityContentObject
16
- from contentctl.objects.enums import DataModel
17
- from contentctl.objects.baseline_tags import BaselineTags
18
15
 
16
+ from contentctl.objects.baseline_tags import BaselineTags
19
17
  from contentctl.objects.config import CustomApp
20
-
21
- from contentctl.objects.lookup import Lookup
22
18
  from contentctl.objects.constants import (
23
- CONTENTCTL_MAX_SEARCH_NAME_LENGTH,
24
19
  CONTENTCTL_BASELINE_STANZA_NAME_FORMAT_TEMPLATE,
20
+ CONTENTCTL_MAX_SEARCH_NAME_LENGTH,
25
21
  )
22
+ from contentctl.objects.deployment import Deployment
23
+ from contentctl.objects.enums import DataModel, DetectionStatus
24
+ from contentctl.objects.lookup import Lookup
25
+ from contentctl.objects.security_content_object import SecurityContentObject
26
26
 
27
27
 
28
28
  class Baseline(SecurityContentObject):
@@ -35,6 +35,7 @@ class Baseline(SecurityContentObject):
35
35
  lookups: list[Lookup] = Field([], validate_default=True)
36
36
  # enrichment
37
37
  deployment: Deployment = Field({})
38
+ status: Literal[DetectionStatus.production, DetectionStatus.deprecated]
38
39
 
39
40
  @field_validator("lookups", mode="before")
40
41
  @classmethod
@@ -144,3 +144,6 @@ CONTENTCTL_MAX_SEARCH_NAME_LENGTH = CONTENTCTL_MAX_STANZA_LENGTH - len(
144
144
  app_label="ESCU", detection_name=""
145
145
  )
146
146
  )
147
+
148
+ DEPRECATED_TEMPLATE = "**WARNING**, this {content_type} has been marked **DEPRECATED** by the Splunk Threat Research Team. This means that it will no longer be maintained or supported. If you have any questions feel free to email us at: research@splunk.com. {description}"
149
+ EXPERIMENTAL_TEMPLATE = "**WARNING**, this {content_type} is marked **EXPERIMENTAL** by the Splunk Threat Research Team. This means that the {content_type} has been manually tested but we do not have the associated attack data to perform automated testing or cannot share this attack dataset due to its sensitive nature. If you have any questions feel free to email us at: research@splunk.com. {description}"
@@ -1,16 +1,19 @@
1
1
  from __future__ import annotations
2
+
2
3
  import re
3
- from typing import List, Any
4
- from pydantic import computed_field, Field, ConfigDict, model_serializer
5
- from contentctl.objects.security_content_object import SecurityContentObject
6
- from contentctl.objects.enums import DataModel
7
- from contentctl.objects.investigation_tags import InvestigationTags
4
+ from typing import Any, List, Literal
5
+
6
+ from pydantic import ConfigDict, Field, computed_field, model_serializer
7
+
8
+ from contentctl.objects.config import CustomApp
8
9
  from contentctl.objects.constants import (
9
10
  CONTENTCTL_MAX_SEARCH_NAME_LENGTH,
10
- CONTENTCTL_RESPONSE_TASK_NAME_FORMAT_TEMPLATE,
11
11
  CONTENTCTL_MAX_STANZA_LENGTH,
12
+ CONTENTCTL_RESPONSE_TASK_NAME_FORMAT_TEMPLATE,
12
13
  )
13
- from contentctl.objects.config import CustomApp
14
+ from contentctl.objects.enums import DataModel, DetectionStatus
15
+ from contentctl.objects.investigation_tags import InvestigationTags
16
+ from contentctl.objects.security_content_object import SecurityContentObject
14
17
 
15
18
 
16
19
  class Investigation(SecurityContentObject):
@@ -21,6 +24,7 @@ class Investigation(SecurityContentObject):
21
24
  how_to_implement: str = Field(...)
22
25
  known_false_positives: str = Field(...)
23
26
  tags: InvestigationTags
27
+ status: Literal[DetectionStatus.production, DetectionStatus.deprecated]
24
28
 
25
29
  # enrichment
26
30
  @computed_field
@@ -99,3 +103,6 @@ class Investigation(SecurityContentObject):
99
103
  # back to itself
100
104
  for story in self.tags.analytic_story:
101
105
  story.investigations.append(self)
106
+ # back to itself
107
+ for story in self.tags.analytic_story:
108
+ story.investigations.append(self)
@@ -1,9 +1,12 @@
1
- from enum import Enum
2
- from pydantic import BaseModel, computed_field, Field
1
+ from __future__ import annotations
2
+
3
3
  from abc import ABC
4
- from typing import Set, Annotated
5
- from contentctl.objects.enums import RiskSeverity
4
+ from enum import Enum
5
+ from typing import Annotated, Set
6
6
 
7
+ from pydantic import BaseModel, Field, computed_field, model_serializer
8
+
9
+ from contentctl.objects.enums import RiskSeverity
7
10
 
8
11
  RiskScoreValue_Type = Annotated[int, Field(ge=1, le=100)]
9
12
 
@@ -51,6 +54,28 @@ class RiskObject(BaseModel):
51
54
  def __hash__(self):
52
55
  return hash((self.field, self.type, self.score))
53
56
 
57
+ def __lt__(self, other: RiskObject) -> bool:
58
+ if (
59
+ f"{self.field}{self.type}{self.score}"
60
+ < f"{other.field}{other.type}{other.score}"
61
+ ):
62
+ return True
63
+ return False
64
+
65
+ @model_serializer
66
+ def serialize_risk_object(self) -> dict[str, str | int]:
67
+ """
68
+ We define this explicitly for two reasons, even though the automatic
69
+ serialization works correctly. First we want to enforce a specific
70
+ field order for reasons of readability. Second, some of the fields
71
+ actually have different names than they do in the object.
72
+ """
73
+ return {
74
+ "risk_object_field": self.field,
75
+ "risk_object_type": self.type,
76
+ "risk_score": self.score,
77
+ }
78
+
54
79
 
55
80
  class ThreatObject(BaseModel):
56
81
  field: str
@@ -59,6 +84,24 @@ class ThreatObject(BaseModel):
59
84
  def __hash__(self):
60
85
  return hash((self.field, self.type))
61
86
 
87
+ def __lt__(self, other: ThreatObject) -> bool:
88
+ if f"{self.field}{self.type}" < f"{other.field}{other.type}":
89
+ return True
90
+ return False
91
+
92
+ @model_serializer
93
+ def serialize_threat_object(self) -> dict[str, str]:
94
+ """
95
+ We define this explicitly for two reasons, even though the automatic
96
+ serialization works correctly. First we want to enforce a specific
97
+ field order for reasons of readability. Second, some of the fields
98
+ actually have different names than they do in the object.
99
+ """
100
+ return {
101
+ "threat_object_field": self.field,
102
+ "threat_object_type": self.type,
103
+ }
104
+
62
105
 
63
106
  class RBAObject(BaseModel, ABC):
64
107
  message: str
@@ -94,3 +137,11 @@ class RBAObject(BaseModel, ABC):
94
137
  raise Exception(
95
138
  f"Error getting severity - risk_score must be between 0-100, but was actually {self.risk_score}"
96
139
  )
140
+
141
+ @model_serializer
142
+ def serialize_rba(self) -> dict[str, str | list[dict[str, str | int]]]:
143
+ return {
144
+ "message": self.message,
145
+ "risk_objects": [obj.model_dump() for obj in sorted(self.risk_objects)],
146
+ "threat_objects": [obj.model_dump() for obj in sorted(self.threat_objects)],
147
+ }
@@ -1,23 +1,27 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING, List
3
- from contentctl.objects.story_tags import StoryTags
4
- from pydantic import Field, model_serializer, computed_field, model_validator
2
+
5
3
  import re
4
+ from typing import TYPE_CHECKING, List, Literal
5
+
6
+ from pydantic import Field, computed_field, model_serializer, model_validator
7
+
8
+ from contentctl.objects.story_tags import StoryTags
6
9
 
7
10
  if TYPE_CHECKING:
8
- from contentctl.objects.detection import Detection
9
- from contentctl.objects.investigation import Investigation
10
11
  from contentctl.objects.baseline import Baseline
11
- from contentctl.objects.data_source import DataSource
12
12
  from contentctl.objects.config import CustomApp
13
+ from contentctl.objects.data_source import DataSource
14
+ from contentctl.objects.detection import Detection
15
+ from contentctl.objects.investigation import Investigation
13
16
 
17
+ from contentctl.objects.enums import DetectionStatus
14
18
  from contentctl.objects.security_content_object import SecurityContentObject
15
19
 
16
20
 
17
21
  class Story(SecurityContentObject):
18
22
  narrative: str = Field(...)
19
23
  tags: StoryTags = Field(...)
20
-
24
+ status: Literal[DetectionStatus.production, DetectionStatus.deprecated]
21
25
  # These are updated when detection and investigation objects are created.
22
26
  # Specifically in the model_post_init functions
23
27
  detections: List[Detection] = []
@@ -7,7 +7,7 @@
7
7
  type = detection
8
8
  asset_type = {{ detection.tags.asset_type }}
9
9
  confidence = medium
10
- explanation = {{ (detection.explanation if detection.explanation else detection.description) | escapeNewlines() }}
10
+ explanation = {{ detection.status_aware_description | escapeNewlines() }}
11
11
  {% if detection.how_to_implement is defined %}
12
12
  how_to_implement = {{ detection.how_to_implement | escapeNewlines() }}
13
13
  {% else %}
@@ -11,7 +11,7 @@ references = {{ story.getReferencesListForJson() | tojson }}
11
11
  maintainers = [{"company": "{{ story.author_company }}", "email": "{{ story.author_email }}", "name": "{{ story.author_name }}"}]
12
12
  spec_version = 3
13
13
  searches = {{ story.storyAndInvestigationNamesWithApp(app) | tojson }}
14
- description = {{ story.description | escapeNewlines() }}
14
+ description = {{ story.status_aware_description | escapeNewlines() }}
15
15
  {% if story.narrative is defined %}
16
16
  narrative = {{ story.narrative | escapeNewlines() }}
17
17
  {% endif %}
@@ -2,7 +2,7 @@
2
2
  {% for response_task in objects %}
3
3
  [panel://workbench_panel_{{ response_task.lowercase_name }}___response_task]
4
4
  label = {{ response_task.name }}
5
- description = {{ response_task.description | escapeNewlines() }}
5
+ description = {{ response_task.status_aware_description | escapeNewlines() }}
6
6
  disabled = 0
7
7
  tokens = {\
8
8
  {% for token in response_task.inputs %}
@@ -2,7 +2,7 @@
2
2
  {% for story in objects %}
3
3
  [panel_group://workbench_panel_group_{{ story.lowercase_name}}]
4
4
  label = {{ story.name }}
5
- description = {{ story.description | escapeNewlines() }}
5
+ description = {{ story.status_aware_description | escapeNewlines() }}
6
6
  disabled = 0
7
7
 
8
8
  {% if story.workbench_panels is defined %}
@@ -8,7 +8,7 @@
8
8
  action.escu = 0
9
9
  action.escu.enabled = 1
10
10
  action.escu.search_type = support
11
- description = {{ detection.description | escapeNewlines() }}
11
+ description = {{ detection.status_aware_description | escapeNewlines() }}
12
12
  action.escu.creation_date = {{ detection.date }}
13
13
  action.escu.modification_date = {{ detection.date }}
14
14
  {% if detection.tags.analytic_story is defined %}
@@ -29,7 +29,7 @@ action.escu.providing_technologies = {{ detection.providing_technologies | tojso
29
29
  {% else %}
30
30
  action.escu.providing_technologies = []
31
31
  {% endif %}
32
- action.escu.eli5 = {{ detection.description | escapeNewlines() }}
32
+ action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
33
33
  {% if detection.how_to_implement is defined %}
34
34
  action.escu.how_to_implement = {{ detection.how_to_implement | escapeNewlines() }}
35
35
  {% else %}
@@ -5,16 +5,10 @@
5
5
  [{{ detection.get_conf_stanza_name(app) }}]
6
6
  action.escu = 0
7
7
  action.escu.enabled = 1
8
- {% if detection.status == "deprecated" %}
9
- description = **WARNING**, this detection has been marked **DEPRECATED** by the Splunk Threat Research Team. This means that it will no longer be maintained or supported. If you have any questions feel free to email us at: research@splunk.com. {{ detection.description | escapeNewlines() }}
10
- {% elif detection.status == "experimental" %}
11
- description = **WARNING**, this detection is marked **EXPERIMENTAL** by the Splunk Threat Research Team. This means that the detection has been manually tested but we do not have the associated attack data to perform automated testing or cannot share this attack dataset due to its sensitive nature. If you have any questions feel free to email us at: research@splunk.com. {{ detection.description | escapeNewlines() }}
12
- {% else %}
13
- description = {{ detection.description | escapeNewlines() }}
14
- {% endif %}
8
+ description = {{ detection.status_aware_description | escapeNewlines() }}
15
9
  action.escu.mappings = {{ detection.mappings | tojson }}
16
10
  action.escu.data_models = {{ detection.datamodel | tojson }}
17
- action.escu.eli5 = {{ detection.description | escapeNewlines() }}
11
+ action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
18
12
  {% if detection.how_to_implement %}
19
13
  action.escu.how_to_implement = {{ detection.how_to_implement | escapeNewlines() }}
20
14
  {% else %}
@@ -9,7 +9,7 @@
9
9
  action.escu = 0
10
10
  action.escu.enabled = 1
11
11
  action.escu.search_type = investigative
12
- description = {{ detection.description | escapeNewlines() }}
12
+ description = {{ detection.status_aware_description | escapeNewlines() }}
13
13
  action.escu.creation_date = {{ detection.date }}
14
14
  action.escu.modification_date = {{ detection.date }}
15
15
  {% if detection.tags.analytic_story is defined %}
@@ -21,7 +21,7 @@ action.escu.earliest_time_offset = 3600
21
21
  action.escu.latest_time_offset = 86400
22
22
  action.escu.providing_technologies = []
23
23
  action.escu.data_models = {{ detection.datamodel | tojson }}
24
- action.escu.eli5 = {{ detection.description | escapeNewlines() }}
24
+ action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
25
25
  action.escu.how_to_implement = none
26
26
  action.escu.known_false_positives = None at this time
27
27
  disabled = true
@@ -13,9 +13,7 @@ default_match = {{ lookup.default_match | lower }}
13
13
  {% if lookup.case_sensitive_match is defined and lookup.case_sensitive_match != None %}
14
14
  case_sensitive_match = {{ lookup.case_sensitive_match | lower }}
15
15
  {% endif %}
16
- {% if lookup.description is defined and lookup.description != None %}
17
16
  # description = {{ lookup.description | escapeNewlines() }}
18
- {% endif %}
19
17
  {% if lookup.match_type | length > 0 %}
20
18
  match_type = {{ lookup.match_type_to_conf_format }}
21
19
  {% endif %}
@@ -3,6 +3,7 @@ id: bcfd17e8-5461-400a-80a2-3b7d1459220c
3
3
  version: 1
4
4
  date: '2021-02-16'
5
5
  author: Michael Haag, Splunk
6
+ status: production
6
7
  description: Cobalt Strike is threat emulation software. Red teams and penetration
7
8
  testers use Cobalt Strike to demonstrate the risk of a breach and evaluate mature
8
9
  security programs. Most recently, Cobalt Strike has become the choice tool by threat
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "contentctl"
3
- version = "5.0.0-alpha.3"
3
+ version = "5.0.1"
4
4
 
5
5
  description = "Splunk Content Control Tool"
6
6
  authors = ["STRT <research@splunk.com>"]
File without changes
File without changes