contentctl 4.0.5__py3-none-any.whl → 4.1.0__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.
@@ -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 CveEnrichment, CveEnrichmentObj
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
- @computed_field
148
- @property
149
- def cve_enrichment(self)->List[CveEnrichmentObj]:
150
- raise Exception("CVE Enrichment Functionality not currently supported. It will be re-added at a later time.")
151
- enriched_cves = []
152
- for cve_id in self.tags.cve:
153
- print(f"\nEnriching {cve_id}\n")
154
- enriched_cves.append(CveEnrichment.enrich_cve(cve_id))
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
- return enriched_cves
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.macros.append(filter_macro)
449
+ director.addContentToDictMappings(filter_macro)
386
450
 
387
451
  macros_from_search = Macro.get_macros(search, director)
388
452
 
389
- return macros_from_search + [filter_macro]
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
 
@@ -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
@@ -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: {str(v)}")
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:
@@ -9,13 +9,14 @@ if TYPE_CHECKING:
9
9
  from contentctl.objects.security_content_object import SecurityContentObject
10
10
 
11
11
 
12
-
13
- MACROS_TO_IGNORE = set(["_filter", "drop_dm_object_name"])
14
- #Should all of the following be included as well?
15
- MACROS_TO_IGNORE.add("get_asset" )
16
- MACROS_TO_IGNORE.add("get_risk_severity")
17
- MACROS_TO_IGNORE.add("cim_corporate_web_domain_search")
18
- MACROS_TO_IGNORE.add("prohibited_processes")
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):
@@ -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.0.5
3
+ Version: 4.1.0
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.2,<4.0.0)
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.5.1,<3.0.0)
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.10.8,<0.11.0)
23
- Requires-Dist: pysigma-backend-splunk (>=1.0.3,<2.0.0)
22
+ Requires-Dist: pysigma (>=0.11.5,<0.12.0)
23
+ Requires-Dist: pysigma-backend-splunk (>=1.1.0,<2.0.0)
24
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,<70.0.0)
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.1,<5.0.0)
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=31v7hISc8B8w5tyMnBPSDb3AHRpm-K9rn-WqJRegzBQ,12628
22
- contentctl/actions/new_content.py,sha256=vhpZAIpsBPjrdsQQlVxRPdymM8xa5ju9XE3sa8S-ni4,6013
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
23
  contentctl/actions/test.py,sha256=JXW1CR-tTM2kJ-U5NRG8quY3JlnOb4OmCBgX24XYWJ0,4896
26
- contentctl/actions/validate.py,sha256=-yZuhFBzqZvtT5FOFO4o4-U72tv6urrAG9QCFwqX4os,2363
27
- contentctl/contentctl.py,sha256=qiowJPiIdMkh8KkbiYhDyVBc1sKJTBKEXhZDwMC-mAk,10083
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=r5a2DVpbz7wBW8iU4-OhXmSmJQ28JnFDQJt8XZ96MVo,3934
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=CNAzSpO2fjjnhyezOGn9u5QiKq3Xqq7rHI-X9LrpyCo,10716
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=YRbDXBFk_To77jyCkUqhswLV4n9IwJGTSDaiAnI7sFU,30167
41
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=cdBb7Yb3vYkD8xRKMWPG8Aq7oAKfw9fRIBGvjYw8zT0,8065
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=x9vXa45kT2Qu7xQ0icPLvVJLFF6Hrj9svqdbuKtHzDc,2248
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=lwiEJu9M3KVP8krH3ieI-4Yke-nI1dRYbStouLmHIWo,43708
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=dYCa4SfoqRiSOwYpbWo93vLGPxy6V9pArCZMWb5fxZs,10238
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=qUnS1UuGrq2nXj49N2qmwzZDJwyfTCqu3KSZMB6CfWk,2451
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=UsVhIJ-QmDB3B3GKiapMZ_ZBCJt_mefBzVmUwD9WfNw,271
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.0.5.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
169
- contentctl-4.0.5.dist-info/METADATA,sha256=64TV2vSygHoDN_WRiXDgUUvIZizbDqPFRNKMdM3RZiU,19751
170
- contentctl-4.0.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
171
- contentctl-4.0.5.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
172
- contentctl-4.0.5.dist-info/RECORD,,
166
+ contentctl-4.1.0.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
167
+ contentctl-4.1.0.dist-info/METADATA,sha256=AXM-iaprX65-e2JRJCjSFS4yr315R3uAbh80e5KJp20,19706
168
+ contentctl-4.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
169
+ contentctl-4.1.0.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
+ contentctl-4.1.0.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
-