contentctl 4.1.1__tar.gz → 4.1.3__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 (171) hide show
  1. {contentctl-4.1.1 → contentctl-4.1.3}/PKG-INFO +1 -1
  2. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/GitService.py +7 -16
  3. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/validate.py +38 -18
  4. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/helper/utils.py +21 -0
  5. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/input/director.py +119 -55
  6. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/input/ssa_detection_builder.py +2 -5
  7. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +18 -5
  8. contentctl-4.1.3/contentctl/objects/data_source.py +28 -0
  9. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/detection_tags.py +3 -2
  10. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/enums.py +2 -0
  11. contentctl-4.1.3/contentctl/objects/event_source.py +10 -0
  12. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/story.py +2 -2
  13. {contentctl-4.1.1 → contentctl-4.1.3}/pyproject.toml +1 -1
  14. contentctl-4.1.1/contentctl/objects/data_source.py +0 -21
  15. {contentctl-4.1.1 → contentctl-4.1.3}/LICENSE.md +0 -0
  16. {contentctl-4.1.1 → contentctl-4.1.3}/README.md +0 -0
  17. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/__init__.py +0 -0
  18. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/build.py +0 -0
  19. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/convert.py +0 -0
  20. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/deploy_acs.py +0 -0
  21. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -0
  22. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
  23. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -0
  24. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
  25. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
  26. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/progress_bar.py +0 -0
  27. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/views/DetectionTestingView.py +0 -0
  28. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
  29. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
  30. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
  31. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/doc_gen.py +0 -0
  32. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/initialize.py +0 -0
  33. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/initialize_old.py +0 -0
  34. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/inspect.py +0 -0
  35. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/new_content.py +0 -0
  36. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/release_notes.py +0 -0
  37. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/reporting.py +0 -0
  38. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/actions/test.py +0 -0
  39. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/api.py +0 -0
  40. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/contentctl.py +0 -0
  41. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/enrichments/attack_enrichment.py +0 -0
  42. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/enrichments/cve_enrichment.py +0 -0
  43. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
  44. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/helper/link_validator.py +0 -0
  45. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/helper/logger.py +0 -0
  46. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/input/backend_splunk_ba.py +0 -0
  47. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/input/new_content_questions.py +0 -0
  48. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/input/sigma_converter.py +0 -0
  49. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/input/yml_reader.py +0 -0
  50. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +0 -0
  51. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/alert_action.py +0 -0
  52. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/atomic.py +0 -0
  53. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/base_test.py +0 -0
  54. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/base_test_result.py +0 -0
  55. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/baseline.py +0 -0
  56. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/baseline_tags.py +0 -0
  57. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/config.py +0 -0
  58. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/constants.py +0 -0
  59. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/correlation_search.py +0 -0
  60. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/deployment.py +0 -0
  61. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/deployment_email.py +0 -0
  62. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/deployment_notable.py +0 -0
  63. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/deployment_phantom.py +0 -0
  64. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/deployment_rba.py +0 -0
  65. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/deployment_scheduling.py +0 -0
  66. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/deployment_slack.py +0 -0
  67. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/detection.py +0 -0
  68. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/integration_test.py +0 -0
  69. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/integration_test_result.py +0 -0
  70. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/investigation.py +0 -0
  71. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/investigation_tags.py +0 -0
  72. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/lookup.py +0 -0
  73. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/macro.py +0 -0
  74. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/mitre_attack_enrichment.py +0 -0
  75. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/notable_action.py +0 -0
  76. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/observable.py +0 -0
  77. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/playbook.py +0 -0
  78. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/playbook_tags.py +0 -0
  79. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/risk_analysis_action.py +0 -0
  80. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/risk_object.py +0 -0
  81. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/security_content_object.py +0 -0
  82. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/ssa_detection.py +0 -0
  83. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/ssa_detection_tags.py +0 -0
  84. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/story_tags.py +0 -0
  85. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/test_group.py +0 -0
  86. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/threat_object.py +0 -0
  87. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/unit_test.py +0 -0
  88. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/unit_test_attack_data.py +0 -0
  89. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/unit_test_baseline.py +0 -0
  90. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/unit_test_old.py +0 -0
  91. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/unit_test_result.py +0 -0
  92. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/objects/unit_test_ssa.py +0 -0
  93. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/api_json_output.py +0 -0
  94. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/attack_nav_output.py +0 -0
  95. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/attack_nav_writer.py +0 -0
  96. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/ba_yml_output.py +0 -0
  97. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/conf_output.py +0 -0
  98. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/conf_writer.py +0 -0
  99. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/detection_writer.py +0 -0
  100. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/doc_md_output.py +0 -0
  101. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/finding_report_writer.py +0 -0
  102. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/jinja_writer.py +0 -0
  103. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/json_writer.py +0 -0
  104. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/new_content_yml_output.py +0 -0
  105. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/svg_output.py +0 -0
  106. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/analyticstories_detections.j2 +0 -0
  107. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
  108. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
  109. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/app.conf.j2 +0 -0
  110. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/app.manifest.j2 +0 -0
  111. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/collections.j2 +0 -0
  112. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/content-version.j2 +0 -0
  113. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/detection_count.j2 +0 -0
  114. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/detection_coverage.j2 +0 -0
  115. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_detection_page.j2 +0 -0
  116. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_detections.j2 +0 -0
  117. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_navigation.j2 +0 -0
  118. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
  119. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_playbooks.j2 +0 -0
  120. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
  121. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_stories.j2 +0 -0
  122. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/doc_story_page.j2 +0 -0
  123. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
  124. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
  125. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/finding_report.j2 +0 -0
  126. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/header.j2 +0 -0
  127. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/macros.j2 +0 -0
  128. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/panel.j2 +0 -0
  129. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
  130. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/savedsearches_detections.j2 +0 -0
  131. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
  132. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/transforms.j2 +0 -0
  133. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/templates/workflow_actions.j2 +0 -0
  134. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/yml_output.py +0 -0
  135. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/output/yml_writer.py +0 -0
  136. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/README +0 -0
  137. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_default.yml +0 -0
  138. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
  139. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
  140. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
  141. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/README.md +0 -0
  142. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
  143. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/app.conf +0 -0
  144. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/commands.conf +0 -0
  145. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/content-version.conf +0 -0
  146. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
  147. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
  148. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
  149. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/usage_searches.conf +0 -0
  150. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
  151. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
  152. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/metadata/default.meta +0 -0
  153. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/static/appIcon.png +0 -0
  154. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
  155. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
  156. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
  157. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/datamodels_cim.conf +0 -0
  158. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/datamodels_custom.conf +0 -0
  159. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
  160. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
  161. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
  162. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
  163. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
  164. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/detections/application/.gitkeep +0 -0
  165. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/detections/cloud/.gitkeep +0 -0
  166. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -0
  167. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/detections/network/.gitkeep +0 -0
  168. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/detections/web/.gitkeep +0 -0
  169. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/macros/security_content_ctime.yml +0 -0
  170. {contentctl-4.1.1 → contentctl-4.1.3}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
  171. {contentctl-4.1.1 → contentctl-4.1.3}/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.1.1
3
+ Version: 4.1.3
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -76,33 +76,28 @@ class GitService(BaseModel):
76
76
  if diff.delta.status in (DeltaStatus.ADDED, DeltaStatus.MODIFIED, DeltaStatus.RENAMED):
77
77
  #print(f"{DeltaStatus(diff.delta.status).name:<8}:{diff.delta.new_file.raw_path}")
78
78
  decoded_path = pathlib.Path(diff.delta.new_file.raw_path.decode('utf-8'))
79
- if 'app_template/' in str(decoded_path) or 'ssa_detections' in str(decoded_path) or str(self.config.getBuildDir()) in str(decoded_path):
80
- #Ignore anything that is embedded in the app template.
81
- #Also ignore ssa detections
82
- pass
83
- elif 'detections/' in str(decoded_path) and decoded_path.suffix == ".yml":
79
+ # Note that we only handle updates to detections, lookups, and macros at this time. All other changes are ignored.
80
+ if decoded_path.is_relative_to(self.config.path/"detections") and decoded_path.suffix == ".yml":
84
81
  detectionObject = filepath_to_content_map.get(decoded_path, None)
85
82
  if isinstance(detectionObject, Detection):
86
83
  updated_detections.append(detectionObject)
87
84
  else:
88
85
  raise Exception(f"Error getting detection object for file {str(decoded_path)}")
89
86
 
90
- elif 'macros/' in str(decoded_path) and decoded_path.suffix == ".yml":
87
+ elif decoded_path.is_relative_to(self.config.path/"macros") and decoded_path.suffix == ".yml":
91
88
  macroObject = filepath_to_content_map.get(decoded_path, None)
92
89
  if isinstance(macroObject, Macro):
93
90
  updated_macros.append(macroObject)
94
91
  else:
95
92
  raise Exception(f"Error getting macro object for file {str(decoded_path)}")
96
93
 
97
- elif 'lookups/' in str(decoded_path):
94
+ elif decoded_path.is_relative_to(self.config.path/"lookups"):
98
95
  # We need to convert this to a yml. This means we will catch
99
96
  # both changes to a csv AND changes to the YML that uses it
100
-
101
-
102
97
  if decoded_path.suffix == ".yml":
103
98
  updatedLookup = filepath_to_content_map.get(decoded_path, None)
104
99
  if not isinstance(updatedLookup,Lookup):
105
- raise Exception(f"Expected {decoded_path} to be type {type(Lookup)}, but instead if was {(type(lookupObject))}")
100
+ raise Exception(f"Expected {decoded_path} to be type {type(Lookup)}, but instead if was {(type(updatedLookup))}")
106
101
  updated_lookups.append(updatedLookup)
107
102
 
108
103
  elif decoded_path.suffix == ".csv":
@@ -127,12 +122,8 @@ class GitService(BaseModel):
127
122
  else:
128
123
  pass
129
124
  #print(f"Ignore changes to file {decoded_path} since it is not a detection, macro, or lookup.")
130
-
131
- # else:
132
- # print(f"{diff.delta.new_file.raw_path}:{DeltaStatus(diff.delta.status).name} (IGNORED)")
133
- # pass
134
125
  else:
135
- raise Exception(f"Unrecognized type {type(diff)}")
126
+ raise Exception(f"Unrecognized diff type {type(diff)}")
136
127
 
137
128
 
138
129
  # If a detection has at least one dependency on changed content,
@@ -153,7 +144,7 @@ class GitService(BaseModel):
153
144
  #Print out the names of all modified/new content
154
145
  modifiedAndNewContentString = "\n - ".join(sorted([d.name for d in updated_detections]))
155
146
 
156
- print(f"[{len(updated_detections)}] Pieces of modifed and new content to test:\n - {modifiedAndNewContentString}")
147
+ print(f"[{len(updated_detections)}] Pieces of modifed and new content (this may include experimental/deprecated/manual_test content):\n - {modifiedAndNewContentString}")
157
148
  return updated_detections
158
149
 
159
150
  def getSelected(self, detectionFilenames:List[FilePath])->List[Detection]:
@@ -6,33 +6,47 @@ from pydantic import ValidationError
6
6
  from typing import Union
7
7
 
8
8
  from contentctl.objects.enums import SecurityContentProduct
9
- from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import SecurityContentObject_Abstract
10
- from contentctl.input.director import (
11
- Director,
12
- DirectorOutputDto
9
+ from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
10
+ SecurityContentObject_Abstract,
13
11
  )
12
+ from contentctl.input.director import Director, DirectorOutputDto
14
13
 
15
14
  from contentctl.objects.config import validate
16
15
  from contentctl.enrichments.attack_enrichment import AttackEnrichment
17
16
  from contentctl.enrichments.cve_enrichment import CveEnrichment
18
17
  from contentctl.objects.atomic import AtomicTest
19
18
 
19
+
20
20
  class Validate:
21
21
  def execute(self, input_dto: validate) -> DirectorOutputDto:
22
-
23
- director_output_dto = DirectorOutputDto(AtomicTest.getAtomicTestsFromArtRepo(repo_path=input_dto.getAtomicRedTeamRepoPath(),
24
- enabled=input_dto.enrichments),
25
- AttackEnrichment.getAttackEnrichment(input_dto),
26
- CveEnrichment.getCveEnrichment(input_dto),
27
- [],[],[],[],[],[],[],[],[])
28
-
29
-
22
+
23
+ director_output_dto = DirectorOutputDto(
24
+ AtomicTest.getAtomicTestsFromArtRepo(
25
+ repo_path=input_dto.getAtomicRedTeamRepoPath(),
26
+ enabled=input_dto.enrichments,
27
+ ),
28
+ AttackEnrichment.getAttackEnrichment(input_dto),
29
+ CveEnrichment.getCveEnrichment(input_dto),
30
+ [],
31
+ [],
32
+ [],
33
+ [],
34
+ [],
35
+ [],
36
+ [],
37
+ [],
38
+ [],
39
+ [],
40
+ [],
41
+ )
42
+
30
43
  director = Director(director_output_dto)
31
44
  director.execute(input_dto)
32
-
33
45
  return director_output_dto
34
46
 
35
- def validate_duplicate_uuids(self, security_content_objects:list[SecurityContentObject_Abstract]):
47
+ def validate_duplicate_uuids(
48
+ self, security_content_objects: list[SecurityContentObject_Abstract]
49
+ ):
36
50
  all_uuids = set()
37
51
  duplicate_uuids = set()
38
52
  for elem in security_content_objects:
@@ -45,14 +59,20 @@ class Validate:
45
59
 
46
60
  if len(duplicate_uuids) == 0:
47
61
  return
48
-
62
+
49
63
  # At least once duplicate uuid has been found. Enumerate all
50
64
  # the pieces of content that use duplicate uuids
51
65
  duplicate_messages = []
52
66
  for uuid in duplicate_uuids:
53
- duplicate_uuid_content = [str(content.file_path) for content in security_content_objects if content.id in duplicate_uuids]
54
- duplicate_messages.append(f"Duplicate UUID [{uuid}] in {duplicate_uuid_content}")
55
-
67
+ duplicate_uuid_content = [
68
+ str(content.file_path)
69
+ for content in security_content_objects
70
+ if content.id in duplicate_uuids
71
+ ]
72
+ duplicate_messages.append(
73
+ f"Duplicate UUID [{uuid}] in {duplicate_uuid_content}"
74
+ )
75
+
56
76
  raise ValueError(
57
77
  "ERROR: Duplicate ID(s) found in objects:\n"
58
78
  + "\n - ".join(duplicate_messages)
@@ -25,6 +25,9 @@ class Utils:
25
25
  @staticmethod
26
26
  def get_all_yml_files_from_directory(path: str) -> list[pathlib.Path]:
27
27
  listOfFiles:list[pathlib.Path] = []
28
+ base_path = pathlib.Path(path)
29
+ if not base_path.exists():
30
+ return listOfFiles
28
31
  for (dirpath, dirnames, filenames) in os.walk(path):
29
32
  for file in filenames:
30
33
  if file.endswith(".yml"):
@@ -32,6 +35,24 @@ class Utils:
32
35
 
33
36
  return sorted(listOfFiles)
34
37
 
38
+ @staticmethod
39
+ def get_all_yml_files_from_directory_one_layer_deep(path: str) -> list[pathlib.Path]:
40
+ listOfFiles: list[pathlib.Path] = []
41
+ base_path = pathlib.Path(path)
42
+ if not base_path.exists():
43
+ return listOfFiles
44
+ # Check the base directory
45
+ for item in base_path.iterdir():
46
+ if item.is_file() and item.suffix == '.yml':
47
+ listOfFiles.append(item)
48
+ # Check one subfolder level deep
49
+ for subfolder in base_path.iterdir():
50
+ if subfolder.is_dir() and subfolder.name != "cim":
51
+ for item in subfolder.iterdir():
52
+ if item.is_file() and item.suffix == '.yml':
53
+ listOfFiles.append(item)
54
+ return sorted(listOfFiles)
55
+
35
56
 
36
57
  @staticmethod
37
58
  def add_id(id_dict:dict[str, list[pathlib.Path]], obj:SecurityContentObject, path:pathlib.Path) -> None:
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import sys
3
+ import pathlib
3
4
  from typing import Union
4
5
  from dataclasses import dataclass, field
5
6
  from pydantic import ValidationError
@@ -20,11 +21,24 @@ from contentctl.objects.lookup import Lookup
20
21
  from contentctl.objects.ssa_detection import SSADetection
21
22
  from contentctl.objects.atomic import AtomicTest
22
23
  from contentctl.objects.security_content_object import SecurityContentObject
24
+ from contentctl.objects.data_source import DataSource
25
+ from contentctl.objects.event_source import EventSource
23
26
 
24
27
  from contentctl.enrichments.attack_enrichment import AttackEnrichment
25
28
  from contentctl.enrichments.cve_enrichment import CveEnrichment
26
29
 
27
30
  from contentctl.objects.config import validate
31
+ from contentctl.input.ssa_detection_builder import SSADetectionBuilder
32
+ from contentctl.objects.enums import SecurityContentType
33
+
34
+ from contentctl.objects.enums import DetectionStatus
35
+ from contentctl.helper.utils import Utils
36
+
37
+ from contentctl.input.ssa_detection_builder import SSADetectionBuilder
38
+ from contentctl.objects.enums import SecurityContentType
39
+
40
+ from contentctl.objects.enums import DetectionStatus
41
+ from contentctl.helper.utils import Utils
28
42
 
29
43
 
30
44
  @dataclass
@@ -43,7 +57,8 @@ class DirectorOutputDto:
43
57
  lookups: list[Lookup]
44
58
  deployments: list[Deployment]
45
59
  ssa_detections: list[SSADetection]
46
-
60
+ data_sources: list[DataSource]
61
+ event_sources: list[EventSource]
47
62
  name_to_content_map: dict[str, SecurityContentObject] = field(default_factory=dict)
48
63
  uuid_to_content_map: dict[UUID, SecurityContentObject] = field(default_factory=dict)
49
64
 
@@ -92,66 +107,84 @@ class DirectorOutputDto:
92
107
  self.uuid_to_content_map[content.id] = content
93
108
 
94
109
 
95
- from contentctl.input.ssa_detection_builder import SSADetectionBuilder
96
- from contentctl.objects.enums import SecurityContentType
97
-
98
- from contentctl.objects.enums import DetectionStatus
99
- from contentctl.helper.utils import Utils
100
-
101
-
102
110
  class Director():
103
111
  input_dto: validate
104
112
  output_dto: DirectorOutputDto
105
113
  ssa_detection_builder: SSADetectionBuilder
106
-
107
-
108
114
 
109
115
  def __init__(self, output_dto: DirectorOutputDto) -> None:
110
116
  self.output_dto = output_dto
111
117
  self.ssa_detection_builder = SSADetectionBuilder()
112
-
118
+
113
119
  def execute(self, input_dto: validate) -> None:
114
120
  self.input_dto = input_dto
115
-
116
-
117
121
  self.createSecurityContent(SecurityContentType.deployments)
118
122
  self.createSecurityContent(SecurityContentType.lookups)
119
123
  self.createSecurityContent(SecurityContentType.macros)
120
124
  self.createSecurityContent(SecurityContentType.stories)
121
125
  self.createSecurityContent(SecurityContentType.baselines)
122
126
  self.createSecurityContent(SecurityContentType.investigations)
127
+ self.createSecurityContent(SecurityContentType.event_sources)
128
+ self.createSecurityContent(SecurityContentType.data_sources)
123
129
  self.createSecurityContent(SecurityContentType.playbooks)
124
130
  self.createSecurityContent(SecurityContentType.detections)
125
-
126
-
127
131
  self.createSecurityContent(SecurityContentType.ssa_detections)
128
-
129
132
 
130
133
  def createSecurityContent(self, contentType: SecurityContentType) -> None:
131
134
  if contentType == SecurityContentType.ssa_detections:
132
- files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.path, 'ssa_detections'))
133
- security_content_files = [f for f in files if f.name.startswith('ssa___')]
134
-
135
- elif contentType in [SecurityContentType.deployments,
136
- SecurityContentType.lookups,
137
- SecurityContentType.macros,
138
- SecurityContentType.stories,
139
- SecurityContentType.baselines,
140
- SecurityContentType.investigations,
141
- SecurityContentType.playbooks,
142
- SecurityContentType.detections]:
143
- files = Utils.get_all_yml_files_from_directory(os.path.join(self.input_dto.path, str(contentType.name)))
144
- security_content_files = [f for f in files if not f.name.startswith('ssa___')]
135
+ files = Utils.get_all_yml_files_from_directory(
136
+ os.path.join(self.input_dto.path, "ssa_detections")
137
+ )
138
+ security_content_files = [f for f in files if f.name.startswith("ssa___")]
139
+
140
+ elif contentType == SecurityContentType.data_sources:
141
+ security_content_files = (
142
+ Utils.get_all_yml_files_from_directory_one_layer_deep(
143
+ os.path.join(self.input_dto.path, "data_sources")
144
+ )
145
+ )
146
+
147
+ elif contentType == SecurityContentType.event_sources:
148
+ security_content_files = Utils.get_all_yml_files_from_directory(
149
+ os.path.join(self.input_dto.path, "data_sources", "cloud", "event_sources")
150
+ )
151
+ security_content_files.extend(
152
+ Utils.get_all_yml_files_from_directory(
153
+ os.path.join(self.input_dto.path, "data_sources", "endpoint", "event_sources")
154
+ )
155
+ )
156
+ security_content_files.extend(
157
+ Utils.get_all_yml_files_from_directory(
158
+ os.path.join(self.input_dto.path, "data_sources", "network", "event_sources")
159
+ )
160
+ )
161
+
162
+ elif contentType in [
163
+ SecurityContentType.deployments,
164
+ SecurityContentType.lookups,
165
+ SecurityContentType.macros,
166
+ SecurityContentType.stories,
167
+ SecurityContentType.baselines,
168
+ SecurityContentType.investigations,
169
+ SecurityContentType.playbooks,
170
+ SecurityContentType.detections,
171
+ ]:
172
+ files = Utils.get_all_yml_files_from_directory(
173
+ os.path.join(self.input_dto.path, str(contentType.name))
174
+ )
175
+ security_content_files = [
176
+ f for f in files if not f.name.startswith("ssa___")
177
+ ]
145
178
  else:
146
- raise(Exception(f"Cannot createSecurityContent for unknown product."))
179
+ raise (Exception(f"Cannot createSecurityContent for unknown product."))
147
180
 
148
181
  validation_errors = []
149
-
182
+
150
183
  already_ran = False
151
184
  progress_percent = 0
152
-
153
- for index,file in enumerate(security_content_files):
154
- progress_percent = ((index+1)/len(security_content_files)) * 100
185
+
186
+ for index, file in enumerate(security_content_files):
187
+ progress_percent = ((index + 1) / len(security_content_files)) * 100
155
188
  try:
156
189
  type_string = contentType.name.upper()
157
190
  modelDict = YmlReader.load_file(file)
@@ -167,7 +200,7 @@ class Director():
167
200
  elif contentType == SecurityContentType.deployments:
168
201
  deployment = Deployment.model_validate(modelDict,context={"output_dto":self.output_dto})
169
202
  self.output_dto.addContentToDictMappings(deployment)
170
-
203
+
171
204
  elif contentType == SecurityContentType.playbooks:
172
205
  playbook = Playbook.model_validate(modelDict,context={"output_dto":self.output_dto})
173
206
  self.output_dto.addContentToDictMappings(playbook)
@@ -193,36 +226,67 @@ class Director():
193
226
  ssa_detection = self.ssa_detection_builder.getObject()
194
227
  if ssa_detection.status in [DetectionStatus.production.value, DetectionStatus.validation.value]:
195
228
  self.output_dto.addContentToDictMappings(ssa_detection)
229
+
230
+ elif contentType == SecurityContentType.data_sources:
231
+ data_source = DataSource.model_validate(
232
+ modelDict, context={"output_dto": self.output_dto}
233
+ )
234
+ self.output_dto.data_sources.append(data_source)
235
+
236
+ elif contentType == SecurityContentType.event_sources:
237
+ event_source = EventSource.model_validate(
238
+ modelDict, context={"output_dto": self.output_dto}
239
+ )
240
+ self.output_dto.event_sources.append(event_source)
196
241
 
197
242
  else:
198
- raise Exception(f"Unsupported type: [{contentType}]")
199
-
200
- if (sys.stdout.isatty() and sys.stdin.isatty() and sys.stderr.isatty()) or not already_ran:
201
- already_ran = True
202
- print(f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True)
203
-
204
- except (ValidationError, ValueError) as e:
205
- relative_path = file.absolute().relative_to(self.input_dto.path.absolute())
206
- validation_errors.append((relative_path,e))
207
-
243
+ raise Exception(f"Unsupported type: [{contentType}]")
244
+
245
+ if (
246
+ sys.stdout.isatty() and sys.stdin.isatty() and sys.stderr.isatty()
247
+ ) or not already_ran:
248
+ already_ran = True
249
+ print(
250
+ f"\r{f'{type_string} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...",
251
+ end="",
252
+ flush=True,
253
+ )
208
254
 
209
- print(f"\r{f'{contentType.name.upper()} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...", end="", flush=True)
255
+ except (ValidationError, ValueError) as e:
256
+ relative_path = file.absolute().relative_to(
257
+ self.input_dto.path.absolute()
258
+ )
259
+ validation_errors.append((relative_path, e))
260
+
261
+ print(
262
+ f"\r{f'{contentType.name.upper()} Progress'.rjust(23)}: [{progress_percent:3.0f}%]...",
263
+ end="",
264
+ flush=True,
265
+ )
210
266
  print("Done!")
211
267
 
212
268
  if len(validation_errors) > 0:
213
- errors_string = '\n\n'.join([f"File: {e_tuple[0]}\nError: {str(e_tuple[1])}" for e_tuple in validation_errors])
214
- #print(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
269
+ errors_string = "\n\n".join(
270
+ [
271
+ f"File: {e_tuple[0]}\nError: {str(e_tuple[1])}"
272
+ for e_tuple in validation_errors
273
+ ]
274
+ )
275
+ # print(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
215
276
  # We quit after validation a single type/group of content because it can cause significant cascading errors in subsequent
216
277
  # types of content (since they may import or otherwise use it)
217
- raise Exception(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
218
-
219
-
220
-
221
-
278
+ raise Exception(
279
+ f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED"
280
+ )
222
281
 
223
- def constructSSADetection(self, builder: SSADetectionBuilder, directorOutput:DirectorOutputDto, file_path: str) -> None:
282
+ def constructSSADetection(
283
+ self,
284
+ builder: SSADetectionBuilder,
285
+ directorOutput: DirectorOutputDto,
286
+ file_path: str,
287
+ ) -> None:
224
288
  builder.reset()
225
- builder.setObject(file_path,self.output_dto)
289
+ builder.setObject(file_path)
226
290
  builder.addMitreAttackEnrichmentNew(directorOutput.attack_enrichment)
227
291
  builder.addKillChainPhase()
228
292
  builder.addCIS()
@@ -13,17 +13,14 @@ from contentctl.enrichments.cve_enrichment import CveEnrichment
13
13
  from contentctl.enrichments.splunk_app_enrichment import SplunkAppEnrichment
14
14
  from contentctl.objects.ssa_detection import SSADetection
15
15
  from contentctl.objects.constants import *
16
- from contentctl.input.director import DirectorOutputDto
17
16
  from contentctl.enrichments.attack_enrichment import AttackEnrichment
18
17
 
19
18
  class SSADetectionBuilder():
20
19
  security_content_obj : SSADetection
21
20
 
22
21
 
23
- def setObject(self, path: str,
24
- output_dto:DirectorOutputDto ) -> None:
25
- yml_dict = YmlReader.load_file(path)
26
- #self.security_content_obj = SSADetection.model_validate(yml_dict, context={"output_dto":output_dto})
22
+ def setObject(self, path: str) -> None:
23
+ yml_dict = YmlReader.load_file(path)
27
24
  self.security_content_obj = SSADetection.parse_obj(yml_dict)
28
25
  self.security_content_obj.source = os.path.split(os.path.dirname(self.security_content_obj.file_path))[-1]
29
26
 
@@ -40,7 +40,7 @@ class Detection_Abstract(SecurityContentObject):
40
40
  search: Union[str, dict[str,Any]] = Field(...)
41
41
  how_to_implement: str = Field(..., min_length=4)
42
42
  known_false_positives: str = Field(..., min_length=4)
43
- #data_source: Optional[List[DataSource]] = None
43
+ data_source_objects: Optional[List[DataSource]] = None
44
44
 
45
45
  enabled_by_default: bool = False
46
46
  file_path: FilePath = Field(...)
@@ -369,8 +369,6 @@ class Detection_Abstract(SecurityContentObject):
369
369
  # if not isinstance(director,DirectorOutputDto):
370
370
  # raise ValueError("DirectorOutputDto was not passed in context of Detection model_post_init")
371
371
  director: Optional[DirectorOutputDto] = ctx.get("output_dto",None)
372
- for story in self.tags.analytic_story:
373
- story.detections.append(self)
374
372
 
375
373
  #Ensure that all baselines link to this detection
376
374
  for baseline in self.baselines:
@@ -385,10 +383,25 @@ class Detection_Abstract(SecurityContentObject):
385
383
  if replaced is False:
386
384
  raise ValueError(f"Error, failed to replace detection reference in Baseline '{baseline.name}' to detection '{self.name}'")
387
385
  baseline.tags.detections = new_detections
388
-
389
- return self
390
386
 
387
+ self.data_source_objects = []
388
+ for data_source_obj in director.data_sources:
389
+ for detection_data_source in self.data_source:
390
+ if data_source_obj.name in detection_data_source:
391
+ self.data_source_objects.append(data_source_obj)
392
+
393
+ # Remove duplicate data source objects based on their 'name' property
394
+ unique_data_sources = {}
395
+ for data_source_obj in self.data_source_objects:
396
+ if data_source_obj.name not in unique_data_sources:
397
+ unique_data_sources[data_source_obj.name] = data_source_obj
398
+ self.data_source_objects = list(unique_data_sources.values())
391
399
 
400
+ for story in self.tags.analytic_story:
401
+ story.detections.append(self)
402
+ story.data_sources.extend(self.data_source_objects)
403
+
404
+ return self
392
405
 
393
406
 
394
407
  @field_validator('lookups',mode="before")
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class DataSource(BaseModel):
6
+ name: str
7
+ id: str
8
+ author: str
9
+ source: str
10
+ sourcetype: str
11
+ separator: str = None
12
+ configuration: str = None
13
+ supported_TA: dict
14
+ event_names: list = None
15
+ event_sources: list = None
16
+ fields: list = None
17
+ example_log: str = None
18
+
19
+ def model_post_init(self, ctx:dict[str,Any]):
20
+ context = ctx.get("output_dto")
21
+
22
+ if self.event_names:
23
+ self.event_sources = []
24
+ for event_source in context.event_sources:
25
+ if any(event['event_name'] == event_source.event_name for event in self.event_names):
26
+ self.event_sources.append(event_source)
27
+
28
+ return self
@@ -31,7 +31,7 @@ class DetectionTags(BaseModel):
31
31
  return round((self.confidence * self.impact)/100)
32
32
 
33
33
 
34
- mitre_attack_id: List[Annotated[str, Field(pattern="^T\d{4}(.\d{3})?$")]] = []
34
+ mitre_attack_id: List[Annotated[str, Field(pattern="^T[0-9]{4}(.[0-9]{3})?$")]] = []
35
35
  nist: list[NistCategory] = []
36
36
  observable: List[Observable] = []
37
37
  message: Optional[str] = Field(...)
@@ -138,6 +138,7 @@ class DetectionTags(BaseModel):
138
138
  "risk_score": self.risk_score,
139
139
  "security_domain": self.security_domain,
140
140
  "risk_severity": self.risk_severity,
141
+ "mitre_attack_id": self.mitre_attack_id,
141
142
  "mitre_attack_enrichments": self.mitre_attack_enrichments
142
143
  }
143
144
 
@@ -248,4 +249,4 @@ class DetectionTags(BaseModel):
248
249
  print(missing_tests_string)
249
250
 
250
251
  return matched_tests + [AtomicTest.AtomicTestWhenTestIsMissing(test) for test in missing_tests]
251
-
252
+
@@ -55,6 +55,8 @@ class SecurityContentType(enum.Enum):
55
55
  investigations = 8
56
56
  unit_tests = 9
57
57
  ssa_detections = 10
58
+ data_sources = 11
59
+ event_sources = 12
58
60
 
59
61
  # Bringing these changes back in line will take some time after
60
62
  # the initial merge is complete
@@ -0,0 +1,10 @@
1
+ from __future__ import annotations
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class EventSource(BaseModel):
6
+ event_name: str
7
+ fields: list[str]
8
+ field_mappings: list[dict] = None
9
+ convert_to_log_source: list[dict] = None
10
+ example_log: str = None
@@ -7,7 +7,7 @@ if TYPE_CHECKING:
7
7
  from contentctl.objects.detection import Detection
8
8
  from contentctl.objects.investigation import Investigation
9
9
  from contentctl.objects.baseline import Baseline
10
-
10
+ from contentctl.objects.data_source import DataSource
11
11
 
12
12
  from contentctl.objects.security_content_object import SecurityContentObject
13
13
 
@@ -33,7 +33,7 @@ class Story(SecurityContentObject):
33
33
  detections:List[Detection] = []
34
34
  investigations: List[Investigation] = []
35
35
  baselines: List[Baseline] = []
36
-
36
+ data_sources: List[DataSource] = []
37
37
 
38
38
  def storyAndInvestigationNamesWithApp(self, app_name:str)->List[str]:
39
39
  return [f"{app_name} - {name} - Rule" for name in self.detection_names] + \
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "contentctl"
3
- version = "4.1.1"
3
+ version = "4.1.3"
4
4
  description = "Splunk Content Control Tool"
5
5
  authors = ["STRT <research@splunk.com>"]
6
6
  license = "Apache 2.0"
@@ -1,21 +0,0 @@
1
- from __future__ import annotations
2
- from pydantic import BaseModel
3
-
4
-
5
-
6
- class DataSource(BaseModel):
7
- name: str
8
- id: str
9
- date: str
10
- author: str
11
- type: str
12
- source: str
13
- sourcetype: str
14
- category: str = None
15
- product: str
16
- service: str = None
17
- supported_TA: list
18
- references: list
19
- raw_fields: list
20
- field_mappings: list = None
21
- convert_to_log_source: list = None
File without changes
File without changes