contentctl 4.2.5__tar.gz → 4.3.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.
Files changed (175) hide show
  1. {contentctl-4.2.5 → contentctl-4.3.0}/PKG-INFO +15 -5
  2. {contentctl-4.2.5 → contentctl-4.3.0}/README.md +14 -2
  3. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/build.py +0 -14
  4. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/validate.py +0 -1
  5. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/input/director.py +9 -44
  6. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +56 -74
  7. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/config.py +0 -2
  8. {contentctl-4.2.5 → contentctl-4.3.0}/pyproject.toml +1 -4
  9. contentctl-4.2.5/contentctl/actions/convert.py +0 -25
  10. contentctl-4.2.5/contentctl/input/backend_splunk_ba.py +0 -144
  11. contentctl-4.2.5/contentctl/input/sigma_converter.py +0 -436
  12. contentctl-4.2.5/contentctl/input/ssa_detection_builder.py +0 -169
  13. contentctl-4.2.5/contentctl/output/ba_yml_output.py +0 -153
  14. contentctl-4.2.5/contentctl/output/finding_report_writer.py +0 -91
  15. {contentctl-4.2.5 → contentctl-4.3.0}/LICENSE.md +0 -0
  16. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/__init__.py +0 -0
  17. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/deploy_acs.py +0 -0
  18. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
  19. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/GitService.py +0 -0
  20. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
  21. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -0
  22. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
  23. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
  24. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/progress_bar.py +0 -0
  25. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
  26. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
  27. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
  28. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
  29. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/doc_gen.py +0 -0
  30. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/initialize.py +0 -0
  31. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/initialize_old.py +0 -0
  32. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/inspect.py +0 -0
  33. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/new_content.py +0 -0
  34. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/release_notes.py +0 -0
  35. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/reporting.py +0 -0
  36. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/actions/test.py +0 -0
  37. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/api.py +0 -0
  38. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/contentctl.py +0 -0
  39. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/enrichments/attack_enrichment.py +0 -0
  40. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/enrichments/cve_enrichment.py +0 -0
  41. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
  42. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/helper/link_validator.py +0 -0
  43. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/helper/logger.py +0 -0
  44. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/helper/splunk_app.py +0 -0
  45. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/helper/utils.py +0 -0
  46. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/input/new_content_questions.py +0 -0
  47. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/input/yml_reader.py +0 -0
  48. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +0 -0
  49. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/alert_action.py +0 -0
  50. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/atomic.py +0 -0
  51. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/base_test.py +0 -0
  52. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/base_test_result.py +0 -0
  53. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/baseline.py +0 -0
  54. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/baseline_tags.py +0 -0
  55. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/constants.py +0 -0
  56. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/correlation_search.py +0 -0
  57. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/data_source.py +0 -0
  58. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/deployment.py +0 -0
  59. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/deployment_email.py +0 -0
  60. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/deployment_notable.py +0 -0
  61. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/deployment_phantom.py +0 -0
  62. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/deployment_rba.py +0 -0
  63. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/deployment_scheduling.py +0 -0
  64. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/deployment_slack.py +0 -0
  65. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/detection.py +0 -0
  66. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/detection_tags.py +0 -0
  67. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/enums.py +0 -0
  68. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/errors.py +0 -0
  69. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/event_source.py +0 -0
  70. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/integration_test.py +0 -0
  71. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/integration_test_result.py +0 -0
  72. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/investigation.py +0 -0
  73. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/investigation_tags.py +0 -0
  74. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/lookup.py +0 -0
  75. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/macro.py +0 -0
  76. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/mitre_attack_enrichment.py +0 -0
  77. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/notable_action.py +0 -0
  78. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/notable_event.py +0 -0
  79. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/observable.py +0 -0
  80. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/playbook.py +0 -0
  81. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/playbook_tags.py +0 -0
  82. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/risk_analysis_action.py +0 -0
  83. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/risk_event.py +0 -0
  84. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/risk_object.py +0 -0
  85. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/security_content_object.py +0 -0
  86. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/ssa_detection.py +0 -0
  87. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/ssa_detection_tags.py +0 -0
  88. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/story.py +0 -0
  89. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/story_tags.py +0 -0
  90. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/test_group.py +0 -0
  91. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/threat_object.py +0 -0
  92. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/unit_test.py +0 -0
  93. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/unit_test_attack_data.py +0 -0
  94. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/unit_test_baseline.py +0 -0
  95. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/unit_test_old.py +0 -0
  96. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/unit_test_result.py +0 -0
  97. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/objects/unit_test_ssa.py +0 -0
  98. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/api_json_output.py +0 -0
  99. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/attack_nav_output.py +0 -0
  100. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/attack_nav_writer.py +0 -0
  101. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/conf_output.py +0 -0
  102. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/conf_writer.py +0 -0
  103. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/data_source_writer.py +0 -0
  104. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/detection_writer.py +0 -0
  105. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/doc_md_output.py +0 -0
  106. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/jinja_writer.py +0 -0
  107. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/json_writer.py +0 -0
  108. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/new_content_yml_output.py +0 -0
  109. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/svg_output.py +0 -0
  110. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
  111. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
  112. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
  113. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/app.conf.j2 +0 -0
  114. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/app.manifest.j2 +0 -0
  115. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/collections.j2 +0 -0
  116. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/content-version.j2 +0 -0
  117. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/detection_count.j2 +0 -0
  118. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/detection_coverage.j2 +0 -0
  119. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_detection_page.j2 +0 -0
  120. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_detections.j2 +0 -0
  121. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_navigation.j2 +0 -0
  122. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
  123. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_playbooks.j2 +0 -0
  124. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
  125. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_stories.j2 +0 -0
  126. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/doc_story_page.j2 +0 -0
  127. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
  128. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
  129. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/finding_report.j2 +0 -0
  130. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/header.j2 +0 -0
  131. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/macros.j2 +0 -0
  132. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/panel.j2 +0 -0
  133. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
  134. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/savedsearches_detections.j2 +0 -0
  135. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
  136. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/transforms.j2 +0 -0
  137. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/templates/workflow_actions.j2 +0 -0
  138. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/yml_output.py +0 -0
  139. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/output/yml_writer.py +0 -0
  140. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/README.md +0 -0
  141. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_default.yml +0 -0
  142. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
  143. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
  144. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
  145. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/README.md +0 -0
  146. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
  147. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/app.conf +0 -0
  148. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/commands.conf +0 -0
  149. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/content-version.conf +0 -0
  150. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
  151. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
  152. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
  153. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
  154. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
  155. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/metadata/default.meta +0 -0
  156. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/static/appIcon.png +0 -0
  157. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
  158. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
  159. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
  160. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
  161. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/datamodels_cim.conf +0 -0
  162. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/datamodels_custom.conf +0 -0
  163. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
  164. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
  165. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
  166. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
  167. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
  168. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/detections/application/.gitkeep +0 -0
  169. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/detections/cloud/.gitkeep +0 -0
  170. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -0
  171. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/detections/network/.gitkeep +0 -0
  172. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/detections/web/.gitkeep +0 -0
  173. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/macros/security_content_ctime.yml +0 -0
  174. {contentctl-4.2.5 → contentctl-4.3.0}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
  175. {contentctl-4.2.5 → contentctl-4.3.0}/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.2.5
3
+ Version: 4.3.0
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -19,8 +19,6 @@ Requires-Dist: gitpython (>=3.1.43,<4.0.0)
19
19
  Requires-Dist: pycvesearch (>=1.2,<2.0)
20
20
  Requires-Dist: pydantic (>=2.7.1,<3.0.0)
21
21
  Requires-Dist: pygit2 (>=1.14.1,<2.0.0)
22
- Requires-Dist: pysigma (>=0.11.5,<0.12.0)
23
- Requires-Dist: pysigma-backend-splunk (>=1.1.0,<2.0.0)
24
22
  Requires-Dist: questionary (>=2.0.1,<3.0.0)
25
23
  Requires-Dist: requests (>=2.32.2,<2.33.0)
26
24
  Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
@@ -36,8 +34,20 @@ Description-Content-Type: text/markdown
36
34
  <p align="center">
37
35
  <img src="docs/contentctl_logo_white.png" title="In case you're wondering, it's a capybara" alt="contentctl logo" width="250" height="250"></p>
38
36
 
39
-
40
-
37
+ # contentctl Quick Start Guide
38
+ If you are already familiar with contentctl, the following common commands may be very useful for basic operations
39
+
40
+ | Operation | Command |
41
+ |-----------|---------|
42
+ | Create a repository | `contentctl init` |
43
+ | Validate Your Content | `contentctl validate` |
44
+ | Validate Your Content, performing MITRE Enrichments | `contentctl validate –-enrichments`|
45
+ | Build Your App | `contentctl build` |
46
+ | Test All the content in your app, pausing so that you can debug a search if it fails | `contentctl test –-post-test-behavior pause_on_failure mode:all` |
47
+ | Test All the content in your app, pausing after every detection to allow debugging | `contentctl test –-post-test-behavior always_pause mode:all` |
48
+ | Test 1 or more specified detections. If you are testing more than one detection, the paths are space-separated. You may also use shell-expanded regexes | `contentctl test –-post-test-behavior always_pause mode:selected --mode.files detections/endpoint/7zip_commandline_to_smb_share_path.yml detections/cloud/aws_multi_factor_authentication_disabled.yml detections/application/okta*` |
49
+ | Diff your current branch with a target_branch and test detections that have been updated. Your current branch **must be DIFFERENT** than the target_branch | `contentctl test –-post-test-behavior always_pause mode:changes –-mode.target_branch develop` |
50
+ | Perform Integration Testing of all content. Note that Enterprise Security MUST be listed as an app in your contentctl.yml folder, otherwise all tests will subsequently fail | `contentctl test –-enable-integration-testing --post-test-behavior never_pause mode:all` |
41
51
 
42
52
  # Introduction
43
53
  #### Security Is Hard
@@ -3,8 +3,20 @@
3
3
  <p align="center">
4
4
  <img src="docs/contentctl_logo_white.png" title="In case you're wondering, it's a capybara" alt="contentctl logo" width="250" height="250"></p>
5
5
 
6
-
7
-
6
+ # contentctl Quick Start Guide
7
+ If you are already familiar with contentctl, the following common commands may be very useful for basic operations
8
+
9
+ | Operation | Command |
10
+ |-----------|---------|
11
+ | Create a repository | `contentctl init` |
12
+ | Validate Your Content | `contentctl validate` |
13
+ | Validate Your Content, performing MITRE Enrichments | `contentctl validate –-enrichments`|
14
+ | Build Your App | `contentctl build` |
15
+ | Test All the content in your app, pausing so that you can debug a search if it fails | `contentctl test –-post-test-behavior pause_on_failure mode:all` |
16
+ | Test All the content in your app, pausing after every detection to allow debugging | `contentctl test –-post-test-behavior always_pause mode:all` |
17
+ | Test 1 or more specified detections. If you are testing more than one detection, the paths are space-separated. You may also use shell-expanded regexes | `contentctl test –-post-test-behavior always_pause mode:selected --mode.files detections/endpoint/7zip_commandline_to_smb_share_path.yml detections/cloud/aws_multi_factor_authentication_disabled.yml detections/application/okta*` |
18
+ | Diff your current branch with a target_branch and test detections that have been updated. Your current branch **must be DIFFERENT** than the target_branch | `contentctl test –-post-test-behavior always_pause mode:changes –-mode.target_branch develop` |
19
+ | Perform Integration Testing of all content. Note that Enterprise Security MUST be listed as an app in your contentctl.yml folder, otherwise all tests will subsequently fail | `contentctl test –-enable-integration-testing --post-test-behavior never_pause mode:all` |
8
20
 
9
21
  # Introduction
10
22
  #### Security Is Hard
@@ -8,7 +8,6 @@ from contentctl.objects.enums import SecurityContentProduct, SecurityContentType
8
8
  from contentctl.input.director import Director, DirectorOutputDto
9
9
  from contentctl.output.conf_output import ConfOutput
10
10
  from contentctl.output.conf_writer import ConfWriter
11
- from contentctl.output.ba_yml_output import BAYmlOutput
12
11
  from contentctl.output.api_json_output import ApiJsonOutput
13
12
  from contentctl.output.data_source_writer import DataSourceWriter
14
13
  from contentctl.objects.lookup import Lookup
@@ -86,17 +85,4 @@ class Build:
86
85
 
87
86
  print(f"Build of '{input_dto.config.app.title}' API successful to {input_dto.config.getAPIPath()}")
88
87
 
89
- if input_dto.config.build_ssa:
90
-
91
- srs_path = input_dto.config.getSSAPath() / 'srs'
92
- complex_path = input_dto.config.getSSAPath() / 'complex'
93
- shutil.rmtree(srs_path, ignore_errors=True)
94
- shutil.rmtree(complex_path, ignore_errors=True)
95
- srs_path.mkdir(parents=True)
96
- complex_path.mkdir(parents=True)
97
- ba_yml_output = BAYmlOutput()
98
- ba_yml_output.writeObjects(input_dto.director_output_dto.ssa_detections, str(input_dto.config.getSSAPath()))
99
-
100
- print(f"Build of 'SSA' successful to {input_dto.config.getSSAPath()}")
101
-
102
88
  return input_dto.director_output_dto
@@ -30,7 +30,6 @@ class Validate:
30
30
  [],
31
31
  [],
32
32
  [],
33
- [],
34
33
  )
35
34
 
36
35
  director = Director(director_output_dto)
@@ -28,13 +28,11 @@ from contentctl.enrichments.attack_enrichment import AttackEnrichment
28
28
  from contentctl.enrichments.cve_enrichment import CveEnrichment
29
29
 
30
30
  from contentctl.objects.config import validate
31
- from contentctl.input.ssa_detection_builder import SSADetectionBuilder
32
31
  from contentctl.objects.enums import SecurityContentType
33
32
 
34
33
  from contentctl.objects.enums import DetectionStatus
35
34
  from contentctl.helper.utils import Utils
36
35
 
37
- from contentctl.input.ssa_detection_builder import SSADetectionBuilder
38
36
  from contentctl.objects.enums import SecurityContentType
39
37
 
40
38
  from contentctl.objects.enums import DetectionStatus
@@ -56,7 +54,6 @@ class DirectorOutputDto:
56
54
  macros: list[Macro]
57
55
  lookups: list[Lookup]
58
56
  deployments: list[Deployment]
59
- ssa_detections: list[SSADetection]
60
57
  data_sources: list[DataSource]
61
58
  name_to_content_map: dict[str, SecurityContentObject] = field(default_factory=dict)
62
59
  uuid_to_content_map: dict[UUID, SecurityContentObject] = field(default_factory=dict)
@@ -98,8 +95,6 @@ class DirectorOutputDto:
98
95
  self.stories.append(content)
99
96
  elif isinstance(content, Detection):
100
97
  self.detections.append(content)
101
- elif isinstance(content, SSADetection):
102
- self.ssa_detections.append(content)
103
98
  elif isinstance(content, DataSource):
104
99
  self.data_sources.append(content)
105
100
  else:
@@ -112,11 +107,9 @@ class DirectorOutputDto:
112
107
  class Director():
113
108
  input_dto: validate
114
109
  output_dto: DirectorOutputDto
115
- ssa_detection_builder: SSADetectionBuilder
116
110
 
117
111
  def __init__(self, output_dto: DirectorOutputDto) -> None:
118
112
  self.output_dto = output_dto
119
- self.ssa_detection_builder = SSADetectionBuilder()
120
113
 
121
114
  def execute(self, input_dto: validate) -> None:
122
115
  self.input_dto = input_dto
@@ -129,7 +122,6 @@ class Director():
129
122
  self.createSecurityContent(SecurityContentType.data_sources)
130
123
  self.createSecurityContent(SecurityContentType.playbooks)
131
124
  self.createSecurityContent(SecurityContentType.detections)
132
- self.createSecurityContent(SecurityContentType.ssa_detections)
133
125
 
134
126
 
135
127
  from contentctl.objects.abstract_security_content_objects.detection_abstract import MISSING_SOURCES
@@ -142,12 +134,7 @@ class Director():
142
134
  print("No missing data_sources!")
143
135
 
144
136
  def createSecurityContent(self, contentType: SecurityContentType) -> None:
145
- if contentType == SecurityContentType.ssa_detections:
146
- files = Utils.get_all_yml_files_from_directory(
147
- os.path.join(self.input_dto.path, "ssa_detections")
148
- )
149
- security_content_files = [f for f in files if f.name.startswith("ssa___")]
150
- elif contentType in [
137
+ if contentType in [
151
138
  SecurityContentType.deployments,
152
139
  SecurityContentType.lookups,
153
140
  SecurityContentType.macros,
@@ -179,43 +166,37 @@ class Director():
179
166
  modelDict = YmlReader.load_file(file)
180
167
 
181
168
  if contentType == SecurityContentType.lookups:
182
- lookup = Lookup.model_validate(modelDict,context={"output_dto":self.output_dto, "config":self.input_dto})
169
+ lookup = Lookup.model_validate(modelDict, context={"output_dto":self.output_dto, "config":self.input_dto})
183
170
  self.output_dto.addContentToDictMappings(lookup)
184
171
 
185
172
  elif contentType == SecurityContentType.macros:
186
- macro = Macro.model_validate(modelDict,context={"output_dto":self.output_dto})
173
+ macro = Macro.model_validate(modelDict, context={"output_dto":self.output_dto})
187
174
  self.output_dto.addContentToDictMappings(macro)
188
175
 
189
176
  elif contentType == SecurityContentType.deployments:
190
- deployment = Deployment.model_validate(modelDict,context={"output_dto":self.output_dto})
177
+ deployment = Deployment.model_validate(modelDict, context={"output_dto":self.output_dto})
191
178
  self.output_dto.addContentToDictMappings(deployment)
192
179
 
193
180
  elif contentType == SecurityContentType.playbooks:
194
- playbook = Playbook.model_validate(modelDict,context={"output_dto":self.output_dto})
181
+ playbook = Playbook.model_validate(modelDict, context={"output_dto":self.output_dto})
195
182
  self.output_dto.addContentToDictMappings(playbook)
196
183
 
197
184
  elif contentType == SecurityContentType.baselines:
198
- baseline = Baseline.model_validate(modelDict,context={"output_dto":self.output_dto})
185
+ baseline = Baseline.model_validate(modelDict, context={"output_dto":self.output_dto})
199
186
  self.output_dto.addContentToDictMappings(baseline)
200
187
 
201
188
  elif contentType == SecurityContentType.investigations:
202
- investigation = Investigation.model_validate(modelDict,context={"output_dto":self.output_dto})
189
+ investigation = Investigation.model_validate(modelDict, context={"output_dto":self.output_dto})
203
190
  self.output_dto.addContentToDictMappings(investigation)
204
191
 
205
192
  elif contentType == SecurityContentType.stories:
206
- story = Story.model_validate(modelDict,context={"output_dto":self.output_dto})
193
+ story = Story.model_validate(modelDict, context={"output_dto":self.output_dto})
207
194
  self.output_dto.addContentToDictMappings(story)
208
195
 
209
196
  elif contentType == SecurityContentType.detections:
210
- detection = Detection.model_validate(modelDict,context={"output_dto":self.output_dto, "app":self.input_dto.app})
197
+ detection = Detection.model_validate(modelDict, context={"output_dto":self.output_dto, "app":self.input_dto.app})
211
198
  self.output_dto.addContentToDictMappings(detection)
212
199
 
213
- elif contentType == SecurityContentType.ssa_detections:
214
- self.constructSSADetection(self.ssa_detection_builder, self.output_dto,str(file))
215
- ssa_detection = self.ssa_detection_builder.getObject()
216
- if ssa_detection.status in [DetectionStatus.production.value, DetectionStatus.validation.value]:
217
- self.output_dto.addContentToDictMappings(ssa_detection)
218
-
219
200
  elif contentType == SecurityContentType.data_sources:
220
201
  data_source = DataSource.model_validate(
221
202
  modelDict, context={"output_dto": self.output_dto}
@@ -262,19 +243,3 @@ class Director():
262
243
  f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED"
263
244
  )
264
245
 
265
- def constructSSADetection(
266
- self,
267
- builder: SSADetectionBuilder,
268
- directorOutput: DirectorOutputDto,
269
- file_path: str,
270
- ) -> None:
271
- builder.reset()
272
- builder.setObject(file_path)
273
- builder.addMitreAttackEnrichmentNew(directorOutput.attack_enrichment)
274
- builder.addKillChainPhase()
275
- builder.addCIS()
276
- builder.addNist()
277
- builder.addAnnotations()
278
- builder.addMappings()
279
- builder.addUnitTest()
280
- builder.addRBA()
@@ -46,7 +46,7 @@ class Detection_Abstract(SecurityContentObject):
46
46
  status: DetectionStatus = Field(...)
47
47
  data_source: list[str] = []
48
48
  tags: DetectionTags = Field(...)
49
- search: Union[str, dict[str, Any]] = Field(...)
49
+ search: str = Field(...)
50
50
  how_to_implement: str = Field(..., min_length=4)
51
51
  known_false_positives: str = Field(..., min_length=4)
52
52
 
@@ -65,11 +65,7 @@ class Detection_Abstract(SecurityContentObject):
65
65
 
66
66
  @field_validator("search", mode="before")
67
67
  @classmethod
68
- def validate_presence_of_filter_macro(
69
- cls,
70
- value: Union[str, dict[str, Any]],
71
- info: ValidationInfo
72
- ) -> Union[str, dict[str, Any]]:
68
+ def validate_presence_of_filter_macro(cls, value:str, info:ValidationInfo)->str:
73
69
  """
74
70
  Validates that, if required to be present, the filter macro is present with the proper name.
75
71
  The filter macro MUST be derived from the name of the detection
@@ -83,12 +79,9 @@ class Detection_Abstract(SecurityContentObject):
83
79
 
84
80
  Returns:
85
81
  Union[str, dict[str,Any]]: The search, either in sigma or SPL format.
86
- """
87
-
88
- if isinstance(value, dict):
89
- # If the search is a dict, then it is in Sigma format so return it
90
- return value
91
-
82
+ """
83
+
84
+
92
85
  # Otherwise, the search is SPL.
93
86
 
94
87
  # In the future, we will may add support that makes the inclusion of the
@@ -155,15 +148,16 @@ class Detection_Abstract(SecurityContentObject):
155
148
  @computed_field
156
149
  @property
157
150
  def datamodel(self) -> List[DataModel]:
158
- if isinstance(self.search, str):
159
- return [dm for dm in DataModel if dm.value in self.search]
160
- else:
161
- return []
151
+ return [dm for dm in DataModel if dm.value in self.search]
152
+
153
+
154
+
162
155
 
163
156
  @computed_field
164
157
  @property
165
158
  def source(self) -> str:
166
159
  return self.file_path.absolute().parent.name
160
+
167
161
 
168
162
  deployment: Deployment = Field({})
169
163
 
@@ -249,12 +243,9 @@ class Detection_Abstract(SecurityContentObject):
249
243
  @computed_field
250
244
  @property
251
245
  def providing_technologies(self) -> List[ProvidingTechnology]:
252
- if isinstance(self.search, str):
253
- return ProvidingTechnology.getProvidingTechFromSearch(self.search)
254
- else:
255
- # Dict-formatted searches (sigma) will not have providing technologies
256
- return []
257
-
246
+ return ProvidingTechnology.getProvidingTechFromSearch(self.search)
247
+
248
+
258
249
  @computed_field
259
250
  @property
260
251
  def risk(self) -> list[dict[str, Any]]:
@@ -445,18 +436,13 @@ class Detection_Abstract(SecurityContentObject):
445
436
 
446
437
  @field_validator('lookups', mode="before")
447
438
  @classmethod
448
- def getDetectionLookups(cls, v: list[str], info: ValidationInfo) -> list[Lookup]:
449
- if info.context is None:
450
- raise ValueError("ValidationInfo.context unexpectedly null")
451
-
452
- director: DirectorOutputDto = info.context.get("output_dto", None)
453
-
454
- search: Union[str, dict[str, Any], None] = info.data.get("search", None)
455
- if not isinstance(search, str):
456
- # The search was sigma formatted (or failed other validation and was None), so we will
457
- # not validate macros in it
458
- return []
459
-
439
+ def getDetectionLookups(cls, v:list[str], info:ValidationInfo) -> list[Lookup]:
440
+ director:DirectorOutputDto = info.context.get("output_dto",None)
441
+
442
+ search:Union[str,None] = info.data.get("search",None)
443
+ if search is None:
444
+ raise ValueError("Search was None - is this file missing the search field?")
445
+
460
446
  lookups = Lookup.get_lookups(search, director)
461
447
  return lookups
462
448
 
@@ -496,11 +482,9 @@ class Detection_Abstract(SecurityContentObject):
496
482
 
497
483
  director: DirectorOutputDto = info.context.get("output_dto", None)
498
484
 
499
- search: str | dict[str, Any] | None = info.data.get("search", None)
500
- if not isinstance(search, str):
501
- # The search was sigma formatted (or failed other validation and was None), so we will
502
- # not validate macros in it
503
- return []
485
+ search: str | None = info.data.get("search", None)
486
+ if search is None:
487
+ raise ValueError("Search was None - is this file missing the search field?")
504
488
 
505
489
  search_name: Union[str, Any] = info.data.get("name", None)
506
490
  message = f"Expected 'search_name' to be a string, instead it was [{type(search_name)}]"
@@ -614,45 +598,43 @@ class Detection_Abstract(SecurityContentObject):
614
598
 
615
599
  @model_validator(mode="after")
616
600
  def search_observables_exist_validate(self):
617
- if isinstance(self.search, str):
618
-
619
- observable_fields = [ob.name.lower() for ob in self.tags.observable]
601
+ observable_fields = [ob.name.lower() for ob in self.tags.observable]
620
602
 
621
- # All $field$ fields from the message must appear in the search
622
- field_match_regex = r"\$([^\s.]*)\$"
603
+ # All $field$ fields from the message must appear in the search
604
+ field_match_regex = r"\$([^\s.]*)\$"
623
605
 
624
- missing_fields: set[str]
625
- if self.tags.message:
626
- matches = re.findall(field_match_regex, self.tags.message.lower())
627
- message_fields = [match.replace("$", "").lower() for match in matches]
628
- missing_fields = set([field for field in observable_fields if field not in self.search.lower()])
629
- else:
630
- message_fields = []
631
- missing_fields = set()
632
-
633
- error_messages: list[str] = []
634
- if len(missing_fields) > 0:
635
- error_messages.append(
636
- "The following fields are declared as observables, but do not exist in the "
637
- f"search: {missing_fields}"
638
- )
606
+ missing_fields: set[str]
607
+ if self.tags.message:
608
+ matches = re.findall(field_match_regex, self.tags.message.lower())
609
+ message_fields = [match.replace("$", "").lower() for match in matches]
610
+ missing_fields = set([field for field in observable_fields if field not in self.search.lower()])
611
+ else:
612
+ message_fields = []
613
+ missing_fields = set()
614
+
615
+ error_messages: list[str] = []
616
+ if len(missing_fields) > 0:
617
+ error_messages.append(
618
+ "The following fields are declared as observables, but do not exist in the "
619
+ f"search: {missing_fields}"
620
+ )
639
621
 
640
- missing_fields = set([field for field in message_fields if field not in self.search.lower()])
641
- if len(missing_fields) > 0:
642
- error_messages.append(
643
- "The following fields are used as fields in the message, but do not exist in "
644
- f"the search: {missing_fields}"
645
- )
622
+ missing_fields = set([field for field in message_fields if field not in self.search.lower()])
623
+ if len(missing_fields) > 0:
624
+ error_messages.append(
625
+ "The following fields are used as fields in the message, but do not exist in "
626
+ f"the search: {missing_fields}"
627
+ )
646
628
 
647
- # NOTE: we ignore the type error around self.status because we are using Pydantic's
648
- # use_enum_values configuration
649
- # https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.populate_by_name
650
- if len(error_messages) > 0 and self.status == DetectionStatus.production.value: # type: ignore
651
- msg = (
652
- "Use of fields in observables/messages that do not appear in search:\n\t- "
653
- "\n\t- ".join(error_messages)
654
- )
655
- raise ValueError(msg)
629
+ # NOTE: we ignore the type error around self.status because we are using Pydantic's
630
+ # use_enum_values configuration
631
+ # https://docs.pydantic.dev/latest/api/config/#pydantic.config.ConfigDict.populate_by_name
632
+ if len(error_messages) > 0 and self.status == DetectionStatus.production.value: # type: ignore
633
+ msg = (
634
+ "Use of fields in observables/messages that do not appear in search:\n\t- "
635
+ "\n\t- ".join(error_messages)
636
+ )
637
+ raise ValueError(msg)
656
638
 
657
639
  # Found everything
658
640
  return self
@@ -175,7 +175,6 @@ class validate(Config_Base):
175
175
  "be avoided for performance reasons.")
176
176
  build_app: bool = Field(default=True, description="Should an app be built and output in the build_path?")
177
177
  build_api: bool = Field(default=False, description="Should api objects be built and output in the build_path?")
178
- build_ssa: bool = Field(default=False, description="Should ssa objects be built and output in the build_path?")
179
178
  data_source_TA_validation: bool = Field(default=False, description="Validate latest TA information from Splunkbase")
180
179
 
181
180
  def getAtomicRedTeamRepoPath(self, atomic_red_team_repo_name:str = "atomic-red-team"):
@@ -577,7 +576,6 @@ class test_common(build):
577
576
  # output to dist. We have already built it!
578
577
  self.build_app = False
579
578
  self.build_api = False
580
- self.build_ssa = False
581
579
  self.enrichments = False
582
580
 
583
581
  self.enable_integration_testing = True
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "contentctl"
3
- version = "4.2.5"
3
+ version = "4.3.0"
4
4
  description = "Splunk Content Control Tool"
5
5
  authors = ["STRT <research@splunk.com>"]
6
6
  license = "Apache 2.0"
@@ -24,9 +24,6 @@ splunk-sdk = "^2.0.1"
24
24
  semantic-version = "^2.10.0"
25
25
  bottle = "^0.12.25"
26
26
  tqdm = "^4.66.4"
27
- #splunk-appinspect = "^2.36.0"
28
- pysigma = "^0.11.5"
29
- pysigma-backend-splunk = "^1.1.0"
30
27
  pygit2 = "^1.14.1"
31
28
  tyro = "^0.8.3"
32
29
  gitpython = "^3.1.43"
@@ -1,25 +0,0 @@
1
-
2
- import sys
3
- import shutil
4
- import os
5
-
6
- from dataclasses import dataclass
7
-
8
- from contentctl.input.sigma_converter import *
9
- from contentctl.output.yml_output import YmlOutput
10
-
11
- @dataclass(frozen=True)
12
- class ConvertInputDto:
13
- sigma_converter_input_dto: SigmaConverterInputDto
14
- output_path : str
15
-
16
-
17
- class Convert:
18
-
19
- def execute(self, input_dto: ConvertInputDto) -> None:
20
- sigma_converter_output_dto = SigmaConverterOutputDto([])
21
- sigma_converter = SigmaConverter(sigma_converter_output_dto)
22
- sigma_converter.execute(input_dto.sigma_converter_input_dto)
23
-
24
- yml_output = YmlOutput()
25
- yml_output.writeDetections(sigma_converter_output_dto.detections, input_dto.output_path)
@@ -1,144 +0,0 @@
1
- import re
2
- from sigma.conversion.state import ConversionState
3
- from sigma.rule import SigmaRule
4
- from sigma.conversion.base import TextQueryBackend
5
- from sigma.conversion.deferred import DeferredTextQueryExpression
6
- from sigma.conditions import ConditionFieldEqualsValueExpression, ConditionOR, ConditionAND, ConditionNOT, ConditionItem
7
- from sigma.types import SigmaCompareExpression
8
- from sigma.exceptions import SigmaFeatureNotSupportedByBackendError
9
- from sigma.pipelines.splunk.splunk import splunk_sysmon_process_creation_cim_mapping, splunk_windows_registry_cim_mapping, splunk_windows_file_event_cim_mapping
10
-
11
- from contentctl.objects.ssa_detection import SSADetection
12
-
13
- from typing import ClassVar, Dict, List, Optional, Pattern, Tuple
14
-
15
-
16
- class SplunkBABackend(TextQueryBackend):
17
- """Splunk SPL backend."""
18
- precedence: ClassVar[Tuple[ConditionItem, ConditionItem, ConditionItem]] = (ConditionNOT, ConditionOR, ConditionAND)
19
- group_expression : ClassVar[str] = "({expr})"
20
- parenthesize : bool = True
21
-
22
- or_token : ClassVar[str] = "OR"
23
- and_token : ClassVar[str] = "AND"
24
- not_token : ClassVar[str] = "NOT"
25
- eq_token : ClassVar[str] = "="
26
-
27
- field_quote: ClassVar[str] = '"'
28
- field_quote_pattern: ClassVar[Pattern] = re.compile("^[\w.]+$")
29
-
30
- str_quote : ClassVar[str] = '"'
31
- escape_char : ClassVar[str] = "\\"
32
- wildcard_multi : ClassVar[str] = "%"
33
- wildcard_single : ClassVar[str] = "%"
34
- add_escaped : ClassVar[str] = "\\"
35
-
36
- re_expression : ClassVar[str] = "match({field}, /(?i){regex}/)=true"
37
- re_escape_char : ClassVar[str] = ""
38
- re_escape : ClassVar[Tuple[str]] = ('"',)
39
-
40
- cidr_expression : ClassVar[str] = "{value}"
41
-
42
- compare_op_expression : ClassVar[str] = "{field}{operator}{value}"
43
- compare_operators : ClassVar[Dict[SigmaCompareExpression.CompareOperators, str]] = {
44
- SigmaCompareExpression.CompareOperators.LT : "<",
45
- SigmaCompareExpression.CompareOperators.LTE : "<=",
46
- SigmaCompareExpression.CompareOperators.GT : ">",
47
- SigmaCompareExpression.CompareOperators.GTE : ">=",
48
- }
49
-
50
- field_null_expression : ClassVar[str] = "{field} IS NOT NULL"
51
-
52
- convert_or_as_in : ClassVar[bool] = True
53
- convert_and_as_in : ClassVar[bool] = False
54
- in_expressions_allow_wildcards : ClassVar[bool] = False
55
- field_in_list_expression : ClassVar[str] = "{field} {op} ({list})"
56
- or_in_operator : ClassVar[Optional[str]] = "IN"
57
- list_separator : ClassVar[str] = ", "
58
-
59
- unbound_value_str_expression : ClassVar[str] = '{value}'
60
- unbound_value_num_expression : ClassVar[str] = '{value}'
61
- unbound_value_re_expression : ClassVar[str] = '{value}'
62
-
63
- deferred_start : ClassVar[str] = " "
64
- deferred_separator : ClassVar[str] = " OR "
65
- deferred_only_query : ClassVar[str] = "*"
66
-
67
- wildcard_match_expression : ClassVar[Optional[str]] = "{field} LIKE {value}"
68
-
69
-
70
- def __init__(self, processing_pipeline: Optional["sigma.processing.pipeline.ProcessingPipeline"] = None, collect_errors: bool = False, min_time : str = "-30d", max_time : str = "now", detection : SSADetection = None, field_mapping: dict = None, **kwargs):
71
- super().__init__(processing_pipeline, collect_errors, **kwargs)
72
- self.min_time = min_time or "-30d"
73
- self.max_time = max_time or "now"
74
- self.detection = detection
75
- self.field_mapping = field_mapping
76
-
77
- def finalize_query_data_model(self, rule: SigmaRule, query: str, index: int, state: ConversionState) -> str:
78
-
79
- try:
80
- fields = state.processing_state["fields"]
81
- except KeyError:
82
- raise SigmaFeatureNotSupportedByBackendError("No fields specified by processing pipeline")
83
-
84
- # fields_input_parsing = ''
85
- # for count, value in enumerate(fields):
86
- # fields_input_parsing = fields_input_parsing + value + '=ucast(map_get(input_event, "' + value + '"), "string", null)'
87
- # if not count == len(fields) - 1:
88
- # fields_input_parsing = fields_input_parsing + ', '
89
-
90
- detection_str = """
91
- $main = from source
92
- | eval timestamp = time
93
- | eval metadata_uid = metadata.uid
94
- """.replace("\n", " ")
95
-
96
- parsed_fields = []
97
-
98
- for field in self.field_mapping["mapping"].keys():
99
- mapped_field = self.field_mapping["mapping"][field]
100
- parent = 'parent'
101
- i = 1
102
- values = mapped_field.split('.')
103
- for val in values:
104
- if parent == "parent":
105
- parent = val
106
- continue
107
- else:
108
- new_val = parent + '_' + val
109
- if new_val in parsed_fields:
110
- parent = new_val
111
- i = i + 1
112
- continue
113
-
114
-
115
- new_val_equals = new_val + "="
116
- new_val_IN = new_val + " IN"
117
- if new_val_equals in query or new_val_IN in query:
118
- parser_str = '| eval ' + new_val + ' = ' + 'lower(' + parent + '.' + val + ') '
119
- else:
120
- parser_str = '| eval ' + new_val + ' = ' + parent + '.' + val + ' '
121
- detection_str = detection_str + parser_str
122
- parsed_fields.append(new_val)
123
- parent = new_val
124
- i = i + 1
125
-
126
-
127
- ### Convert sigma values into lower case
128
- lower_query = ""
129
- in_quotes = False
130
- for char in query:
131
- if char == '"':
132
- in_quotes = not in_quotes
133
- if in_quotes:
134
- lower_query += char.lower()
135
- else:
136
- lower_query += char
137
-
138
- detection_str = detection_str + "| where " + lower_query
139
-
140
- detection_str = detection_str.replace("\\\\\\\\", "\\\\")
141
- return detection_str
142
-
143
- def finalize_output_data_model(self, queries: List[str]) -> List[str]:
144
- return queries