contentctl 4.0.5__py3-none-any.whl → 4.1.1__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.
- contentctl/actions/inspect.py +1 -1
- contentctl/actions/new_content.py +6 -3
- contentctl/actions/test.py +4 -1
- contentctl/actions/validate.py +1 -0
- contentctl/api.py +137 -0
- contentctl/contentctl.py +28 -24
- contentctl/enrichments/cve_enrichment.py +43 -78
- contentctl/input/director.py +72 -72
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +77 -13
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +17 -0
- contentctl/objects/baseline.py +0 -1
- contentctl/objects/config.py +4 -8
- contentctl/objects/detection_tags.py +1 -1
- contentctl/objects/macro.py +8 -7
- contentctl/output/yml_writer.py +39 -1
- {contentctl-4.0.5.dist-info → contentctl-4.1.1.dist-info}/METADATA +7 -8
- {contentctl-4.0.5.dist-info → contentctl-4.1.1.dist-info}/RECORD +21 -23
- contentctl/actions/apav_deploy.py +0 -98
- contentctl/actions/api_deploy.py +0 -151
- contentctl/templates/app_template/default/distsearch.conf +0 -5
- /contentctl/actions/{acs_deploy.py → deploy_acs.py} +0 -0
- {contentctl-4.0.5.dist-info → contentctl-4.1.1.dist-info}/LICENSE.md +0 -0
- {contentctl-4.0.5.dist-info → contentctl-4.1.1.dist-info}/WHEEL +0 -0
- {contentctl-4.0.5.dist-info → contentctl-4.1.1.dist-info}/entry_points.txt +0 -0
|
@@ -26,7 +26,7 @@ from contentctl.objects.integration_test import IntegrationTest
|
|
|
26
26
|
|
|
27
27
|
#from contentctl.objects.playbook import Playbook
|
|
28
28
|
from contentctl.objects.enums import DataSource,ProvidingTechnology
|
|
29
|
-
from contentctl.enrichments.cve_enrichment import
|
|
29
|
+
from contentctl.enrichments.cve_enrichment import CveEnrichmentObj
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class Detection_Abstract(SecurityContentObject):
|
|
@@ -40,7 +40,6 @@ 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
|
-
check_references: bool = False
|
|
44
43
|
#data_source: Optional[List[DataSource]] = None
|
|
45
44
|
|
|
46
45
|
enabled_by_default: bool = False
|
|
@@ -54,6 +53,58 @@ class Detection_Abstract(SecurityContentObject):
|
|
|
54
53
|
# A list of groups of tests, relying on the same data
|
|
55
54
|
test_groups: Union[list[TestGroup], None] = Field(None,validate_default=True)
|
|
56
55
|
|
|
56
|
+
|
|
57
|
+
@field_validator("search", mode="before")
|
|
58
|
+
@classmethod
|
|
59
|
+
def validate_presence_of_filter_macro(cls, value:Union[str, dict[str,Any]], info:ValidationInfo)->Union[str, dict[str,Any]]:
|
|
60
|
+
"""
|
|
61
|
+
Validates that, if required to be present, the filter macro is present with the proper name.
|
|
62
|
+
The filter macro MUST be derived from the name of the detection
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
value (Union[str, dict[str,Any]]): The search. It can either be a string (and should be SPL)
|
|
67
|
+
or a dict, in which case it is Sigma-formatted.
|
|
68
|
+
info (ValidationInfo): The validation info can contain a number of different objects. Today it only contains the director.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Union[str, dict[str,Any]]: The search, either in sigma or SPL format.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
if isinstance(value,dict):
|
|
75
|
+
#If the search is a dict, then it is in Sigma format so return it
|
|
76
|
+
return value
|
|
77
|
+
|
|
78
|
+
# Otherwise, the search is SPL.
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# In the future, we will may add support that makes the inclusion of the
|
|
82
|
+
# filter macro optional or automatically generates it for searches that
|
|
83
|
+
# do not have it. For now, continue to require that all searches have a filter macro.
|
|
84
|
+
FORCE_FILTER_MACRO = True
|
|
85
|
+
if not FORCE_FILTER_MACRO:
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
# Get the required macro name, which is derived from the search name.
|
|
89
|
+
# Note that a separate validation ensures that the file name matches the content name
|
|
90
|
+
name:Union[str,None] = info.data.get("name",None)
|
|
91
|
+
if name is None:
|
|
92
|
+
#The search was sigma formatted (or failed other validation and was None), so we will not validate macros in it
|
|
93
|
+
raise ValueError("Cannot validate filter macro, field 'name' (which is required to validate the macro) was missing from the detection YML.")
|
|
94
|
+
|
|
95
|
+
#Get the file name without the extension. Note this is not a full path!
|
|
96
|
+
file_name = pathlib.Path(cls.contentNameToFileName(name)).stem
|
|
97
|
+
file_name_with_filter = f"`{file_name}_filter`"
|
|
98
|
+
|
|
99
|
+
if file_name_with_filter not in value:
|
|
100
|
+
raise ValueError(f"Detection does not contain the EXACT filter macro {file_name_with_filter}. "
|
|
101
|
+
"This filter macro MUST be present in the search. It usually placed at the end "
|
|
102
|
+
"of the search and is useful for environment-specific filtering of False Positive or noisy results.")
|
|
103
|
+
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
57
108
|
@field_validator("test_groups")
|
|
58
109
|
@classmethod
|
|
59
110
|
def validate_test_groups(cls, value:Union[None, List[TestGroup]], info:ValidationInfo) -> Union[List[TestGroup], None]:
|
|
@@ -144,17 +195,30 @@ class Detection_Abstract(SecurityContentObject):
|
|
|
144
195
|
macros: list[Macro] = Field([],validate_default=True)
|
|
145
196
|
lookups: list[Lookup] = Field([],validate_default=True)
|
|
146
197
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
cve_enrichment: list[CveEnrichmentObj] = Field([], validate_default=True)
|
|
199
|
+
|
|
200
|
+
@model_validator(mode="after")
|
|
201
|
+
def cve_enrichment_func(self, info:ValidationInfo):
|
|
202
|
+
if len(self.cve_enrichment) > 0:
|
|
203
|
+
raise ValueError(f"Error, field 'cve_enrichment' should be empty and "
|
|
204
|
+
f"dynamically populated at runtime. Instead, this field contained: {self.cve_enrichment}")
|
|
205
|
+
|
|
206
|
+
output_dto:Union[DirectorOutputDto,None]= info.context.get("output_dto",None)
|
|
207
|
+
if output_dto is None:
|
|
208
|
+
raise ValueError("Context not provided to detection model post validator")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
enriched_cves:list[CveEnrichmentObj] = []
|
|
155
212
|
|
|
156
|
-
|
|
213
|
+
for cve_id in self.tags.cve:
|
|
214
|
+
try:
|
|
215
|
+
enriched_cves.append(output_dto.cve_enrichment.enrich_cve(cve_id, raise_exception_on_failure=False))
|
|
216
|
+
except Exception as e:
|
|
217
|
+
raise ValueError(f"{e}")
|
|
218
|
+
self.cve_enrichment = enriched_cves
|
|
219
|
+
return self
|
|
157
220
|
|
|
221
|
+
|
|
158
222
|
splunk_app_enrichment: Optional[List[dict]] = None
|
|
159
223
|
|
|
160
224
|
@computed_field
|
|
@@ -382,11 +446,11 @@ class Detection_Abstract(SecurityContentObject):
|
|
|
382
446
|
filter_macro = Macro.model_validate({"name":filter_macro_name,
|
|
383
447
|
"definition":'search *',
|
|
384
448
|
"description":'Update this macro to limit the output results to filter out false positives.'})
|
|
385
|
-
director.
|
|
449
|
+
director.addContentToDictMappings(filter_macro)
|
|
386
450
|
|
|
387
451
|
macros_from_search = Macro.get_macros(search, director)
|
|
388
452
|
|
|
389
|
-
return macros_from_search
|
|
453
|
+
return macros_from_search
|
|
390
454
|
|
|
391
455
|
def get_content_dependencies(self)->list[SecurityContentObject]:
|
|
392
456
|
#Do this separately to satisfy type checker
|
|
@@ -12,6 +12,7 @@ import re
|
|
|
12
12
|
import abc
|
|
13
13
|
import uuid
|
|
14
14
|
import datetime
|
|
15
|
+
import pprint
|
|
15
16
|
from pydantic import BaseModel, field_validator, Field, ValidationInfo, FilePath, HttpUrl, NonNegativeInt, ConfigDict, model_validator, model_serializer
|
|
16
17
|
from typing import Tuple, Optional, List, Union
|
|
17
18
|
import pathlib
|
|
@@ -181,6 +182,22 @@ class SecurityContentObject_Abstract(BaseModel, abc.ABC):
|
|
|
181
182
|
for object in all_objects:
|
|
182
183
|
name_dict[str(pathlib.Path(object.file_path))] = object
|
|
183
184
|
return name_dict
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def __repr__(self)->str:
|
|
188
|
+
# Just use the model_dump functionality that
|
|
189
|
+
# has already been written. This loses some of the
|
|
190
|
+
# richness where objects reference themselves, but
|
|
191
|
+
# is usable
|
|
192
|
+
m = self.model_dump()
|
|
193
|
+
return pprint.pformat(m, indent=3)
|
|
194
|
+
|
|
195
|
+
def __str__(self)->str:
|
|
196
|
+
return(self.__repr__())
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
184
201
|
|
|
185
202
|
|
|
186
203
|
|
contentctl/objects/baseline.py
CHANGED
|
@@ -31,7 +31,6 @@ class Baseline(SecurityContentObject):
|
|
|
31
31
|
search: str = Field(..., min_length=4)
|
|
32
32
|
how_to_implement: str = Field(..., min_length=4)
|
|
33
33
|
known_false_positives: str = Field(..., min_length=4)
|
|
34
|
-
check_references: bool = False #Validation is done in order, this field must be defined first
|
|
35
34
|
tags: BaselineTags = Field(...)
|
|
36
35
|
|
|
37
36
|
# enrichment
|
contentctl/objects/config.py
CHANGED
|
@@ -154,6 +154,10 @@ class Config_Base(BaseModel):
|
|
|
154
154
|
|
|
155
155
|
path: DirectoryPath = Field(default=DirectoryPath("."), description="The root of your app.")
|
|
156
156
|
app:CustomApp = Field(default_factory=CustomApp)
|
|
157
|
+
verbose:bool = Field(default=False, description="Enable verbose error logging, including a stacktrace. "
|
|
158
|
+
"This option makes debugging contentctl errors much easier, but produces way more "
|
|
159
|
+
"output than is useful under most uses cases. "
|
|
160
|
+
"Please use this flag if you are submitting a bug report or issue on GitHub.")
|
|
157
161
|
|
|
158
162
|
@field_serializer('path',when_used='always')
|
|
159
163
|
def serialize_path(path: DirectoryPath)->str:
|
|
@@ -269,14 +273,6 @@ class Infrastructure(BaseModel):
|
|
|
269
273
|
instance_name: str = Field(...)
|
|
270
274
|
|
|
271
275
|
|
|
272
|
-
class deploy_rest(build):
|
|
273
|
-
model_config = ConfigDict(use_enum_values=True,validate_default=True, arbitrary_types_allowed=True)
|
|
274
|
-
|
|
275
|
-
target:Infrastructure = Infrastructure(instance_name="splunk_target_host", instance_address="localhost")
|
|
276
|
-
#This will overwrite existing content without promprting for confirmation
|
|
277
|
-
overwrite_existing_content:bool = Field(default=True, description="Overwrite existing macros and savedsearches in your enviornment")
|
|
278
|
-
|
|
279
|
-
|
|
280
276
|
class Container(Infrastructure):
|
|
281
277
|
model_config = ConfigDict(use_enum_values=True,validate_default=True, arbitrary_types_allowed=True)
|
|
282
278
|
instance_address:str = Field(default="localhost", description="Address of your splunk server.")
|
|
@@ -145,7 +145,7 @@ class DetectionTags(BaseModel):
|
|
|
145
145
|
@model_validator(mode="after")
|
|
146
146
|
def addAttackEnrichment(self, info:ValidationInfo):
|
|
147
147
|
if len(self.mitre_attack_enrichments) > 0:
|
|
148
|
-
raise ValueError(f"Error, field 'mitre_attack_enrichment' should be empty and dynamically populated at runtime. Instead, this field contained: {
|
|
148
|
+
raise ValueError(f"Error, field 'mitre_attack_enrichment' should be empty and dynamically populated at runtime. Instead, this field contained: {self.mitre_attack_enrichments}")
|
|
149
149
|
|
|
150
150
|
output_dto:Union[DirectorOutputDto,None]= info.context.get("output_dto",None)
|
|
151
151
|
if output_dto is None:
|
contentctl/objects/macro.py
CHANGED
|
@@ -9,13 +9,14 @@ if TYPE_CHECKING:
|
|
|
9
9
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
MACROS_TO_IGNORE
|
|
16
|
-
MACROS_TO_IGNORE.add("
|
|
17
|
-
MACROS_TO_IGNORE.add("
|
|
18
|
-
MACROS_TO_IGNORE.add("
|
|
12
|
+
#The following macros are included in commonly-installed apps.
|
|
13
|
+
#As such, we will ignore if they are missing from our app.
|
|
14
|
+
#Included in
|
|
15
|
+
MACROS_TO_IGNORE = set(["drop_dm_object_name"]) # Part of CIM/Splunk_SA_CIM
|
|
16
|
+
MACROS_TO_IGNORE.add("get_asset") #SA-IdentityManagement, part of Enterprise Security
|
|
17
|
+
MACROS_TO_IGNORE.add("get_risk_severity") #SA-ThreatIntelligence, part of Enterprise Security
|
|
18
|
+
MACROS_TO_IGNORE.add("cim_corporate_web_domain_search") #Part of CIM/Splunk_SA_CIM
|
|
19
|
+
#MACROS_TO_IGNORE.add("prohibited_processes")
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class Macro(SecurityContentObject):
|
contentctl/output/yml_writer.py
CHANGED
|
@@ -8,4 +8,42 @@ class YmlWriter:
|
|
|
8
8
|
def writeYmlFile(file_path : str, obj : dict[Any,Any]) -> None:
|
|
9
9
|
|
|
10
10
|
with open(file_path, 'w') as outfile:
|
|
11
|
-
yaml.safe_dump(obj, outfile, default_flow_style=False, sort_keys=False)
|
|
11
|
+
yaml.safe_dump(obj, outfile, default_flow_style=False, sort_keys=False)
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def writeDetection(file_path: str, obj: dict[Any,Any]) -> None:
|
|
15
|
+
output = dict()
|
|
16
|
+
output["name"] = obj["name"]
|
|
17
|
+
output["id"] = obj["id"]
|
|
18
|
+
output["version"] = obj["version"]
|
|
19
|
+
output["date"] = obj["date"]
|
|
20
|
+
output["author"] = obj["author"]
|
|
21
|
+
output["type"] = obj["type"]
|
|
22
|
+
output["status"] = obj["status"]
|
|
23
|
+
output["data_source"] = obj['data_sources']
|
|
24
|
+
output["description"] = obj["description"]
|
|
25
|
+
output["search"] = obj["search"]
|
|
26
|
+
output["how_to_implement"] = obj["how_to_implement"]
|
|
27
|
+
output["known_false_positives"] = obj["known_false_positives"]
|
|
28
|
+
output["references"] = obj["references"]
|
|
29
|
+
output["tags"] = obj["tags"]
|
|
30
|
+
output["tests"] = obj["tags"]
|
|
31
|
+
|
|
32
|
+
YmlWriter.writeYmlFile(file_path=file_path, obj=output)
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def writeStory(file_path: str, obj: dict[Any,Any]) -> None:
|
|
36
|
+
output = dict()
|
|
37
|
+
output['name'] = obj['name']
|
|
38
|
+
output['id'] = obj['id']
|
|
39
|
+
output['version'] = obj['version']
|
|
40
|
+
output['date'] = obj['date']
|
|
41
|
+
output['author'] = obj['author']
|
|
42
|
+
output['description'] = obj['description']
|
|
43
|
+
output['narrative'] = obj['narrative']
|
|
44
|
+
output['references'] = obj['references']
|
|
45
|
+
output['tags'] = obj['tags']
|
|
46
|
+
|
|
47
|
+
YmlWriter.writeYmlFile(file_path=file_path, obj=output)
|
|
48
|
+
|
|
49
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: contentctl
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.1.1
|
|
4
4
|
Summary: Splunk Content Control Tool
|
|
5
5
|
License: Apache 2.0
|
|
6
6
|
Author: STRT
|
|
@@ -10,25 +10,24 @@ Classifier: License :: Other/Proprietary License
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Requires-Dist: Jinja2 (>=3.1.
|
|
13
|
+
Requires-Dist: Jinja2 (>=3.1.4,<4.0.0)
|
|
14
14
|
Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
|
|
15
15
|
Requires-Dist: attackcti (>=0.3.7,<0.4.0)
|
|
16
16
|
Requires-Dist: bottle (>=0.12.25,<0.13.0)
|
|
17
17
|
Requires-Dist: docker (>=7.1.0,<8.0.0)
|
|
18
18
|
Requires-Dist: gitpython (>=3.1.43,<4.0.0)
|
|
19
19
|
Requires-Dist: pycvesearch (>=1.2,<2.0)
|
|
20
|
-
Requires-Dist: pydantic (>=2.
|
|
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.
|
|
23
|
-
Requires-Dist: pysigma-backend-splunk (>=1.0
|
|
22
|
+
Requires-Dist: pysigma (>=0.11.5,<0.12.0)
|
|
23
|
+
Requires-Dist: pysigma-backend-splunk (>=1.1.0,<2.0.0)
|
|
24
24
|
Requires-Dist: questionary (>=2.0.1,<3.0.0)
|
|
25
25
|
Requires-Dist: requests (>=2.32.2,<2.33.0)
|
|
26
26
|
Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
|
|
27
|
-
Requires-Dist: setuptools (>=69.5.1,<
|
|
27
|
+
Requires-Dist: setuptools (>=69.5.1,<71.0.0)
|
|
28
28
|
Requires-Dist: splunk-sdk (>=2.0.1,<3.0.0)
|
|
29
|
-
Requires-Dist: tqdm (>=4.66.
|
|
29
|
+
Requires-Dist: tqdm (>=4.66.4,<5.0.0)
|
|
30
30
|
Requires-Dist: tyro (>=0.8.3,<0.9.0)
|
|
31
|
-
Requires-Dist: validators (>=0.22.0,<0.23.0)
|
|
32
31
|
Requires-Dist: xmltodict (>=0.13.0,<0.14.0)
|
|
33
32
|
Description-Content-Type: text/markdown
|
|
34
33
|
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
contentctl/__init__.py,sha256=IMjkMO3twhQzluVTo8Z6rE7Eg-9U79_LGKMcsWLKBkY,22
|
|
2
|
-
contentctl/actions/acs_deploy.py,sha256=mf3uk495H1EU_LNN-TiOsYCo18HMGoEBMb6ojeTr0zw,1418
|
|
3
|
-
contentctl/actions/apav_deploy.py,sha256=vjq-24zCLRvNyS0FSLyE4L2b4etG-qo4OM6Z9P0NYK4,2999
|
|
4
|
-
contentctl/actions/api_deploy.py,sha256=h8r_CjsQo4RXzBN4Q8DqoPh6e7JfNDoXdcxT1nrsaRQ,6965
|
|
5
2
|
contentctl/actions/build.py,sha256=BVc-1E63zeUQ9wWAHTC_fLNvfEK5YT3Z6_QLiE72TQs,4765
|
|
6
3
|
contentctl/actions/convert.py,sha256=0KBWLxvP1hSPXpExePqpOQPRvlQLamvPLyQqeTIWNbk,704
|
|
4
|
+
contentctl/actions/deploy_acs.py,sha256=mf3uk495H1EU_LNN-TiOsYCo18HMGoEBMb6ojeTr0zw,1418
|
|
7
5
|
contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=zg8JasDjCpSC-yhseEyUwO8qbDJIUJbhlus9Li9ZAnA,8818
|
|
8
6
|
contentctl/actions/detection_testing/GitService.py,sha256=Rm5Usc0EZk87rk1W8eKyED6b5CdD0YUQZMjkPfk3ztU,8666
|
|
9
7
|
contentctl/actions/detection_testing/generate_detection_coverage_badge.py,sha256=N5mznaeErVak3mOBwsd0RDBFJO3bku0EZvpayCyU-uk,2259
|
|
@@ -18,34 +16,35 @@ contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py,sha256=6me
|
|
|
18
16
|
contentctl/actions/doc_gen.py,sha256=YNc1VYA0ikL1hWDHYjfEOmUkfhy8PEIdvTyC4ZLxQRY,863
|
|
19
17
|
contentctl/actions/initialize.py,sha256=2h3_A68mNWcyZjbrKF-OeQXBi5p4Zu3z74K7QxEtII4,1749
|
|
20
18
|
contentctl/actions/initialize_old.py,sha256=0qXbW_fNDvkcnEeL6Zpte8d-hpTu1REyzHsXOCY-YB8,9333
|
|
21
|
-
contentctl/actions/inspect.py,sha256=
|
|
22
|
-
contentctl/actions/new_content.py,sha256=
|
|
19
|
+
contentctl/actions/inspect.py,sha256=6gVVKmV5CUUYOkNNVTMPKj9bM1uXVthgGCoFKZGDeS8,12628
|
|
20
|
+
contentctl/actions/new_content.py,sha256=4gTlxV0fmdsSETPX4T9uQPpIAm--Jf2vc6Vm3w-RkfI,6128
|
|
23
21
|
contentctl/actions/release_notes.py,sha256=akkFfLhsJuaPUyjsb6dLlKt9cUM-JApAjTFQMbYoXeM,13115
|
|
24
22
|
contentctl/actions/reporting.py,sha256=MJEmvmoA1WnSFZEU9QM6daL_W94oOX0WXAcX1qAM2As,1583
|
|
25
|
-
contentctl/actions/test.py,sha256=
|
|
26
|
-
contentctl/actions/validate.py,sha256
|
|
27
|
-
contentctl/
|
|
23
|
+
contentctl/actions/test.py,sha256=dx7f750_MrlvysxOmOdIro1bH0iVKF4K54TSwhvU2MU,5146
|
|
24
|
+
contentctl/actions/validate.py,sha256=6_M5IMi68Pv4CNw69balQTkfRdjMTurUUv7z8BpGh3Y,2454
|
|
25
|
+
contentctl/api.py,sha256=FBOpRhbBCBdjORmwe_8MPQ3PRZ6T0KrrFcfKovVFkug,6343
|
|
26
|
+
contentctl/contentctl.py,sha256=Vr2cuvaPjpJpYvD9kVoYq7iD6rhLQEpTKmcGoq4emhA,10470
|
|
28
27
|
contentctl/enrichments/attack_enrichment.py,sha256=EkEloG3hMmPTloPyYiVkhq3iT_BieXaJmprJ5stfyRw,6732
|
|
29
|
-
contentctl/enrichments/cve_enrichment.py,sha256=
|
|
28
|
+
contentctl/enrichments/cve_enrichment.py,sha256=IzkKSdnQi3JrAUUyLpcGA_Y2g_B7latq9bOIMlaMpGg,2315
|
|
30
29
|
contentctl/enrichments/splunk_app_enrichment.py,sha256=zDNHFLZTi2dJ1gdnh0sHkD6F1VtkblqFnhacFcCMBfc,3418
|
|
31
30
|
contentctl/helper/link_validator.py,sha256=-XorhxfGtjLynEL1X4hcpRMiyemogf2JEnvLwhHq80c,7139
|
|
32
31
|
contentctl/helper/logger.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
32
|
contentctl/helper/utils.py,sha256=iZ6keMdTCs1XySiDVoGIGkMSxD_eDUphwEW-VUYA6vM,15659
|
|
34
33
|
contentctl/input/backend_splunk_ba.py,sha256=Y70tJqgaUM0nzfm2SiGMof4HkhY84feqf-xnRx1xPb4,5861
|
|
35
|
-
contentctl/input/director.py,sha256=
|
|
34
|
+
contentctl/input/director.py,sha256=JsmN35xamnMNl3Wug7KGVLFbVfa5F7jf0ToHEwXFcqM,10965
|
|
36
35
|
contentctl/input/new_content_questions.py,sha256=o4prlBoUhEMxqpZukquI9WKbzfFJfYhEF7a8m2q_BEE,5565
|
|
37
36
|
contentctl/input/sigma_converter.py,sha256=ATFNW7boNngp5dmWM7Gr4rMZrUKjvKW2_qu28--FdiU,19391
|
|
38
37
|
contentctl/input/ssa_detection_builder.py,sha256=43B7q4A8MEMjUU-FR7UapO80deW6BooV9WYzZWxcvgI,8377
|
|
39
38
|
contentctl/input/yml_reader.py,sha256=oaal24UP8rDXkCmN5I3GnIheZrsgkhbKOlzXtyhB474,1475
|
|
40
|
-
contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=
|
|
41
|
-
contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=
|
|
39
|
+
contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=BPY0D_evVu4n3xGE7uGJRP5RKOtPbNKNPnZqvo0qC1A,33311
|
|
40
|
+
contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=IVr26xFrIlTvsqQoqwYl4cmcfaP9BeYt9I0QTriKmwE,8451
|
|
42
41
|
contentctl/objects/alert_action.py,sha256=E9gjCn5C31h0sN7k90KNe4agRxFFSnMW_Z-Ri_3YQss,1335
|
|
43
42
|
contentctl/objects/atomic.py,sha256=a_G_iliAm86BunpAAG86aAL3LAEGpd9Crp7t7-PxYvI,8979
|
|
44
43
|
contentctl/objects/base_test.py,sha256=6hCL9K-N_jJx1zLbuZQCsB93_XWj6JcGGs2PbbjzJWo,1028
|
|
45
44
|
contentctl/objects/base_test_result.py,sha256=dPupudgeXW64Emk9YJfS5JhUXbZwpEZrrx_DiqbRgvU,4752
|
|
46
|
-
contentctl/objects/baseline.py,sha256=
|
|
45
|
+
contentctl/objects/baseline.py,sha256=Lb1vJKtDdlDrzWgrdkC9oQao_TnRrOxSwOWHf4trtaU,2150
|
|
47
46
|
contentctl/objects/baseline_tags.py,sha256=JLdlCUc_DEccMQD6f-sa2qD8pcxYiwMUT_sRZEhW7ZA,2978
|
|
48
|
-
contentctl/objects/config.py,sha256=
|
|
47
|
+
contentctl/objects/config.py,sha256=tK0BY4A9Go5jp8tpOSwgczuOAyu9dMPvC0nyOHeO-74,43642
|
|
49
48
|
contentctl/objects/constants.py,sha256=1LjiK9A7t0aHHkJz2mrW-DImdW1P98GPssTwmwNNI_M,3468
|
|
50
49
|
contentctl/objects/correlation_search.py,sha256=B97vCt2Ew7PGgqd5Y9l6RD3DJdy51Eh7Gzkxxs2xqZ0,36891
|
|
51
50
|
contentctl/objects/data_source.py,sha256=ELNsNsarVHJgytPTcaGZOoWgub2v_-Q0xtc_-xUM8yg,405
|
|
@@ -57,14 +56,14 @@ contentctl/objects/deployment_rba.py,sha256=YFLSKzLU7s8Bt1cJkSBWlfCsc_2MfgiwyaDi
|
|
|
57
56
|
contentctl/objects/deployment_scheduling.py,sha256=bQjbJHNaUGdU1VAGV8-nFOHzHutbIlt7FZpUvR1CV4Y,198
|
|
58
57
|
contentctl/objects/deployment_slack.py,sha256=P6z8OLHDKcDWx7nbKWasqBc3dFRatGcpO2GtmxzVV8I,135
|
|
59
58
|
contentctl/objects/detection.py,sha256=3W41cXf3ECjWuPqWrseqSLC3PAA7O5_nENWWM6MPK0Y,620
|
|
60
|
-
contentctl/objects/detection_tags.py,sha256=
|
|
59
|
+
contentctl/objects/detection_tags.py,sha256=QR906JN8cf5et5aPf-AluEEyP3IvdUQ_KzxKffMSjrc,10261
|
|
61
60
|
contentctl/objects/enums.py,sha256=2gLRtJ-dHW_xMFdbjOp0LaX_fEV0V-YAZn2JY9gUzJ8,14030
|
|
62
61
|
contentctl/objects/integration_test.py,sha256=W_VksBN_cRo7DTXdr1aLujjS9mgkEp0uvoNpmL0dVnQ,1273
|
|
63
62
|
contentctl/objects/integration_test_result.py,sha256=DrIZRRlILSHGcsK_Rlm3KJLnbKPtIen8uEPFi4ZdJ8s,370
|
|
64
63
|
contentctl/objects/investigation.py,sha256=JRoZxc_qi1fu_VFTRaxOc3B7zzSzCfEURsNzWPUCrtY,2620
|
|
65
64
|
contentctl/objects/investigation_tags.py,sha256=nFpMRKBVBsW21YW_vy2G1lXaSARX-kfFyrPoCyE77Q8,1280
|
|
66
65
|
contentctl/objects/lookup.py,sha256=P8YbzdDAj_MsTBJTEsym35zhQjiN9Eq0MlfON-qvuTM,4556
|
|
67
|
-
contentctl/objects/macro.py,sha256=
|
|
66
|
+
contentctl/objects/macro.py,sha256=9nE-bxkFhtaltHOUCr0luU8jCCthmglHjhKs6Q2YzLU,2684
|
|
68
67
|
contentctl/objects/mitre_attack_enrichment.py,sha256=bWrMG-Xj3knmULR5q2YZk7mloJBdQUzU1moZfEw9lQM,1073
|
|
69
68
|
contentctl/objects/notable_action.py,sha256=ValkblBaG-60TF19y_vSnNzoNZ3eg48wIfr0qZxyKTA,1605
|
|
70
69
|
contentctl/objects/observable.py,sha256=-nbVASkwyLpstWQk9Za1Hyjg0etGHiZArg7doEOS02k,1156
|
|
@@ -127,7 +126,7 @@ contentctl/output/templates/savedsearches_investigations.j2,sha256=aFIDK4NqtsZr3
|
|
|
127
126
|
contentctl/output/templates/transforms.j2,sha256=-cSoie0LgJwibtW-GMhc9BQlmS6h1s1Vykm9O2M0f9Y,1456
|
|
128
127
|
contentctl/output/templates/workflow_actions.j2,sha256=DFoZVnCa8dMRHjW2AdpoydBC0THgiH_W-Nx7WI4-uR4,925
|
|
129
128
|
contentctl/output/yml_output.py,sha256=xtTD3f_WWy8O6Joi4S8gG9paot8JpQFRlwt17_ek5B4,2682
|
|
130
|
-
contentctl/output/yml_writer.py,sha256=
|
|
129
|
+
contentctl/output/yml_writer.py,sha256=zZJ3aK-l0YQXbDweS-XZKejHblyhy2eliSthZZEogUs,1668
|
|
131
130
|
contentctl/templates/README,sha256=Hg4LI9g_ss8o3u060woDkhunLXHMtKOhuFK2i-xJpuM,133
|
|
132
131
|
contentctl/templates/app_default.yml,sha256=kDeYdJbfMADQPcho8iH1nqgTFrHNt4EXnIJjPHc2unI,6390
|
|
133
132
|
contentctl/templates/app_template/README/essoc_story_detail.txt,sha256=7hFPBfPpRH28TFl7QchKceZLewQqgFjRWDlmxZzwpmo,897
|
|
@@ -141,7 +140,6 @@ contentctl/templates/app_template/default/content-version.conf,sha256=TGzX6qLdzR
|
|
|
141
140
|
contentctl/templates/app_template/default/data/ui/nav/default.xml,sha256=fKN53HZCtNJbQqq_5pP8e5-5m30DRrJittr6q5s6V_0,236
|
|
142
141
|
contentctl/templates/app_template/default/data/ui/views/escu_summary.xml,sha256=jQhkIthPgEEptCJ2wUCj2lWGHBvUl6JGsKkDfONloxI,8635
|
|
143
142
|
contentctl/templates/app_template/default/data/ui/views/feedback.xml,sha256=uM71EMK2uFz8h68nOTNKGnYxob3HhE_caSL6yA-3H-k,696
|
|
144
|
-
contentctl/templates/app_template/default/distsearch.conf,sha256=5fa9bNr9WuVI2_8tTIftvrRwk27Oz3rUoKh6_xlASFw,156
|
|
145
143
|
contentctl/templates/app_template/default/usage_searches.conf,sha256=mFnhAHGhFHIzl8xxA626thnAjyxs5ZQQfur1PP_Xmbg,4257
|
|
146
144
|
contentctl/templates/app_template/default/use_case_library.conf,sha256=zWuCOOl8SiP7Kit2s-de4KRu3HySLtBSXcp1QnJx0ec,168
|
|
147
145
|
contentctl/templates/app_template/lookups/mitre_enrichment.csv,sha256=tifPQjFoQHtvpb78hxSP2fKHnHeehNbZDwUjdvc0aEM,66072
|
|
@@ -165,8 +163,8 @@ contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|
|
165
163
|
contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
|
|
166
164
|
contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
|
|
167
165
|
contentctl/templates/stories/cobalt_strike.yml,sha256=rlaXxMN-5k8LnKBLPafBoksyMtlmsPMHPJOjTiMiZ-M,3063
|
|
168
|
-
contentctl-4.
|
|
169
|
-
contentctl-4.
|
|
170
|
-
contentctl-4.
|
|
171
|
-
contentctl-4.
|
|
172
|
-
contentctl-4.
|
|
166
|
+
contentctl-4.1.1.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
|
|
167
|
+
contentctl-4.1.1.dist-info/METADATA,sha256=ZKt2bKUc9AoQMA8TTIQy4Ohx-GiXEWbX0DxQeJcXvu8,19706
|
|
168
|
+
contentctl-4.1.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
169
|
+
contentctl-4.1.1.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
|
|
170
|
+
contentctl-4.1.1.dist-info/RECORD,,
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import splunklib.client as client
|
|
2
|
-
import multiprocessing
|
|
3
|
-
import http.server
|
|
4
|
-
import time
|
|
5
|
-
import sys
|
|
6
|
-
import subprocess
|
|
7
|
-
import os
|
|
8
|
-
class Deploy:
|
|
9
|
-
def __init__(self, args):
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
#First, check to ensure that the legal ack is correct. If not, quit
|
|
14
|
-
if args.acs_legal_ack != "Y":
|
|
15
|
-
raise(Exception(f"Error - must supply 'acs-legal-ack=Y', not 'acs-legal-ack={args.acs_legal_ack}'"))
|
|
16
|
-
|
|
17
|
-
self.acs_legal_ack = args.acs_legal_ack
|
|
18
|
-
self.app_package = args.app_package
|
|
19
|
-
if not os.path.exists(self.app_package):
|
|
20
|
-
raise(Exception(f"Error - app_package file {self.app_package} does not exist"))
|
|
21
|
-
self.username = args.username
|
|
22
|
-
self.password = args.password
|
|
23
|
-
self.server = args.server
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
self.deploy_to_splunk_cloud()
|
|
28
|
-
#self.http_process = self.start_http_server()
|
|
29
|
-
|
|
30
|
-
#self.install_app()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def deploy_to_splunk_cloud(self):
|
|
34
|
-
|
|
35
|
-
commandline = f"acs apps install private --acs-legal-ack={self.acs_legal_ack} "\
|
|
36
|
-
f"--app-package {self.app_package} --server {self.server} --username "\
|
|
37
|
-
f"{self.username} --password {self.password}"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
res = subprocess.run(args = commandline.split(' '), )
|
|
42
|
-
except Exception as e:
|
|
43
|
-
raise(Exception(f"Error deploying to Splunk Cloud Instance: {str(e)}"))
|
|
44
|
-
print(res.returncode)
|
|
45
|
-
if res.returncode != 0:
|
|
46
|
-
raise(Exception("Error deploying to Splunk Cloud Instance. Review output to diagnose error."))
|
|
47
|
-
|
|
48
|
-
'''
|
|
49
|
-
def install_app_local(self) -> bool:
|
|
50
|
-
#Connect to the service
|
|
51
|
-
time.sleep(1)
|
|
52
|
-
#self.http_process.start()
|
|
53
|
-
#time.sleep(2)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
print(f"Connecting to server {self.host}")
|
|
57
|
-
try:
|
|
58
|
-
service = client.connect(host=self.host, port=self.api_port, username=self.username, password=self.password)
|
|
59
|
-
assert isinstance(service, client.Service)
|
|
60
|
-
|
|
61
|
-
except Exception as e:
|
|
62
|
-
raise(Exception(f"Failure connecting the Splunk Search Head: {str(e)}"))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
#Install the app
|
|
66
|
-
try:
|
|
67
|
-
params = {'name': self.server_app_path}
|
|
68
|
-
res = service.post('apps/appinstall', **params)
|
|
69
|
-
#Check the result?
|
|
70
|
-
|
|
71
|
-
print(f"Successfully installed {self.server_app_path}!")
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
except Exception as e:
|
|
76
|
-
raise(Exception(f"Failure installing the app {self.server_app_path}: {str(e)}"))
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
#Query and list all of the installed apps
|
|
80
|
-
try:
|
|
81
|
-
all_apps = service.apps
|
|
82
|
-
except Exception as e:
|
|
83
|
-
print(f"Failed listing all apps: {str(e)}")
|
|
84
|
-
return False
|
|
85
|
-
|
|
86
|
-
print("Installed apps:")
|
|
87
|
-
for count, app in enumerate(all_apps):
|
|
88
|
-
print("\t{count}. {app.name}")
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
print(f"Installing app {self.path}")
|
|
92
|
-
|
|
93
|
-
self.http_process.terminate()
|
|
94
|
-
|
|
95
|
-
return True
|
|
96
|
-
'''
|
|
97
|
-
|
|
98
|
-
|