contentctl 3.6.0__py3-none-any.whl → 4.0.2__py3-none-any.whl

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 (142) hide show
  1. contentctl/actions/build.py +89 -0
  2. contentctl/actions/detection_testing/DetectionTestingManager.py +48 -49
  3. contentctl/actions/detection_testing/GitService.py +148 -230
  4. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +14 -24
  5. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +43 -17
  6. contentctl/actions/detection_testing/views/DetectionTestingView.py +3 -2
  7. contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -8
  8. contentctl/actions/doc_gen.py +1 -1
  9. contentctl/actions/initialize.py +28 -65
  10. contentctl/actions/inspect.py +260 -0
  11. contentctl/actions/new_content.py +106 -13
  12. contentctl/actions/release_notes.py +168 -144
  13. contentctl/actions/reporting.py +24 -13
  14. contentctl/actions/test.py +39 -20
  15. contentctl/actions/validate.py +25 -48
  16. contentctl/contentctl.py +196 -754
  17. contentctl/enrichments/attack_enrichment.py +69 -19
  18. contentctl/enrichments/cve_enrichment.py +28 -13
  19. contentctl/helper/link_validator.py +24 -26
  20. contentctl/helper/utils.py +7 -3
  21. contentctl/input/director.py +139 -201
  22. contentctl/input/new_content_questions.py +63 -61
  23. contentctl/input/sigma_converter.py +1 -2
  24. contentctl/input/ssa_detection_builder.py +16 -7
  25. contentctl/input/yml_reader.py +4 -3
  26. contentctl/objects/abstract_security_content_objects/detection_abstract.py +487 -154
  27. contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +155 -51
  28. contentctl/objects/alert_action.py +40 -0
  29. contentctl/objects/atomic.py +212 -0
  30. contentctl/objects/baseline.py +44 -43
  31. contentctl/objects/baseline_tags.py +69 -20
  32. contentctl/objects/config.py +857 -125
  33. contentctl/objects/constants.py +0 -1
  34. contentctl/objects/correlation_search.py +1 -1
  35. contentctl/objects/data_source.py +2 -4
  36. contentctl/objects/deployment.py +61 -21
  37. contentctl/objects/deployment_email.py +2 -2
  38. contentctl/objects/deployment_notable.py +4 -4
  39. contentctl/objects/deployment_phantom.py +2 -2
  40. contentctl/objects/deployment_rba.py +3 -4
  41. contentctl/objects/deployment_scheduling.py +2 -3
  42. contentctl/objects/deployment_slack.py +2 -2
  43. contentctl/objects/detection.py +1 -5
  44. contentctl/objects/detection_tags.py +210 -119
  45. contentctl/objects/enums.py +312 -24
  46. contentctl/objects/integration_test.py +1 -1
  47. contentctl/objects/integration_test_result.py +0 -2
  48. contentctl/objects/investigation.py +62 -53
  49. contentctl/objects/investigation_tags.py +30 -6
  50. contentctl/objects/lookup.py +80 -31
  51. contentctl/objects/macro.py +29 -45
  52. contentctl/objects/mitre_attack_enrichment.py +29 -5
  53. contentctl/objects/observable.py +3 -7
  54. contentctl/objects/playbook.py +60 -30
  55. contentctl/objects/playbook_tags.py +45 -8
  56. contentctl/objects/security_content_object.py +1 -5
  57. contentctl/objects/ssa_detection.py +8 -4
  58. contentctl/objects/ssa_detection_tags.py +19 -26
  59. contentctl/objects/story.py +142 -44
  60. contentctl/objects/story_tags.py +46 -33
  61. contentctl/objects/unit_test.py +7 -2
  62. contentctl/objects/unit_test_attack_data.py +10 -19
  63. contentctl/objects/unit_test_baseline.py +1 -1
  64. contentctl/objects/unit_test_old.py +4 -3
  65. contentctl/objects/unit_test_result.py +5 -3
  66. contentctl/objects/unit_test_ssa.py +31 -0
  67. contentctl/output/api_json_output.py +202 -130
  68. contentctl/output/attack_nav_output.py +20 -9
  69. contentctl/output/attack_nav_writer.py +3 -3
  70. contentctl/output/ba_yml_output.py +3 -3
  71. contentctl/output/conf_output.py +125 -391
  72. contentctl/output/conf_writer.py +169 -31
  73. contentctl/output/jinja_writer.py +2 -2
  74. contentctl/output/json_writer.py +17 -5
  75. contentctl/output/new_content_yml_output.py +8 -7
  76. contentctl/output/svg_output.py +17 -27
  77. contentctl/output/templates/analyticstories_detections.j2 +8 -4
  78. contentctl/output/templates/analyticstories_investigations.j2 +1 -1
  79. contentctl/output/templates/analyticstories_stories.j2 +6 -6
  80. contentctl/output/templates/app.conf.j2 +2 -2
  81. contentctl/output/templates/app.manifest.j2 +2 -2
  82. contentctl/output/templates/detection_coverage.j2 +6 -8
  83. contentctl/output/templates/doc_detection_page.j2 +2 -2
  84. contentctl/output/templates/doc_detections.j2 +2 -2
  85. contentctl/output/templates/doc_stories.j2 +1 -1
  86. contentctl/output/templates/es_investigations_investigations.j2 +1 -1
  87. contentctl/output/templates/es_investigations_stories.j2 +1 -1
  88. contentctl/output/templates/header.j2 +2 -1
  89. contentctl/output/templates/macros.j2 +6 -10
  90. contentctl/output/templates/savedsearches_baselines.j2 +5 -5
  91. contentctl/output/templates/savedsearches_detections.j2 +36 -33
  92. contentctl/output/templates/savedsearches_investigations.j2 +4 -4
  93. contentctl/output/templates/transforms.j2 +4 -4
  94. contentctl/output/yml_writer.py +2 -2
  95. contentctl/templates/app_template/README.md +7 -0
  96. contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/nav/default.xml +1 -0
  97. contentctl/templates/app_template/lookups/mitre_enrichment.csv +638 -0
  98. contentctl/templates/deployments/{00_default_anomaly.yml → escu_default_configuration_anomaly.yml} +1 -2
  99. contentctl/templates/deployments/{00_default_baseline.yml → escu_default_configuration_baseline.yml} +1 -2
  100. contentctl/templates/deployments/{00_default_correlation.yml → escu_default_configuration_correlation.yml} +2 -2
  101. contentctl/templates/deployments/{00_default_hunting.yml → escu_default_configuration_hunting.yml} +2 -2
  102. contentctl/templates/deployments/{00_default_ttp.yml → escu_default_configuration_ttp.yml} +1 -2
  103. contentctl/templates/detections/anomalous_usage_of_7zip.yml +0 -1
  104. contentctl/templates/stories/cobalt_strike.yml +0 -1
  105. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/METADATA +36 -15
  106. contentctl-4.0.2.dist-info/RECORD +168 -0
  107. contentctl/actions/detection_testing/DataManipulation.py +0 -149
  108. contentctl/actions/generate.py +0 -91
  109. contentctl/helper/config_handler.py +0 -75
  110. contentctl/input/baseline_builder.py +0 -66
  111. contentctl/input/basic_builder.py +0 -58
  112. contentctl/input/detection_builder.py +0 -370
  113. contentctl/input/investigation_builder.py +0 -42
  114. contentctl/input/new_content_generator.py +0 -95
  115. contentctl/input/playbook_builder.py +0 -68
  116. contentctl/input/story_builder.py +0 -106
  117. contentctl/objects/app.py +0 -214
  118. contentctl/objects/repo_config.py +0 -163
  119. contentctl/objects/test_config.py +0 -630
  120. contentctl/output/templates/macros_detections.j2 +0 -7
  121. contentctl/output/templates/splunk_app/README.md +0 -7
  122. contentctl-3.6.0.dist-info/RECORD +0 -176
  123. /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_story_detail.txt +0 -0
  124. /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_summary.txt +0 -0
  125. /contentctl/{output/templates/splunk_app → templates/app_template}/README/essoc_usage_dashboard.txt +0 -0
  126. /contentctl/{output/templates/splunk_app → templates/app_template}/default/analytic_stories.conf +0 -0
  127. /contentctl/{output/templates/splunk_app → templates/app_template}/default/app.conf +0 -0
  128. /contentctl/{output/templates/splunk_app → templates/app_template}/default/commands.conf +0 -0
  129. /contentctl/{output/templates/splunk_app → templates/app_template}/default/content-version.conf +0 -0
  130. /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/escu_summary.xml +0 -0
  131. /contentctl/{output/templates/splunk_app → templates/app_template}/default/data/ui/views/feedback.xml +0 -0
  132. /contentctl/{output/templates/splunk_app → templates/app_template}/default/distsearch.conf +0 -0
  133. /contentctl/{output/templates/splunk_app → templates/app_template}/default/usage_searches.conf +0 -0
  134. /contentctl/{output/templates/splunk_app → templates/app_template}/default/use_case_library.conf +0 -0
  135. /contentctl/{output/templates/splunk_app → templates/app_template}/metadata/default.meta +0 -0
  136. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon.png +0 -0
  137. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt.png +0 -0
  138. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIconAlt_2x.png +0 -0
  139. /contentctl/{output/templates/splunk_app → templates/app_template}/static/appIcon_2x.png +0 -0
  140. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/LICENSE.md +0 -0
  141. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/WHEEL +0 -0
  142. {contentctl-3.6.0.dist-info → contentctl-4.0.2.dist-info}/entry_points.txt +0 -0
@@ -1,75 +0,0 @@
1
- import os
2
- import collections
3
- import sys
4
- import pathlib
5
-
6
- from contentctl.input.yml_reader import YmlReader
7
- from contentctl.objects.config import Config, TestConfig, ConfigEnrichments
8
- from contentctl.objects.test_config import InfrastructureConfig, Infrastructure
9
- from contentctl.objects.enums import DetectionTestingMode
10
- from typing import Union
11
- import argparse
12
-
13
- from contentctl.objects.enums import (
14
- DetectionTestingTargetInfrastructure,
15
- )
16
-
17
- class ConfigHandler:
18
-
19
- @classmethod
20
- def read_config(cls, args:argparse.Namespace) -> Config:
21
- config_path = pathlib.Path(args.path)/"contentctl.yml"
22
- try:
23
- yml_dict = YmlReader.load_file(config_path, add_fields=False)
24
-
25
- except:
26
- print("ERROR: no contentctl.yml found in given path")
27
- sys.exit(1)
28
-
29
- try:
30
- config = Config.parse_obj(yml_dict)
31
- if args.enable_enrichment:
32
- config.enrichments.attack_enrichment = True
33
- else:
34
- # Use whatever setting is in contentctl.yml
35
- pass
36
- except Exception as e:
37
- raise Exception(f"Error reading config file: {str(e)}")
38
-
39
-
40
- return config
41
-
42
- @classmethod
43
- def read_test_config(cls, args:argparse.Namespace) -> TestConfig:
44
- test_config_path = pathlib.Path(args.path)/"contentctl_test.yml"
45
- try:
46
- yml_dict = YmlReader.load_file(test_config_path, add_fields=False)
47
- except:
48
- print("ERROR: no contentctl_test.yml found in given path")
49
- sys.exit(1)
50
-
51
- try:
52
- if args.dry_run:
53
- yml_dict['apps'] = []
54
- yml_dict['infrastructure_config'] = InfrastructureConfig(infrastructure_type=DetectionTestingTargetInfrastructure.server, ).__dict__
55
- if args.server_info is None:
56
- yml_dict['infrastructure_config']['infrastructures'] = [Infrastructure().__dict__]
57
- if args.mode != DetectionTestingMode.changes:
58
- yml_dict['version_control_config'] = None
59
- if yml_dict.get("version_control_config", None) is not None:
60
- #If they have been passed, override the target and test branch. If not, keep the defaults
61
- yml_dict.get("version_control_config", None)['target_branch'] = args.target_branch or yml_dict.get("version_control_config", None)['target_branch']
62
- yml_dict.get("version_control_config", None)['test_branch'] = args.test_branch or yml_dict.get("version_control_config", None)['test_branch']
63
- if yml_dict.get("infrastructure_config", None) is not None:
64
- yml_dict.get("infrastructure_config", None)['infrastructure_type'] = args.infrastructure or yml_dict.get("infrastructure_config", None)['infrastructure_type']
65
- test_config = TestConfig.parse_obj(yml_dict)
66
- except Exception as e:
67
- raise Exception(f"Error reading test config file: {str(e)}")
68
-
69
-
70
- return test_config
71
-
72
-
73
-
74
-
75
-
@@ -1,66 +0,0 @@
1
- import sys
2
- import pathlib
3
- from pydantic import ValidationError
4
-
5
- from contentctl.input.yml_reader import YmlReader
6
- from contentctl.objects.baseline import Baseline
7
- from contentctl.objects.enums import SecurityContentType
8
- from contentctl.objects.enums import SecurityContentProduct
9
-
10
-
11
- class BaselineBuilder():
12
- baseline : Baseline
13
-
14
- def setObject(self, path: pathlib.Path) -> None:
15
- yml_dict = YmlReader.load_file(path)
16
- yml_dict["tags"]["name"] = yml_dict["name"]
17
-
18
- try:
19
- self.baseline = Baseline.parse_obj(yml_dict)
20
-
21
- except ValidationError as e:
22
- print('Validation Error for file ' + str(path))
23
- print(e)
24
- sys.exit(1)
25
-
26
-
27
- def addDeployment(self, deployments: list) -> None:
28
- if not self.baseline.deployment:
29
-
30
- matched_deployments = []
31
-
32
- for d in deployments:
33
- d_tags = dict(d.tags)
34
- baseline_dict = self.baseline.dict()
35
- baseline_tags_dict = self.baseline.tags.dict()
36
- for d_tag in d_tags.keys():
37
- for attr in baseline_dict.keys():
38
- if attr == d_tag:
39
- if isinstance(baseline_dict[attr], str):
40
- if baseline_dict[attr] == d_tags[d_tag]:
41
- matched_deployments.append(d)
42
- elif isinstance(baseline_dict[attr], list):
43
- if d_tags[d_tag] in baseline_dict[attr]:
44
- matched_deployments.append(d)
45
-
46
- for attr in baseline_tags_dict.keys():
47
- if attr == d_tag:
48
- if isinstance(baseline_tags_dict[attr], str):
49
- if baseline_tags_dict[attr] == d_tags[d_tag]:
50
- matched_deployments.append(d)
51
- elif isinstance(baseline_tags_dict[attr], list):
52
- if d_tags[d_tag] in baseline_tags_dict[attr]:
53
- matched_deployments.append(d)
54
-
55
- if len(matched_deployments) == 0:
56
- raise ValueError('No deployment found for baseline: ' + self.baseline.name)
57
-
58
- self.baseline.deployment = matched_deployments[-1]
59
-
60
-
61
- def reset(self) -> None:
62
- self.baseline = None
63
-
64
-
65
- def getObject(self) -> Baseline:
66
- return self.baseline
@@ -1,58 +0,0 @@
1
- import sys
2
- import pathlib
3
- from pydantic import ValidationError
4
-
5
- from contentctl.objects.security_content_object import SecurityContentObject
6
- from contentctl.input.yml_reader import YmlReader
7
- from contentctl.objects.enums import SecurityContentType
8
- from contentctl.objects.deployment import Deployment
9
- from contentctl.objects.macro import Macro
10
- from contentctl.objects.lookup import Lookup
11
- from contentctl.objects.playbook import Playbook
12
- from contentctl.objects.unit_test import UnitTest
13
-
14
-
15
- class BasicBuilder():
16
- security_content_obj : SecurityContentObject
17
-
18
-
19
- def setObject(self, path: pathlib.Path, type: SecurityContentType) -> None:
20
- yml_dict = YmlReader.load_file(path)
21
- if type == SecurityContentType.deployments:
22
- if "alert_action" in yml_dict:
23
- alert_action_dict = yml_dict["alert_action"]
24
- for key in alert_action_dict.keys():
25
- yml_dict[key] = yml_dict["alert_action"][key]
26
- try:
27
- self.security_content_obj = Deployment.parse_obj(yml_dict)
28
- except ValidationError as e:
29
- print('Validation Error for file ' + str(path))
30
- print(e)
31
- sys.exit(1)
32
- elif type == SecurityContentType.macros:
33
- try:
34
- self.security_content_obj = Macro.parse_obj(yml_dict)
35
- except ValidationError as e:
36
- print('Validation Error for file ' + str(path))
37
- print(e)
38
- sys.exit(1)
39
- elif type == SecurityContentType.lookups:
40
- try:
41
- self.security_content_obj = Lookup.parse_obj(yml_dict)
42
- except ValidationError as e:
43
- print('Validation Error for file ' + str(path))
44
- print(e)
45
- sys.exit(1)
46
- elif type == SecurityContentType.unit_tests:
47
- try:
48
- self.security_content_obj = UnitTest.parse_obj(yml_dict)
49
- except ValidationError as e:
50
- print('Validation Error for file ' + str(path))
51
- print(e)
52
- sys.exit(1)
53
-
54
- def reset(self) -> None:
55
- self.security_content_obj = None
56
-
57
- def getObject(self) -> SecurityContentObject:
58
- return self.security_content_obj
@@ -1,370 +0,0 @@
1
- import sys
2
- import re
3
- import os
4
-
5
- from pydantic import ValidationError
6
-
7
- from contentctl.input.yml_reader import YmlReader
8
- from contentctl.objects.detection import Detection
9
- from contentctl.objects.security_content_object import SecurityContentObject
10
- from contentctl.objects.macro import Macro
11
- from contentctl.objects.lookup import Lookup
12
- from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
13
- from contentctl.objects.integration_test import IntegrationTest
14
- from contentctl.enrichments.cve_enrichment import CveEnrichment
15
- from contentctl.enrichments.splunk_app_enrichment import SplunkAppEnrichment
16
- from contentctl.objects.config import ConfigDetectionConfiguration
17
- from contentctl.objects.constants import ATTACK_TACTICS_KILLCHAIN_MAPPING
18
-
19
- class DetectionBuilder():
20
- security_content_obj : SecurityContentObject
21
-
22
-
23
- def setObject(self, path: str) -> None:
24
- yml_dict = YmlReader.load_file(path)
25
- yml_dict["tags"]["name"] = yml_dict["name"]
26
- self.security_content_obj = Detection.parse_obj(yml_dict)
27
- self.security_content_obj.source = os.path.split(os.path.dirname(self.security_content_obj.file_path))[-1]
28
-
29
-
30
- def addDeployment(self, deployments: list) -> None:
31
- if self.security_content_obj:
32
- if not self.security_content_obj.deployment:
33
- matched_deployments = []
34
- for d in deployments:
35
- d_tags = dict(d.tags)
36
- for d_tag in d_tags.keys():
37
- for attr in dir(self.security_content_obj):
38
- if not (attr.startswith('__') or attr.startswith('_')):
39
- if attr == d_tag:
40
- if type(self.security_content_obj.__getattribute__(attr)) is str:
41
- attr_values = [self.security_content_obj.__getattribute__(attr)]
42
- else:
43
- attr_values = self.security_content_obj.__getattribute__(attr)
44
-
45
- for attr_value in attr_values:
46
- if attr_value == d_tags[d_tag]:
47
- matched_deployments.append(d)
48
-
49
- if len(matched_deployments) == 0:
50
- self.security_content_obj.deployment = None
51
- else:
52
- self.security_content_obj.deployment = matched_deployments[-1]
53
-
54
-
55
- def addRBA(self) -> None:
56
- if self.security_content_obj:
57
-
58
- risk_objects = []
59
- risk_object_user_types = {'user', 'username', 'email address'}
60
- risk_object_system_types = {'device', 'endpoint', 'hostname', 'ip address'}
61
- process_threat_object_types = {'process name','process'}
62
- file_threat_object_types = {'file name','file', 'file hash'}
63
- url_threat_object_types = {'url string','url'}
64
- ip_threat_object_types = {'ip address'}
65
-
66
- if hasattr(self.security_content_obj.tags, 'observable') and hasattr(self.security_content_obj.tags, 'risk_score'):
67
- for entity in self.security_content_obj.tags.observable:
68
-
69
- risk_object = dict()
70
- if 'Victim' in entity.role and entity.type.lower() in risk_object_user_types:
71
- risk_object['risk_object_type'] = 'user'
72
- risk_object['risk_object_field'] = entity.name
73
- risk_object['risk_score'] = self.security_content_obj.tags.risk_score
74
- risk_objects.append(risk_object)
75
-
76
- elif 'Victim' in entity.role and entity.type.lower() in risk_object_system_types:
77
- risk_object['risk_object_type'] = 'system'
78
- risk_object['risk_object_field'] = entity.name
79
- risk_object['risk_score'] = self.security_content_obj.tags.risk_score
80
- risk_objects.append(risk_object)
81
-
82
- elif 'Attacker' in entity.role and entity.type.lower() in process_threat_object_types:
83
- risk_object['threat_object_field'] = entity.name
84
- risk_object['threat_object_type'] = "process"
85
- risk_objects.append(risk_object)
86
-
87
- elif 'Attacker' in entity.role and entity.type.lower() in file_threat_object_types:
88
- risk_object['threat_object_field'] = entity.name
89
- risk_object['threat_object_type'] = "file_name"
90
- risk_objects.append(risk_object)
91
-
92
- elif 'Attacker' in entity.role and entity.type.lower() in ip_threat_object_types:
93
- risk_object['threat_object_field'] = entity.name
94
- risk_object['threat_object_type'] = "ip_address"
95
- risk_objects.append(risk_object)
96
-
97
- elif 'Attacker' in entity.role and entity.type.lower() in url_threat_object_types:
98
- risk_object['threat_object_field'] = entity.name
99
- risk_object['threat_object_type'] = "url"
100
- risk_objects.append(risk_object)
101
-
102
- else:
103
- risk_object['risk_object_type'] = 'other'
104
- risk_object['risk_object_field'] = entity.name
105
- risk_object['risk_score'] = self.security_content_obj.tags.risk_score
106
- risk_objects.append(risk_object)
107
- continue
108
-
109
- if self.security_content_obj.tags.risk_score >= 80:
110
- self.security_content_obj.tags.risk_severity = 'high'
111
- elif (self.security_content_obj.tags.risk_score >= 50 and self.security_content_obj.tags.risk_score <= 79):
112
- self.security_content_obj.tags.risk_severity = 'medium'
113
- else:
114
- self.security_content_obj.tags.risk_severity = 'low'
115
-
116
- self.security_content_obj.risk = risk_objects
117
-
118
-
119
- def addProvidingTechnologies(self) -> None:
120
- if self.security_content_obj:
121
- if 'Endpoint' in str(self.security_content_obj.search):
122
- self.security_content_obj.providing_technologies = ["Sysmon", "Microsoft Windows","Carbon Black Response","CrowdStrike Falcon", "Symantec Endpoint Protection"]
123
-
124
- if "`sysmon`" in str(self.security_content_obj.search):
125
- self.security_content_obj.providing_technologies = ["Microsoft Sysmon"]
126
-
127
- if "`cloudtrail`" in str(self.security_content_obj.search):
128
- self.security_content_obj.providing_technologies = ["Amazon Web Services - Cloudtrail"]
129
-
130
- if '`wineventlog_security`' in self.security_content_obj.search or '`powershell`' in self.security_content_obj.search:
131
- self.security_content_obj.providing_technologies = ["Microsoft Windows"]
132
-
133
- if '`ms_defender`' in self.security_content_obj.search:
134
- self.security_content_obj.providing_technologies = ["Microsoft Defender"]
135
- if '`pingid`' in self.security_content_obj.search:
136
- self.security_content_obj.providing_technologies = ["Ping ID"]
137
- if '`okta' in self.security_content_obj.search:
138
- self.security_content_obj.providing_technologies = ["Okta"]
139
- if '`zeek_' in self.security_content_obj.search:
140
- self.security_content_obj.providing_technologies = ["Zeek"]
141
- if '`amazon_security_lake`' in self.security_content_obj.search:
142
- self.security_content_obj.providing_technologies = ["Amazon Security Lake"]
143
-
144
- if '`azure_monitor_aad`' in self.security_content_obj.search :
145
- self.security_content_obj.providing_technologies = ["Azure AD", "Entra ID"]
146
-
147
- if '`o365_' in self.security_content_obj.search:
148
- self.security_content_obj.providing_technologies = ["Microsoft Office 365"]
149
-
150
- if '`gsuite' in self.security_content_obj.search or '`google_' in self.security_content_obj.search or '`gws_' in self.security_content_obj.search:
151
- self.security_content_obj.providing_technologies = ["Google Workspace","Google Cloud Platform"]
152
-
153
- if '`splunkd_' in self.security_content_obj.search or 'audit_searches' in self.security_content_obj.search:
154
- self.security_content_obj.providing_technologies = ["Splunk Internal Logs"]
155
-
156
- if '`kube' in self.security_content_obj.search:
157
- self.security_content_obj.providing_technologies = ["Kubernetes"]
158
-
159
- def addNesFields(self) -> None:
160
- if self.security_content_obj:
161
- if self.security_content_obj.deployment:
162
- if self.security_content_obj.deployment.notable:
163
- nes_fields = ",".join(list(self.security_content_obj.deployment.notable.nes_fields))
164
- self.security_content_obj.nes_fields = nes_fields
165
-
166
-
167
- def addMappings(self) -> None:
168
- if self.security_content_obj:
169
- keys = ['mitre_attack', 'kill_chain_phases', 'cis20', 'nist']
170
- mappings = {}
171
- for key in keys:
172
- if key == 'mitre_attack':
173
- if getattr(self.security_content_obj.tags, 'mitre_attack_id'):
174
- mappings[key] = getattr(self.security_content_obj.tags, 'mitre_attack_id')
175
- elif getattr(self.security_content_obj.tags, key):
176
- mappings[key] = getattr(self.security_content_obj.tags, key)
177
- self.security_content_obj.mappings = mappings
178
-
179
-
180
- def addAnnotations(self) -> None:
181
- if self.security_content_obj:
182
- annotations = {}
183
- annotation_keys = ['mitre_attack', 'kill_chain_phases', 'cis20', 'nist',
184
- 'analytic_story', 'context', 'impact', 'confidence', 'cve']
185
- for key in annotation_keys:
186
- if key == 'mitre_attack':
187
- if getattr(self.security_content_obj.tags, 'mitre_attack_id'):
188
- annotations[key] = getattr(self.security_content_obj.tags, 'mitre_attack_id')
189
- try:
190
- if getattr(self.security_content_obj.tags, key):
191
- annotations[key] = getattr(self.security_content_obj.tags, key)
192
- except AttributeError as e:
193
- continue
194
- self.security_content_obj.annotations = annotations
195
-
196
-
197
- def addPlaybook(self, playbooks: list) -> None:
198
- if self.security_content_obj:
199
- matched_playbooks = []
200
- for playbook in playbooks:
201
- if playbook.tags.detections:
202
- for detection in playbook.tags.detections:
203
- if detection == self.security_content_obj.name:
204
- matched_playbooks.append(playbook)
205
-
206
- self.security_content_obj.playbooks = matched_playbooks
207
-
208
-
209
- def addBaseline(self, baselines: list) -> None:
210
- if self.security_content_obj:
211
- matched_baselines = []
212
- for baseline in baselines:
213
- for detection in baseline.tags.detections:
214
- if detection == self.security_content_obj.name:
215
- matched_baselines.append(baseline)
216
-
217
- self.security_content_obj.baselines = matched_baselines
218
-
219
-
220
- def addUnitTest(self) -> None:
221
- if self.security_content_obj:
222
- if self.security_content_obj.tests:
223
- self.security_content_obj.test = self.security_content_obj.tests[0]
224
-
225
-
226
- def addMitreAttackEnrichment(self, attack_enrichment: dict) -> None:
227
- if self.security_content_obj:
228
- if attack_enrichment:
229
- if self.security_content_obj.tags.mitre_attack_id:
230
- self.security_content_obj.tags.mitre_attack_enrichments = []
231
-
232
- for mitre_attack_id in self.security_content_obj.tags.mitre_attack_id:
233
- if mitre_attack_id in attack_enrichment:
234
- mitre_attack_enrichment = MitreAttackEnrichment(
235
- mitre_attack_id = mitre_attack_id,
236
- mitre_attack_technique = attack_enrichment[mitre_attack_id]["technique"],
237
- mitre_attack_tactics = sorted(attack_enrichment[mitre_attack_id]["tactics"]),
238
- mitre_attack_groups = sorted(attack_enrichment[mitre_attack_id]["groups"])
239
- )
240
- self.security_content_obj.tags.mitre_attack_enrichments.append(mitre_attack_enrichment)
241
- else:
242
- #print("mitre_attack_id " + mitre_attack_id + " doesn't exist for detecction " + self.security_content_obj.name)
243
- raise ValueError("mitre_attack_id " + mitre_attack_id + " doesn't exist for detection " + self.security_content_obj.name)
244
-
245
-
246
- def addMacros(self, macros: list) -> None:
247
- if self.security_content_obj:
248
- found_macros, missing_macros = Macro.get_macros(self.security_content_obj.search, macros)
249
- name = self.security_content_obj.name.replace(' ', '_').replace('-', '_').replace('.', '_').replace('/', '_').lower() + '_filter'
250
- macro = Macro(name=name, definition='search *', description='Update this macro to limit the output results to filter out false positives.')
251
- found_macros.append(macro)
252
- self.security_content_obj.macros = found_macros
253
- if len(missing_macros) > 0:
254
- raise Exception(f"{self.security_content_obj.name} is missing the following macros: {missing_macros}")
255
-
256
-
257
-
258
- def addLookups(self, lookups: list) -> None:
259
- if self.security_content_obj:
260
- found_lookups, missing_lookups = Lookup.get_lookups(self.security_content_obj.search, lookups)
261
- self.security_content_obj.lookups = found_lookups
262
- if len(missing_lookups) > 0:
263
- raise Exception(f"{self.security_content_obj.name} is missing the following lookups: {missing_lookups}")
264
-
265
-
266
-
267
- def addCve(self) -> None:
268
- if self.security_content_obj:
269
- self.security_content_obj.cve_enrichment = []
270
- if self.security_content_obj.tags.cve:
271
- for cve in self.security_content_obj.tags.cve:
272
- self.security_content_obj.cve_enrichment.append(CveEnrichment.enrich_cve(cve))
273
-
274
-
275
- def addSplunkApp(self) -> None:
276
- if self.security_content_obj:
277
- self.security_content_obj.splunk_app_enrichment = []
278
- if self.security_content_obj.tags.supported_tas:
279
- for splunk_app in self.security_content_obj.tags.supported_tas:
280
- self.security_content_obj.splunk_app_enrichment.append(SplunkAppEnrichment.enrich_splunk_app(splunk_app))
281
-
282
-
283
- def addCIS(self) -> None:
284
- if self.security_content_obj:
285
- if self.security_content_obj.tags.security_domain == "network":
286
- self.security_content_obj.tags.cis20 = ["CIS 13"]
287
- else:
288
- self.security_content_obj.tags.cis20 = ["CIS 10"]
289
-
290
-
291
- def addKillChainPhase(self) -> None:
292
- if self.security_content_obj:
293
- if not self.security_content_obj.tags.kill_chain_phases:
294
- kill_chain_phases = list()
295
- if self.security_content_obj.tags.mitre_attack_enrichments:
296
- for mitre_attack_enrichment in self.security_content_obj.tags.mitre_attack_enrichments:
297
- for mitre_attack_tactic in mitre_attack_enrichment.mitre_attack_tactics:
298
- kill_chain_phases.append(ATTACK_TACTICS_KILLCHAIN_MAPPING[mitre_attack_tactic])
299
- self.security_content_obj.tags.kill_chain_phases = list(dict.fromkeys(kill_chain_phases))
300
-
301
-
302
- def addNist(self) -> None:
303
- if self.security_content_obj:
304
- if self.security_content_obj.type == "TTP":
305
- self.security_content_obj.tags.nist = ["DE.CM"]
306
- else:
307
- self.security_content_obj.tags.nist = ["DE.AE"]
308
-
309
-
310
- def addDatamodel(self) -> None:
311
- if self.security_content_obj:
312
- self.security_content_obj.datamodel = []
313
- data_models = [
314
- "Authentication",
315
- "Change",
316
- "Change_Analysis",
317
- "Email",
318
- "Endpoint",
319
- "Network_Resolution",
320
- "Network_Sessions",
321
- "Network_Traffic",
322
- "Risk",
323
- "Splunk_Audit",
324
- "UEBA",
325
- "Updates",
326
- "Vulnerabilities",
327
- "Web"
328
- ]
329
- for data_model in data_models:
330
- if data_model in self.security_content_obj.search:
331
- self.security_content_obj.datamodel.append(data_model)
332
-
333
- def skipIntegrationTests(self) -> None:
334
- """
335
- Skip all integration tests
336
- """
337
- # Sanity check for typing and in setObject wasn't called yet
338
- if self.security_content_obj is not None and isinstance(self.security_content_obj, Detection):
339
- for test in self.security_content_obj.tests:
340
- if isinstance(test, IntegrationTest):
341
- test.skip("TEST SKIPPED: Skipping all integration tests")
342
- else:
343
- raise ValueError(
344
- "security_content_obj must be an instance of Detection to skip integration tests, "
345
- f"not {type(self.security_content_obj)}"
346
- )
347
-
348
- def skipAllTests(self, manual_test_explanation:str) -> None:
349
- """
350
- Skip all unit and integration tests if the manual_test flag is defined in the yml
351
- """
352
- # Sanity check for typing and in setObject wasn't called yet
353
- if self.security_content_obj is not None and isinstance(self.security_content_obj, Detection):
354
- for test in self.security_content_obj.tests:
355
- #This should skip both unit and integration tests as appropriate
356
- test.skip(f"TEST SKIPPED: Detection marked as 'manual_test' with explanation: {manual_test_explanation}")
357
-
358
- else:
359
- raise ValueError(
360
- "security_content_obj must be an instance of Detection to skip unit and integration tests due "
361
- f"to the presence of the manual_test field, not {type(self.security_content_obj)}"
362
- )
363
-
364
-
365
- def reset(self) -> None:
366
- self.security_content_obj = None
367
-
368
-
369
- def getObject(self) -> SecurityContentObject:
370
- return self.security_content_obj
@@ -1,42 +0,0 @@
1
- import re
2
- import sys
3
-
4
- from pydantic import ValidationError
5
-
6
- from contentctl.objects.investigation import Investigation
7
- from contentctl.input.yml_reader import YmlReader
8
- from contentctl.objects.enums import SecurityContentType
9
-
10
-
11
- class InvestigationBuilder():
12
- investigation: Investigation
13
-
14
- def setObject(self, path: str) -> None:
15
- yml_dict = YmlReader.load_file(path)
16
- try:
17
- self.investigation = Investigation.parse_obj(yml_dict)
18
- except ValidationError as e:
19
- print('Validation Error for file ' + path)
20
- print(e)
21
- sys.exit(1)
22
-
23
- def reset(self) -> None:
24
- self.investigation = None
25
-
26
-
27
- def getObject(self) -> Investigation:
28
- return self.investigation
29
-
30
-
31
- def addInputs(self) -> None:
32
- pattern = r"\$([^\s.]*)\$"
33
- inputs = []
34
-
35
- for input in re.findall(pattern, self.investigation.search):
36
- inputs.append(input)
37
-
38
- self.investigation.inputs = inputs
39
-
40
-
41
- def addLowercaseName(self) -> None:
42
- self.investigation.lowercase_name = self.investigation.name.replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower().replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower()