contentctl 4.3.0__tar.gz → 4.3.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 (169) hide show
  1. {contentctl-4.3.0 → contentctl-4.3.1}/PKG-INFO +2 -2
  2. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +60 -24
  3. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +5 -0
  4. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/constants.py +5 -0
  5. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/observable.py +5 -3
  6. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +2 -2
  7. {contentctl-4.3.0 → contentctl-4.3.1}/pyproject.toml +2 -2
  8. {contentctl-4.3.0 → contentctl-4.3.1}/LICENSE.md +0 -0
  9. {contentctl-4.3.0 → contentctl-4.3.1}/README.md +0 -0
  10. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/__init__.py +0 -0
  11. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/build.py +0 -0
  12. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/deploy_acs.py +0 -0
  13. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
  14. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/GitService.py +0 -0
  15. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
  16. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
  17. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
  18. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/progress_bar.py +0 -0
  19. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
  20. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
  21. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
  22. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
  23. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/doc_gen.py +0 -0
  24. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/initialize.py +0 -0
  25. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/initialize_old.py +0 -0
  26. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/inspect.py +0 -0
  27. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/new_content.py +0 -0
  28. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/release_notes.py +0 -0
  29. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/reporting.py +0 -0
  30. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/test.py +0 -0
  31. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/actions/validate.py +0 -0
  32. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/api.py +0 -0
  33. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/contentctl.py +0 -0
  34. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/enrichments/attack_enrichment.py +0 -0
  35. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/enrichments/cve_enrichment.py +0 -0
  36. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
  37. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/helper/link_validator.py +0 -0
  38. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/helper/logger.py +0 -0
  39. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/helper/splunk_app.py +0 -0
  40. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/helper/utils.py +0 -0
  41. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/input/director.py +0 -0
  42. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/input/new_content_questions.py +0 -0
  43. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/input/yml_reader.py +0 -0
  44. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +0 -0
  45. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/alert_action.py +0 -0
  46. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/atomic.py +0 -0
  47. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/base_test.py +0 -0
  48. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/base_test_result.py +0 -0
  49. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/baseline.py +0 -0
  50. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/baseline_tags.py +0 -0
  51. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/config.py +0 -0
  52. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/correlation_search.py +0 -0
  53. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/data_source.py +0 -0
  54. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/deployment.py +0 -0
  55. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/deployment_email.py +0 -0
  56. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/deployment_notable.py +0 -0
  57. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/deployment_phantom.py +0 -0
  58. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/deployment_rba.py +0 -0
  59. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/deployment_scheduling.py +0 -0
  60. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/deployment_slack.py +0 -0
  61. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/detection.py +0 -0
  62. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/detection_tags.py +0 -0
  63. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/enums.py +0 -0
  64. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/errors.py +0 -0
  65. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/event_source.py +0 -0
  66. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/integration_test.py +0 -0
  67. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/integration_test_result.py +0 -0
  68. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/investigation.py +0 -0
  69. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/investigation_tags.py +0 -0
  70. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/lookup.py +0 -0
  71. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/macro.py +0 -0
  72. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/mitre_attack_enrichment.py +0 -0
  73. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/notable_action.py +0 -0
  74. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/notable_event.py +0 -0
  75. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/playbook.py +0 -0
  76. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/playbook_tags.py +0 -0
  77. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/risk_analysis_action.py +0 -0
  78. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/risk_event.py +0 -0
  79. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/risk_object.py +0 -0
  80. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/security_content_object.py +0 -0
  81. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/ssa_detection.py +0 -0
  82. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/ssa_detection_tags.py +0 -0
  83. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/story.py +0 -0
  84. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/story_tags.py +0 -0
  85. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/test_group.py +0 -0
  86. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/threat_object.py +0 -0
  87. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/unit_test.py +0 -0
  88. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/unit_test_attack_data.py +0 -0
  89. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/unit_test_baseline.py +0 -0
  90. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/unit_test_old.py +0 -0
  91. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/unit_test_result.py +0 -0
  92. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/objects/unit_test_ssa.py +0 -0
  93. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/api_json_output.py +0 -0
  94. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/attack_nav_output.py +0 -0
  95. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/attack_nav_writer.py +0 -0
  96. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/conf_output.py +0 -0
  97. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/conf_writer.py +0 -0
  98. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/data_source_writer.py +0 -0
  99. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/detection_writer.py +0 -0
  100. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/doc_md_output.py +0 -0
  101. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/jinja_writer.py +0 -0
  102. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/json_writer.py +0 -0
  103. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/new_content_yml_output.py +0 -0
  104. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/svg_output.py +0 -0
  105. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
  106. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
  107. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
  108. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/app.conf.j2 +0 -0
  109. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/app.manifest.j2 +0 -0
  110. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/collections.j2 +0 -0
  111. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/content-version.j2 +0 -0
  112. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/detection_count.j2 +0 -0
  113. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/detection_coverage.j2 +0 -0
  114. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_detection_page.j2 +0 -0
  115. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_detections.j2 +0 -0
  116. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_navigation.j2 +0 -0
  117. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
  118. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_playbooks.j2 +0 -0
  119. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
  120. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_stories.j2 +0 -0
  121. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/doc_story_page.j2 +0 -0
  122. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
  123. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
  124. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/finding_report.j2 +0 -0
  125. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/header.j2 +0 -0
  126. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/macros.j2 +0 -0
  127. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/panel.j2 +0 -0
  128. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
  129. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/savedsearches_detections.j2 +0 -0
  130. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
  131. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/transforms.j2 +0 -0
  132. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/templates/workflow_actions.j2 +0 -0
  133. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/yml_output.py +0 -0
  134. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/output/yml_writer.py +0 -0
  135. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/README.md +0 -0
  136. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_default.yml +0 -0
  137. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
  138. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
  139. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
  140. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/README.md +0 -0
  141. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
  142. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/app.conf +0 -0
  143. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/commands.conf +0 -0
  144. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/content-version.conf +0 -0
  145. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
  146. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
  147. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
  148. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
  149. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
  150. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/metadata/default.meta +0 -0
  151. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/static/appIcon.png +0 -0
  152. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
  153. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
  154. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
  155. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
  156. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/datamodels_cim.conf +0 -0
  157. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/datamodels_custom.conf +0 -0
  158. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
  159. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
  160. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
  161. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
  162. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
  163. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/detections/application/.gitkeep +0 -0
  164. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/detections/cloud/.gitkeep +0 -0
  165. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/detections/network/.gitkeep +0 -0
  166. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/detections/web/.gitkeep +0 -0
  167. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/macros/security_content_ctime.yml +0 -0
  168. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
  169. {contentctl-4.3.0 → contentctl-4.3.1}/contentctl/templates/stories/cobalt_strike.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: contentctl
3
- Version: 4.3.0
3
+ Version: 4.3.1
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -22,7 +22,7 @@ Requires-Dist: pygit2 (>=1.14.1,<2.0.0)
22
22
  Requires-Dist: questionary (>=2.0.1,<3.0.0)
23
23
  Requires-Dist: requests (>=2.32.2,<2.33.0)
24
24
  Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
25
- Requires-Dist: setuptools (>=69.5.1,<73.0.0)
25
+ Requires-Dist: setuptools (>=69.5.1,<74.0.0)
26
26
  Requires-Dist: splunk-sdk (>=2.0.1,<3.0.0)
27
27
  Requires-Dist: tqdm (>=4.66.4,<5.0.0)
28
28
  Requires-Dist: tyro (>=0.8.3,<0.9.0)
@@ -1060,7 +1060,9 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1060
1060
  results = JSONResultsReader(job.results(output_mode="json"))
1061
1061
 
1062
1062
  # Consolidate a set of the distinct observable field names
1063
- observable_fields_set = set([o.name for o in detection.tags.observable])
1063
+ observable_fields_set = set([o.name for o in detection.tags.observable]) # keeping this around for later
1064
+ risk_object_fields_set = set([o.name for o in detection.tags.observable if "Victim" in o.role ]) # just the "Risk Objects"
1065
+ threat_object_fields_set = set([o.name for o in detection.tags.observable if "Attacker" in o.role]) # just the "threat objects"
1064
1066
 
1065
1067
  # Ensure the search had at least one result
1066
1068
  if int(job.content.get("resultCount", "0")) > 0:
@@ -1068,7 +1070,10 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1068
1070
  test.result = UnitTestResult()
1069
1071
 
1070
1072
  # Initialize the collection of fields that are empty that shouldn't be
1073
+ present_threat_objects: set[str] = set()
1071
1074
  empty_fields: set[str] = set()
1075
+
1076
+
1072
1077
 
1073
1078
  # Filter out any messages in the results
1074
1079
  for result in results:
@@ -1077,30 +1082,50 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1077
1082
 
1078
1083
  # If not a message, it is a dict and we will process it
1079
1084
  results_fields_set = set(result.keys())
1085
+ # Guard against first events (relevant later)
1080
1086
 
1081
- # Identify any observable fields that are not available in the results
1082
- missing_fields = observable_fields_set - results_fields_set
1083
- if len(missing_fields) > 0:
1087
+ # Identify any risk object fields that are not available in the results
1088
+ missing_risk_objects = risk_object_fields_set - results_fields_set
1089
+ if len(missing_risk_objects) > 0:
1084
1090
  # Report a failure in such cases
1085
- e = Exception(f"The observable field(s) {missing_fields} are missing in the detection results")
1091
+ e = Exception(f"The observable field(s) {missing_risk_objects} are missing in the detection results")
1086
1092
  test.result.set_job_content(
1087
1093
  job.content,
1088
1094
  self.infrastructure,
1089
- TestResultStatus.ERROR,
1095
+ TestResultStatus.FAIL,
1090
1096
  exception=e,
1091
1097
  duration=time.time() - search_start_time,
1092
1098
  )
1093
1099
 
1094
- return
1100
+ return
1095
1101
 
1096
- # If we find one or more fields that contain the string "null" then they were
1102
+ # If we find one or more risk object fields that contain the string "null" then they were
1097
1103
  # not populated and we should throw an error. This can happen if there is a typo
1098
1104
  # on a field. In this case, the field will appear but will not contain any values
1099
- current_empty_fields = set()
1105
+ current_empty_fields: set[str] = set()
1106
+
1100
1107
  for field in observable_fields_set:
1101
1108
  if result.get(field, 'null') == 'null':
1102
- current_empty_fields.add(field)
1103
-
1109
+ if field in risk_object_fields_set:
1110
+ e = Exception(f"The risk object field {field} is missing in at least one result.")
1111
+ test.result.set_job_content(
1112
+ job.content,
1113
+ self.infrastructure,
1114
+ TestResultStatus.FAIL,
1115
+ exception=e,
1116
+ duration=time.time() - search_start_time,
1117
+ )
1118
+ return
1119
+ else:
1120
+ if field in threat_object_fields_set:
1121
+ current_empty_fields.add(field)
1122
+ else:
1123
+ if field in threat_object_fields_set:
1124
+ present_threat_objects.add(field)
1125
+ continue
1126
+
1127
+
1128
+
1104
1129
  # If everything succeeded up until now, and no empty fields are found in the
1105
1130
  # current result, then the search was a success
1106
1131
  if len(current_empty_fields) == 0:
@@ -1114,21 +1139,32 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1114
1139
 
1115
1140
  else:
1116
1141
  empty_fields = empty_fields.union(current_empty_fields)
1117
-
1118
- # Report a failure if there were empty fields in all results
1119
- e = Exception(
1120
- f"One or more required observable fields {empty_fields} contained 'null' values. Is the "
1121
- "data being parsed correctly or is there an error in the naming of a field?"
1142
+
1143
+
1144
+ missing_threat_objects = threat_object_fields_set - present_threat_objects
1145
+ # Report a failure if there were empty fields in a threat object in all results
1146
+ if len(missing_threat_objects) > 0:
1147
+ e = Exception(
1148
+ f"One or more required threat object fields {missing_threat_objects} contained 'null' values in all events. "
1149
+ "Is the data being parsed correctly or is there an error in the naming of a field?"
1122
1150
  )
1123
- test.result.set_job_content(
1124
- job.content,
1125
- self.infrastructure,
1126
- TestResultStatus.ERROR,
1127
- exception=e,
1128
- duration=time.time() - search_start_time,
1129
- )
1151
+ test.result.set_job_content(
1152
+ job.content,
1153
+ self.infrastructure,
1154
+ TestResultStatus.FAIL,
1155
+ exception=e,
1156
+ duration=time.time() - search_start_time,
1157
+ )
1158
+ return
1159
+
1130
1160
 
1131
- return
1161
+ test.result.set_job_content(
1162
+ job.content,
1163
+ self.infrastructure,
1164
+ TestResultStatus.PASS,
1165
+ duration=time.time() - search_start_time,
1166
+ )
1167
+ return
1132
1168
 
1133
1169
  else:
1134
1170
  # Report a failure if there were no results at all
@@ -290,6 +290,11 @@ class Detection_Abstract(SecurityContentObject):
290
290
  risk_object['threat_object_field'] = entity.name
291
291
  risk_object['threat_object_type'] = "url"
292
292
  risk_objects.append(risk_object)
293
+
294
+ elif 'Attacker' in entity.role:
295
+ risk_object['threat_object_field'] = entity.name
296
+ risk_object['threat_object_type'] = entity.type.lower()
297
+ risk_objects.append(risk_object)
293
298
 
294
299
  else:
295
300
  risk_object['risk_object_type'] = 'other'
@@ -132,3 +132,8 @@ SES_ATTACK_TACTICS_ID_MAPPING = {
132
132
  "Exfiltration": "TA0010",
133
133
  "Impact": "TA0040"
134
134
  }
135
+
136
+ RBA_OBSERVABLE_ROLE_MAPPING = {
137
+ "Attacker": 0,
138
+ "Victim": 1
139
+ }
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, field_validator
2
- from contentctl.objects.constants import SES_OBSERVABLE_TYPE_MAPPING, SES_OBSERVABLE_ROLE_MAPPING
2
+ from contentctl.objects.constants import SES_OBSERVABLE_TYPE_MAPPING, RBA_OBSERVABLE_ROLE_MAPPING
3
3
 
4
4
 
5
5
  class Observable(BaseModel):
@@ -26,10 +26,12 @@ class Observable(BaseModel):
26
26
  def check_roles(cls, v: list[str]):
27
27
  if len(v) == 0:
28
28
  raise ValueError("Error, at least 1 role must be listed for Observable.")
29
+ if len(v) > 1:
30
+ raise ValueError("Error, each Observable can only have one role.")
29
31
  for role in v:
30
- if role not in SES_OBSERVABLE_ROLE_MAPPING.keys():
32
+ if role not in RBA_OBSERVABLE_ROLE_MAPPING.keys():
31
33
  raise ValueError(
32
34
  f"Invalid role '{role}' provided for observable. Valid observable types are "
33
- f"{SES_OBSERVABLE_ROLE_MAPPING.keys()}"
35
+ f"{RBA_OBSERVABLE_ROLE_MAPPING.keys()}"
34
36
  )
35
37
  return v
@@ -53,11 +53,11 @@ tags:
53
53
  - name: parent_process_name
54
54
  type: Process
55
55
  role:
56
- - Parent Process
56
+ - Attacker
57
57
  - name: process_name
58
58
  type: Process
59
59
  role:
60
- - Child Process
60
+ - Attacker
61
61
  product:
62
62
  - Splunk Enterprise
63
63
  - Splunk Enterprise Security
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "contentctl"
3
- version = "4.3.0"
3
+ version = "4.3.1"
4
4
  description = "Splunk Content Control Tool"
5
5
  authors = ["STRT <research@splunk.com>"]
6
6
  license = "Apache 2.0"
@@ -27,7 +27,7 @@ tqdm = "^4.66.4"
27
27
  pygit2 = "^1.14.1"
28
28
  tyro = "^0.8.3"
29
29
  gitpython = "^3.1.43"
30
- setuptools = ">=69.5.1,<73.0.0"
30
+ setuptools = ">=69.5.1,<74.0.0"
31
31
  [tool.poetry.dev-dependencies]
32
32
 
33
33
  [build-system]
File without changes
File without changes
File without changes