contentctl 5.2.0__py3-none-any.whl → 5.3.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.
Files changed (42) hide show
  1. contentctl/actions/build.py +5 -43
  2. contentctl/actions/detection_testing/DetectionTestingManager.py +64 -24
  3. contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +146 -42
  4. contentctl/actions/detection_testing/views/DetectionTestingView.py +5 -6
  5. contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +2 -0
  6. contentctl/actions/initialize.py +35 -9
  7. contentctl/actions/release_notes.py +14 -12
  8. contentctl/actions/test.py +16 -20
  9. contentctl/actions/validate.py +8 -15
  10. contentctl/helper/utils.py +69 -20
  11. contentctl/input/director.py +147 -119
  12. contentctl/input/yml_reader.py +39 -27
  13. contentctl/objects/abstract_security_content_objects/detection_abstract.py +94 -20
  14. contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +548 -8
  15. contentctl/objects/baseline.py +24 -6
  16. contentctl/objects/config.py +32 -8
  17. contentctl/objects/content_versioning_service.py +508 -0
  18. contentctl/objects/correlation_search.py +53 -63
  19. contentctl/objects/dashboard.py +15 -1
  20. contentctl/objects/data_source.py +13 -1
  21. contentctl/objects/deployment.py +23 -9
  22. contentctl/objects/detection.py +2 -0
  23. contentctl/objects/enums.py +28 -18
  24. contentctl/objects/investigation.py +40 -20
  25. contentctl/objects/lookup.py +61 -5
  26. contentctl/objects/macro.py +19 -4
  27. contentctl/objects/playbook.py +16 -2
  28. contentctl/objects/rba.py +1 -33
  29. contentctl/objects/removed_security_content_object.py +50 -0
  30. contentctl/objects/security_content_object.py +1 -0
  31. contentctl/objects/story.py +37 -5
  32. contentctl/output/api_json_output.py +5 -3
  33. contentctl/output/conf_output.py +9 -1
  34. contentctl/output/runtime_csv_writer.py +111 -0
  35. contentctl/output/svg_output.py +4 -5
  36. contentctl/output/templates/savedsearches_detections.j2 +2 -6
  37. {contentctl-5.2.0.dist-info → contentctl-5.3.0.dist-info}/METADATA +4 -3
  38. {contentctl-5.2.0.dist-info → contentctl-5.3.0.dist-info}/RECORD +41 -39
  39. {contentctl-5.2.0.dist-info → contentctl-5.3.0.dist-info}/WHEEL +1 -1
  40. contentctl/output/data_source_writer.py +0 -52
  41. {contentctl-5.2.0.dist-info → contentctl-5.3.0.dist-info}/LICENSE.md +0 -0
  42. {contentctl-5.2.0.dist-info → contentctl-5.3.0.dist-info}/entry_points.txt +0 -0
contentctl/objects/rba.py CHANGED
@@ -4,9 +4,7 @@ from abc import ABC
4
4
  from enum import Enum
5
5
  from typing import Annotated, Set
6
6
 
7
- from pydantic import BaseModel, Field, computed_field, model_serializer
8
-
9
- from contentctl.objects.enums import RiskSeverity
7
+ from pydantic import BaseModel, Field, model_serializer
10
8
 
11
9
  RiskScoreValue_Type = Annotated[int, Field(ge=1, le=100)]
12
10
 
@@ -108,36 +106,6 @@ class RBAObject(BaseModel, ABC):
108
106
  risk_objects: Annotated[Set[RiskObject], Field(min_length=1)]
109
107
  threat_objects: Set[ThreatObject]
110
108
 
111
- @computed_field
112
- @property
113
- def risk_score(self) -> RiskScoreValue_Type:
114
- # First get the maximum score associated with
115
- # a risk object. If there are no objects, then
116
- # we should throw an exception.
117
- if len(self.risk_objects) == 0:
118
- raise Exception(
119
- "There must be at least one Risk Object present to get Severity."
120
- )
121
- return max([risk_object.score for risk_object in self.risk_objects])
122
-
123
- @computed_field
124
- @property
125
- def severity(self) -> RiskSeverity:
126
- if 0 <= self.risk_score <= 20:
127
- return RiskSeverity.INFORMATIONAL
128
- elif 20 < self.risk_score <= 40:
129
- return RiskSeverity.LOW
130
- elif 40 < self.risk_score <= 60:
131
- return RiskSeverity.MEDIUM
132
- elif 60 < self.risk_score <= 80:
133
- return RiskSeverity.HIGH
134
- elif 80 < self.risk_score <= 100:
135
- return RiskSeverity.CRITICAL
136
- else:
137
- raise Exception(
138
- f"Error getting severity - risk_score must be between 0-100, but was actually {self.risk_score}"
139
- )
140
-
141
109
  @model_serializer
142
110
  def serialize_rba(self) -> dict[str, str | list[dict[str, str | int]]]:
143
111
  return {
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ import pathlib
4
+ from functools import cached_property
5
+
6
+ from pydantic import ConfigDict, HttpUrl, computed_field, field_validator
7
+
8
+ from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
9
+ SecurityContentObject_Abstract,
10
+ )
11
+ from contentctl.objects.enums import ContentStatus
12
+
13
+
14
+ class SecurityContentObject(SecurityContentObject_Abstract):
15
+ pass
16
+
17
+
18
+ class RemovedSecurityContentObject(SecurityContentObject):
19
+ # We MUST allow extra fields here because the python definitions of the underlying
20
+ # objects can change. We do not want to throw pasing errors on any of these, but we will
21
+ # only expose fields that are defined in the SecurityContentObject definiton directly
22
+ model_config = ConfigDict(validate_default=True, extra="ignore")
23
+ status: ContentStatus
24
+
25
+ @field_validator("status", mode="after")
26
+ @classmethod
27
+ def NarrowStatus(cls, status: ContentStatus) -> ContentStatus:
28
+ return cls.NarrowStatusTemplate(status, [ContentStatus.removed])
29
+
30
+ @computed_field
31
+ @cached_property
32
+ def migration_guide(self) -> HttpUrl:
33
+ """
34
+ A link to the research site containing a migration guide for the content
35
+
36
+ :returns: URL to the research site
37
+ :rtype: HTTPUrl
38
+ """
39
+ # this is split up so that we can explicilty ignore the warning on constructing
40
+ # the HttpUrl but catch other type issues
41
+ # link = f"https://research.splunk.com/migration_guide/{self.id}"
42
+ # This can likely be dynamically generated per detection, but for now we
43
+ # just make it static
44
+ link = "https://research.splunk.com/migration_guide/"
45
+
46
+ return HttpUrl(url=link) # type: ignore
47
+
48
+ @classmethod
49
+ def containing_folder(cls) -> pathlib.Path:
50
+ return pathlib.Path("removed")
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
3
4
  SecurityContentObject_Abstract,
4
5
  )
@@ -1,9 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
- from typing import TYPE_CHECKING, List, Literal
5
-
6
- from pydantic import Field, computed_field, model_serializer, model_validator
4
+ from functools import cached_property
5
+ from typing import TYPE_CHECKING, List
6
+
7
+ from pydantic import (
8
+ Field,
9
+ HttpUrl,
10
+ computed_field,
11
+ field_validator,
12
+ model_serializer,
13
+ model_validator,
14
+ )
7
15
 
8
16
  from contentctl.objects.story_tags import StoryTags
9
17
 
@@ -14,20 +22,40 @@ if TYPE_CHECKING:
14
22
  from contentctl.objects.detection import Detection
15
23
  from contentctl.objects.investigation import Investigation
16
24
 
17
- from contentctl.objects.enums import DetectionStatus
25
+ import pathlib
26
+
27
+ from contentctl.objects.enums import ContentStatus
18
28
  from contentctl.objects.security_content_object import SecurityContentObject
19
29
 
20
30
 
21
31
  class Story(SecurityContentObject):
22
32
  narrative: str = Field(...)
23
33
  tags: StoryTags = Field(...)
24
- status: Literal[DetectionStatus.production, DetectionStatus.deprecated]
34
+ status: ContentStatus
25
35
  # These are updated when detection and investigation objects are created.
26
36
  # Specifically in the model_post_init functions
27
37
  detections: List[Detection] = []
28
38
  investigations: List[Investigation] = []
29
39
  baselines: List[Baseline] = []
30
40
 
41
+ @field_validator("status", mode="after")
42
+ @classmethod
43
+ def NarrowStatus(cls, status: ContentStatus) -> ContentStatus:
44
+ return cls.NarrowStatusTemplate(
45
+ status, [ContentStatus.production, ContentStatus.deprecated]
46
+ )
47
+
48
+ @classmethod
49
+ def containing_folder(cls) -> pathlib.Path:
50
+ return pathlib.Path("stories")
51
+
52
+ @computed_field
53
+ @cached_property
54
+ def researchSiteLink(self) -> HttpUrl:
55
+ return HttpUrl(
56
+ url=f"https://research.splunk.com/stories/{self.name.lower().replace(' ', '_')}"
57
+ ) # type:ignore
58
+
31
59
  @computed_field
32
60
  @property
33
61
  def data_sources(self) -> list[DataSource]:
@@ -145,3 +173,7 @@ class Story(SecurityContentObject):
145
173
  @property
146
174
  def baseline_names(self) -> List[str]:
147
175
  return [baseline.name for baseline in self.baselines]
176
+
177
+ @classmethod
178
+ def static_get_conf_stanza_name(cls, name: str, app: CustomApp) -> str:
179
+ return name
@@ -1,14 +1,15 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  if TYPE_CHECKING:
6
+ from contentctl.objects.baseline import Baseline
7
+ from contentctl.objects.deployment import Deployment
5
8
  from contentctl.objects.detection import Detection
9
+ from contentctl.objects.investigation import Investigation
6
10
  from contentctl.objects.lookup import Lookup
7
11
  from contentctl.objects.macro import Macro
8
12
  from contentctl.objects.story import Story
9
- from contentctl.objects.baseline import Baseline
10
- from contentctl.objects.investigation import Investigation
11
- from contentctl.objects.deployment import Deployment
12
13
 
13
14
  import os
14
15
  import pathlib
@@ -42,6 +43,7 @@ class ApiJsonOutput:
42
43
  "search",
43
44
  "how_to_implement",
44
45
  "known_false_positives",
46
+ "rba",
45
47
  "references",
46
48
  "datamodel",
47
49
  "macros",
@@ -93,6 +93,10 @@ class ConfOutput:
93
93
 
94
94
  return written_files
95
95
 
96
+ # TODO (#339): we could have a discrepancy between detections tested and those delivered
97
+ # based on the jinja2 template
98
+ # {% if (detection.type == 'TTP' or detection.type == 'Anomaly' or
99
+ # detection.type == 'Hunting' or detection.type == 'Correlation') %}
96
100
  def writeDetections(self, objects: list[Detection]) -> set[pathlib.Path]:
97
101
  written_files: set[pathlib.Path] = set()
98
102
  for output_app_path, template_name in [
@@ -211,7 +215,11 @@ class ConfOutput:
211
215
  # even though the MLModel info was intentionally not written to the
212
216
  # transforms.conf file as noted above.
213
217
  if isinstance(lookup, FileBackedLookup):
214
- shutil.copy(lookup.filename, lookup_folder / lookup.app_filename.name)
218
+ with (
219
+ open(lookup_folder / lookup.app_filename.name, "w") as output_file,
220
+ lookup.content_file_handle as output,
221
+ ):
222
+ output_file.write(output.read())
215
223
  return written_files
216
224
 
217
225
  def writeMacros(self, objects: list[Macro]) -> set[pathlib.Path]:
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ import csv
4
+ from io import StringIO
5
+ from typing import TYPE_CHECKING, List
6
+
7
+ if TYPE_CHECKING:
8
+ from contentctl.input.director import DirectorOutputDto
9
+
10
+ from contentctl.objects.config import CustomApp
11
+ from contentctl.objects.data_source import DataSource
12
+
13
+
14
+ class RuntimeCsvWriter:
15
+ @staticmethod
16
+ def generateDeprecationCSVContent(
17
+ director: DirectorOutputDto, app: CustomApp
18
+ ) -> str:
19
+ with StringIO() as output_buffer:
20
+ fieldNames = [
21
+ "Name",
22
+ "Content Type",
23
+ "Removed in Version",
24
+ "Reason",
25
+ "Replacement Content",
26
+ "Replacement Content Link",
27
+ ]
28
+
29
+ writer = csv.DictWriter(output_buffer, fieldnames=fieldNames)
30
+ writer.writeheader()
31
+
32
+ for content in director.name_to_content_map.values():
33
+ if content.deprecation_info is not None:
34
+ try:
35
+ writer.writerow(
36
+ {
37
+ "Name": content.deprecation_info.contentType.static_get_conf_stanza_name(
38
+ content.name, app
39
+ ),
40
+ "Content Type": content.deprecation_info.contentType.__name__,
41
+ "Removed in Version": content.deprecation_info.removed_in_version,
42
+ "Reason": content.deprecation_info.reason,
43
+ "Replacement Content": "\n".join(
44
+ [
45
+ c.name
46
+ for c in content.deprecation_info.replacement_content
47
+ ]
48
+ )
49
+ or "No Replacement Content Available",
50
+ "Replacement Content Link": "\n".join(
51
+ [
52
+ str(c.researchSiteLink)
53
+ for c in content.deprecation_info.replacement_content
54
+ ]
55
+ )
56
+ or "No Content Link Available",
57
+ }
58
+ )
59
+ except Exception as e:
60
+ print(e)
61
+ import code
62
+
63
+ code.interact(local=locals())
64
+ return output_buffer.getvalue()
65
+
66
+ @staticmethod
67
+ def generateDatasourceCSVContent(
68
+ data_source_objects: List[DataSource],
69
+ ) -> str:
70
+ with StringIO() as output_buffer:
71
+ writer = csv.writer(output_buffer)
72
+ # Write the header
73
+ writer.writerow(
74
+ [
75
+ "name",
76
+ "id",
77
+ "author",
78
+ "source",
79
+ "sourcetype",
80
+ "separator",
81
+ "supported_TA_name",
82
+ "supported_TA_version",
83
+ "supported_TA_url",
84
+ "description",
85
+ ]
86
+ )
87
+ # Write the data
88
+ for data_source in data_source_objects:
89
+ if len(data_source.supported_TA) > 0:
90
+ supported_TA_name = data_source.supported_TA[0].name
91
+ supported_TA_version = data_source.supported_TA[0].version
92
+ supported_TA_url = data_source.supported_TA[0].url or ""
93
+ else:
94
+ supported_TA_name = ""
95
+ supported_TA_version = ""
96
+ supported_TA_url = ""
97
+ writer.writerow(
98
+ [
99
+ data_source.name,
100
+ data_source.id,
101
+ data_source.author,
102
+ data_source.source,
103
+ data_source.sourcetype,
104
+ data_source.separator,
105
+ supported_TA_name,
106
+ supported_TA_version,
107
+ supported_TA_url,
108
+ data_source.description,
109
+ ]
110
+ )
111
+ return output_buffer.getvalue()
@@ -1,10 +1,9 @@
1
1
  import pathlib
2
- from typing import List, Any
2
+ from typing import Any, List
3
3
 
4
- from contentctl.objects.enums import SecurityContentType
5
- from contentctl.output.jinja_writer import JinjaWriter
6
- from contentctl.objects.enums import DetectionStatus
7
4
  from contentctl.objects.detection import Detection
5
+ from contentctl.objects.enums import ContentStatus, SecurityContentType
6
+ from contentctl.output.jinja_writer import JinjaWriter
8
7
 
9
8
 
10
9
  class SvgOutput:
@@ -49,7 +48,7 @@ class SvgOutput:
49
48
  [
50
49
  detection
51
50
  for detection in detections
52
- if detection.status == DetectionStatus.production
51
+ if detection.status == ContentStatus.production
53
52
  ],
54
53
  )
55
54
  # deprecated_dict = self.get_badge_dict("Deprecated", detections, [detection for detection in detections if detection.status == DetectionStatus.deprecated])
@@ -65,14 +65,10 @@ action.notable.param.nes_fields = {{ detection.nes_fields }}
65
65
  action.notable.param.rule_description = {{ detection.deployment.alert_action.notable.rule_description | custom_jinja2_enrichment_filter(detection) | escapeNewlines()}}
66
66
  action.notable.param.rule_title = {% if detection.type | lower == "correlation" %}RBA: {{ detection.deployment.alert_action.notable.rule_title | custom_jinja2_enrichment_filter(detection) }}{% else %}{{ detection.deployment.alert_action.notable.rule_title | custom_jinja2_enrichment_filter(detection) }}{% endif +%}
67
67
  action.notable.param.security_domain = {{ detection.tags.security_domain }}
68
- {% if detection.rba %}
69
- action.notable.param.severity = {{ detection.rba.severity }}
70
- {% else %}
71
- {# Correlations do not have detection.rba defined, but should get a default severity #}
72
- action.notable.param.severity = high
73
- {% endif %}
68
+ action.notable.param.severity = {{ detection.severity }}
74
69
  {% endif %}
75
70
  {% if detection.deployment.alert_action.email %}
71
+ action.email = 1
76
72
  action.email.subject.alert = {{ detection.deployment.alert_action.email.subject | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }}
77
73
  action.email.to = {{ detection.deployment.alert_action.email.to }}
78
74
  action.email.message.alert = {{ detection.deployment.alert_action.email.message | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: contentctl
3
- Version: 5.2.0
3
+ Version: 5.3.0
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Requires-Dist: Jinja2 (>=3.1.4,<4.0.0)
15
15
  Requires-Dist: PyYAML (>=6.0.2,<7.0.0)
16
- Requires-Dist: attackcti (>=0.4.0,<0.5.0)
16
+ Requires-Dist: attackcti (>=0.5.4,<0.6)
17
17
  Requires-Dist: bottle (>=0.12.25,<0.14.0)
18
18
  Requires-Dist: docker (>=7.1.0,<8.0.0)
19
19
  Requires-Dist: gitpython (>=3.1.43,<4.0.0)
@@ -22,8 +22,9 @@ Requires-Dist: pydantic (>=2.9.2,<2.10.0)
22
22
  Requires-Dist: pygit2 (>=1.15.1,<2.0.0)
23
23
  Requires-Dist: questionary (>=2.0.1,<3.0.0)
24
24
  Requires-Dist: requests (>=2.32.3,<2.33.0)
25
+ Requires-Dist: rich (>=14.0.0,<15.0.0)
25
26
  Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
26
- Requires-Dist: setuptools (>=69.5.1,<76.0.0)
27
+ Requires-Dist: setuptools (>=69.5.1,<79.0.0)
27
28
  Requires-Dist: splunk-sdk (>=2.0.2,<3.0.0)
28
29
  Requires-Dist: tqdm (>=4.66.5,<5.0.0)
29
30
  Requires-Dist: tyro (>=0.9.2,<0.10.0)
@@ -1,25 +1,25 @@
1
1
  contentctl/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
- contentctl/actions/build.py,sha256=M68XubxTqI-LiW8P4eaNmb62pWNQtVE8cF3xKb6g44c,5669
2
+ contentctl/actions/build.py,sha256=agpHT6QCCFdWTs4jU7v8xqvGy3DG_zgsZZT96j1gcW0,3965
3
3
  contentctl/actions/deploy_acs.py,sha256=w3OqO8GXzB_5zHrE8lDYbadAy4Etw7F2o84Gze74RY0,3264
4
- contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=TWZpmDjMqWRWyzsLyiYol_jAovAr6ok9J_GzE9-kNN0,9079
4
+ contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=94apBwLkXWsgdLSvE9f_KqCfQSdmDChMncMcsEdY1A8,10974
5
5
  contentctl/actions/detection_testing/GitService.py,sha256=a6y7lqCgSL1KdSVEgJDxawea8ZgEkGNfOKEf9v_BgLo,11135
6
6
  contentctl/actions/detection_testing/generate_detection_coverage_badge.py,sha256=bGUVKjKv96lTw1GZ4Kw1JX-Yicu4aOJWm-IL524e9HI,2302
7
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=dus-8ANS0VgKq1HOdis4wVF5DKtdot6csbcpayxqXDo,57282
7
+ contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=nJVo_L3Y4V0Uk7VCGHY58waGCBKcfujIFmxKC83oVgY,61082
8
8
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py,sha256=qYWgRW7uc-15jzwv5xSUF2xyLDmtyGyMfuXkQK9j-aM,7221
9
9
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py,sha256=Q1ZfCYOp54O39bgTScZMInkmZiU-bGAM9Hiwr2mq5ms,370
10
10
  contentctl/actions/detection_testing/progress_bar.py,sha256=UrpNCqxTmQ4hfoRZgxPJ1xvDVwMrTq0UnotdryHN0gM,3232
11
- contentctl/actions/detection_testing/views/DetectionTestingView.py,sha256=E07v2VK-pyLMeTA6EtNI_04kt-T90SwSM4kN9yfp-2I,7475
12
- contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py,sha256=-4yhxGJsafcRRAIebFZebUG_qSkASDLHajN9piAPlvM,2104
11
+ contentctl/actions/detection_testing/views/DetectionTestingView.py,sha256=gSrLNR8Nz-YYsXunWLRn53UH7TeQugDKjnDLXy4cL6M,7466
12
+ contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py,sha256=kvMP4G2tLUZPM6AFHO6KrlkjWBNaHmEoke5qxS7XBws,2238
13
13
  contentctl/actions/detection_testing/views/DetectionTestingViewFile.py,sha256=G-6YqBtj0R1A9eiPrgHP2yvUMm7H8wopTBYjxIEIl8g,1090
14
14
  contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py,sha256=CXV1fByf3J-Jc4D9U6jgWSaUhNzjcMpvEgRMuusF2vU,4740
15
15
  contentctl/actions/doc_gen.py,sha256=P2-RYsJoW-QuhAkSpOQespDLJBC-4Cq3-XGTmadK8Ys,936
16
- contentctl/actions/initialize.py,sha256=KaWSbrTaJA4vNSpKc_rwdlaaERnWw_hPlWwsPOA6Gy8,3191
16
+ contentctl/actions/initialize.py,sha256=KXVUyjLMS7yE34wd2odyj5pVXyc_eOlvH_d7LzgR_Bc,4238
17
17
  contentctl/actions/inspect.py,sha256=zFNbDXY7Bi1xTBHirNyHpH1-2A1n3rsOsRvu8E0xUao,19375
18
18
  contentctl/actions/new_content.py,sha256=xs0QvHzlrf0g-EgdUJTkdDdFaA-uEGmzMTixDt6NcTY,8212
19
- contentctl/actions/release_notes.py,sha256=_Rdljg0tPSAFlw34LJ7dUsHLiH8tJTQ6B95X6MvxURo,17023
19
+ contentctl/actions/release_notes.py,sha256=rrloomsLBfl53xpjqDez6RgHU5AE4Gb9ASrivGbYYVs,17122
20
20
  contentctl/actions/reporting.py,sha256=GF32i7sHdc47bw-VWSW-nZ1QBaUl6Ni1JjV5_SOyiAU,1660
21
- contentctl/actions/test.py,sha256=GTtvHi1yB5yDm1jPMyuc4WxczOq-O7f2N8LpTmMaWgU,6014
22
- contentctl/actions/validate.py,sha256=thnxanLj6mfw5g17C1FrzWlkpyIT_AjnDxv_wyNdspA,5837
21
+ contentctl/actions/test.py,sha256=ftZazqoqv7bLNhyW23aRnDpetG9zltS8wr4Xq9Hls0k,6268
22
+ contentctl/actions/validate.py,sha256=2AzxEhqtvq4QlkcMbzw1fPCCZKnnuO3XfBE5WEEnFrs,5879
23
23
  contentctl/api.py,sha256=6s17vNOW1E1EzQqOCXAa5uWuhwwShu-JkGSgrsOFEMs,6329
24
24
  contentctl/contentctl.py,sha256=nR8nHxXY0elvQogVHFqsyid7Ch5sMnIiNAOFbCa0yzI,11755
25
25
  contentctl/enrichments/attack_enrichment.py,sha256=68C9xQ8Q3YX-luRdK2hLnwWtRFpheFA2kE4v5GOLGEo,6358
@@ -28,58 +28,60 @@ contentctl/enrichments/splunk_app_enrichment.py,sha256=Xynxjjkqlw0_RtQ1thGSFwy1I
28
28
  contentctl/helper/link_validator.py,sha256=kzEi2GdncPWSi-UKNerXm2jtTJfFQ5goS9pqyAz5U5c,7427
29
29
  contentctl/helper/logger.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  contentctl/helper/splunk_app.py,sha256=Zq_C9rjNVqCjBNgm-5CWdBpXyeX5jSpbE-QTGptEZlk,14571
31
- contentctl/helper/utils.py,sha256=rigwZzCwWzn11sKTVWDkYEtLmRSf0yBbJ671OSRQnOM,19094
32
- contentctl/input/director.py,sha256=7mx93-k_8DUc4pwYjxwq39n8-BlDYZOvRBkdzdGA8Qc,10919
31
+ contentctl/helper/utils.py,sha256=1_6cbvvbPXWxym3ZhRhL18ttmXLXiHbavpXAkROtGcg,21154
32
+ contentctl/input/director.py,sha256=rThzfssOG4v52ClhVwUx-sU0MKWb_UaMy3MSANCrwmo,11999
33
33
  contentctl/input/new_content_questions.py,sha256=z2C4Mg7-EyxtiF2z9m4SnSbi6QO4CUPB3wg__JeMXIQ,4067
34
- contentctl/input/yml_reader.py,sha256=ymmAqsWsf9Oj56waDOhCh_E4SomkSCmu4dAx7iURFt8,2050
35
- contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=VMt_0eA8eFa64DD2j96kIqfhREVNMPBeJAclsv0eFUw,43978
36
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=P1v5n8hM4XzJOjNZbJ5m3y4Bu-cQYaddLPdbE6K-4Xw,12164
34
+ contentctl/input/yml_reader.py,sha256=L27b14_xXQYypUV1eAzZrfMtwtkzAZx--6nRo3RNZnE,2729
35
+ contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=dNkCXqlGVJw06tMWFS4r5a1LV10jxwKUslNG7iVL__s,46765
36
+ contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=VzAkq-oJ05Y7Y2BzoUG8De8tVANV5X9ZvHbrDe65iRA,33282
37
37
  contentctl/objects/alert_action.py,sha256=iEvdEOT4TrTXT0z4rQ_W5v79hPJpPhFPSzo7TuHDxwA,1376
38
38
  contentctl/objects/annotated_types.py,sha256=xR4EKvdOpNDEt0doGs8XjxCzKK99J2NHZgHFAmt7p2c,424
39
39
  contentctl/objects/atomic.py,sha256=5nl-JhZnymadi8B8ZEJ8l80DnpvjG-OlRxUjVKR6ffY,7341
40
40
  contentctl/objects/base_test.py,sha256=JG6qlr7xe9P71n3CzKOro8_bsmDQGYDfTG9YooHQSIE,1105
41
41
  contentctl/objects/base_test_result.py,sha256=TYYzTPKWqp9rHTebWoid50uxAp_iALZouril4sFwIcA,5197
42
- contentctl/objects/baseline.py,sha256=grzM56KCpROjMnJQIan-fG0LCYfRGA2GHui4FwBwb8A,3172
42
+ contentctl/objects/baseline.py,sha256=EMcuz_9sVgOFh3YCj871GSAA6v3FIkRTf90-LAHq-J0,3700
43
43
  contentctl/objects/baseline_tags.py,sha256=Eomy8y3HV-E6Lym5B5ZZTtsmQJYi6Jd4y8GZpTWGYgQ,1643
44
- contentctl/objects/config.py,sha256=3l8tFVwrBDpAnS7aBgj6to0Kc8_s4bxuZY5Bm5vel8k,48605
44
+ contentctl/objects/config.py,sha256=8F_zpcnFyE_rSdGomm2qeVDG6tTpjhrVyK6BsNHM8js,49357
45
45
  contentctl/objects/constants.py,sha256=VwwQtJBGC_zb3ukjb3A7P0CwAlyhacWiXczwAW5Jiog,5466
46
- contentctl/objects/correlation_search.py,sha256=ab6v-0nbzujhTMpwaXynQiInWpRO1zB5KR4eZLCav_M,45234
47
- contentctl/objects/dashboard.py,sha256=9DiHP_Nx2XBQv4-zUaz6v9XH5yeTJhxaGDlaQqCsbIU,4468
48
- contentctl/objects/data_source.py,sha256=AtB6lAe43mx0GicphWTkmYupp8Fb1hmWgm_GpbX73wo,1567
49
- contentctl/objects/deployment.py,sha256=FRsgsX2T1gvA_0A44_sFPr22rsedxXVIhtO7o9F7eZM,2902
46
+ contentctl/objects/content_versioning_service.py,sha256=BDk_TV1PTVoXpPcUxqTLa5_bjkfOs9PFYgqTuzOS9UI,20566
47
+ contentctl/objects/correlation_search.py,sha256=Zui6GAtYSUnMUzJkKOQ5sSYDTYoAxODUICpHXwxNMwo,45147
48
+ contentctl/objects/dashboard.py,sha256=owp-bYVagmSHUpVyOHgGtAEaKFSHAXN7kDhYqs09H_g,4998
49
+ contentctl/objects/data_source.py,sha256=O58GArXVlflz3dCtVOn96Ubyi5_ekSC1N9LuveQNws4,2019
50
+ contentctl/objects/deployment.py,sha256=OctNayxFPRvrQtTklAKgfjCXFKOspD19swLj0hi6dWE,3323
50
51
  contentctl/objects/deployment_email.py,sha256=_Sdr_BNjvXECiFonRHLkiOrIQp3slnUaERbptqRbD0Q,206
51
52
  contentctl/objects/deployment_notable.py,sha256=j5AniTRDcw32El5H91qKOXDVZvUYxnIuM4Zzlhrm9cM,258
52
53
  contentctl/objects/deployment_phantom.py,sha256=Qs9UH3pYe2M3evLLgn5FblTe28QH1QojVaBGM_Ydvjw,261
53
54
  contentctl/objects/deployment_rba.py,sha256=n_v79NhcLYHyABceKsI_iEziWhp3uNrqxIUrC8tdjD4,184
54
55
  contentctl/objects/deployment_scheduling.py,sha256=PbyAeIEV6ShHuwfzF4LtGrv6tNt1cwNdl-VDQLj0rE8,257
55
56
  contentctl/objects/deployment_slack.py,sha256=pC8-BB4qOD5fUqUi7Oj2Tre7-kKVqW2xEvCF7tZENQ4,194
56
- contentctl/objects/detection.py,sha256=GKjjhnwyFxm7139dOlPJ4c3vIW0675-NLCPWxEB5m-c,631
57
+ contentctl/objects/detection.py,sha256=i-BCxTrP11fmxHLI1zv3XESF4R-f9BScl6FFOdUlHxY,641
57
58
  contentctl/objects/detection_metadata.py,sha256=JMz8rtcn5HfeEoaAx34kw2wXa35qsRIap_mXoY0Vbss,2237
58
59
  contentctl/objects/detection_stanza.py,sha256=-BRQNib5NNhY7Z2fILS5xkpjNkGSLF7qBciTmgOgLV8,3112
59
60
  contentctl/objects/detection_tags.py,sha256=j92t4TWlNNVdFi4_DoHvEyvJuURlBp5_o1xv2w2pAVk,10699
60
61
  contentctl/objects/drilldown.py,sha256=Vinw6UYlOl0YzoRA_0oBCfHA5Gvgu5p-rEsfBIgMCdI,4186
61
- contentctl/objects/enums.py,sha256=HdaSQgEQ_T38BIlVYk1xdqMm05YyhQb0720nzBorXQw,13554
62
+ contentctl/objects/enums.py,sha256=nWufu5YgzllBfDQBneIe_Hf_erNXouERciqU_di5DNo,13754
62
63
  contentctl/objects/errors.py,sha256=xX_FDUaJbJiOWgjgrzjtYW5QsD41UZ2KWqH-yGkHaCU,5554
63
64
  contentctl/objects/integration_test.py,sha256=TYjKyH4YinUnYXOse5BQGCa4-ez_5mtoMwvh1JJcb0o,1254
64
65
  contentctl/objects/integration_test_result.py,sha256=_uUSgqgjFhEZM8UwOJI6Q9K-ekIrbKU6OPdqHZycl-s,279
65
- contentctl/objects/investigation.py,sha256=kmvQ0grCd1YVEW5wVF4-_Rx87PnOxPiNoN_ufTLc3ZM,3659
66
+ contentctl/objects/investigation.py,sha256=GZsvhSZO7ZSmhg2ZeT-kPMqDG-GYpTXIvGBgV1H2lwQ,4030
66
67
  contentctl/objects/investigation_tags.py,sha256=qDGNusrWDvCX_GcBEzag2MydSV0LIhGxoXZGgxDXfHA,1317
67
- contentctl/objects/lookup.py,sha256=uP8N2Munu9vDGOzkC3mBlM93xkICyVBi62fAXAloI_I,13656
68
- contentctl/objects/macro.py,sha256=usbxyOPIRIJoDmvawfP2DxtFNf71GaDwffxiZsRkP5A,3594
68
+ contentctl/objects/lookup.py,sha256=XDluqS9CumSP7ijbV9O2bbSaDGh0Beo4QaL-II_xC6U,15601
69
+ contentctl/objects/macro.py,sha256=cMQ_frqJnX2NSy-21cbUnMRZZxBionQ2KOxuC9J9bp0,4051
69
70
  contentctl/objects/manual_test.py,sha256=cx_XAtQ8VG8Ui_F553Xnut75vFEOtRwm1dDIIWNpOaM,952
70
71
  contentctl/objects/manual_test_result.py,sha256=FyCVVf-f1DKs-qBkM4tbKfY6mkrW25NcIEBqyaDC2rE,156
71
72
  contentctl/objects/mitre_attack_enrichment.py,sha256=PCakRksW5qrTENIZ7JirEZplE9xpmvSvX2GKv7N8j_k,3683
72
73
  contentctl/objects/notable_action.py,sha256=sW5XlpGznMHqyBmGXtXrl22hWLiCoKkfGCasGtK3rGo,1607
73
74
  contentctl/objects/notable_event.py,sha256=2aOtmfnsdInTtN_fHAGIKmBTBritjHbS_Nc-pqL-GbY,689
74
- contentctl/objects/playbook.py,sha256=mgYbWsD3OW86u11MbIFKvmyFueSoMJ1WBJm_rNrFvAo,2425
75
+ contentctl/objects/playbook.py,sha256=veG2luPfFrOMdzl99D8gsO85HYSJ8kZMYWj3GG64HKk,2879
75
76
  contentctl/objects/playbook_tags.py,sha256=O5obkQyb82YdJEii8ZJEQtrHtLOSnAvAkT1qIgpCK2s,1547
76
- contentctl/objects/rba.py,sha256=VudoJAqADAEflDqVDWlOIk8epRfg_rqvfCsyavjvcaU,4737
77
+ contentctl/objects/rba.py,sha256=2xE_DXhQvG6tVLJTXYaFEBm9owePE4QG0NVgdcVgoiY,3547
78
+ contentctl/objects/removed_security_content_object.py,sha256=bx-gVCqzT81E5jKncMD3-yKawTnl3tWsuzRBmsAqeqQ,1852
77
79
  contentctl/objects/risk_analysis_action.py,sha256=v-TQktXEEzbGzmTtqwEykXoSKdGnIlK_JojnqvvAE1s,4370
78
80
  contentctl/objects/risk_event.py,sha256=JQUmXriiwi5FetqVnhM0hf5cUp6LzLSNPuoecC2JKK0,12593
79
81
  contentctl/objects/risk_object.py,sha256=5iUKW_UwQLjjLWiD_vlE78uwH9bkaMNCHRNmKM25W1Q,905
80
82
  contentctl/objects/savedsearches_conf.py,sha256=Dn_Pxd9i3RT6DwNh6JrgmfxjsO3q15xzMksYr3wIGwQ,8624
81
- contentctl/objects/security_content_object.py,sha256=iDnhq81P7m6Qkmc_Yi-wOyFm9gZUYnPy1GJxxyCtonA,245
82
- contentctl/objects/story.py,sha256=jalNgK4yYGRr_yVkzuO_YHIrPhpWHF0Q79PkTJhcLzY,5048
83
+ contentctl/objects/security_content_object.py,sha256=2mEf-wt3hMsLEyo4yatyK66jKjgUOVjJHIN9fgQB5nA,246
84
+ contentctl/objects/story.py,sha256=1JCiF9D1EZeVcoMXXDoWkOqHXQn4TsQgl8EtUN59a2E,5796
83
85
  contentctl/objects/story_tags.py,sha256=IYumFuBF2Bt7HtW4lBfCRo2EUpjMYlnNjpx24jBErs4,2365
84
86
  contentctl/objects/test_attack_data.py,sha256=7p-kOJguTZtG9y5th5U3qfPFvpiAWLST_OBw8dwWl_4,488
85
87
  contentctl/objects/test_group.py,sha256=r-dXyddok4yslv8SIjwOpqylbN1rdjsRi-HIijvpWD0,2602
@@ -88,16 +90,16 @@ contentctl/objects/throttling.py,sha256=oupWmdtvwAXzLmD3MBJyAU18SD2L2ciEZWUcnL8M
88
90
  contentctl/objects/unit_test.py,sha256=-rtSmZ8N2UZ4NkDsfzNXzXiF6dTDwt_jsQ_14xp0hjs,1005
89
91
  contentctl/objects/unit_test_baseline.py,sha256=ezg8Ctih_3che2ln2tuVCAtRPHaf5tDMR3dGb34MqaA,287
90
92
  contentctl/objects/unit_test_result.py,sha256=gqHqYN5XGBKdV-mdKhAdwfOw4_PpN3i9z_b6ciByDSc,2928
91
- contentctl/output/api_json_output.py,sha256=QWe_KWlHHxE4Mhd3BHRfJbUJ4z2mLHZn_eMWfMVInik,8237
93
+ contentctl/output/api_json_output.py,sha256=s07bD79qFyFQIzfsTpIoFVyGYWlC77J9DNAlVwZwaOs,8269
92
94
  contentctl/output/attack_nav_output.py,sha256=cbQNZkcNCKaQm7Ht70_tcmTvixtsuVDjQB4BpZ8s-Ts,2489
93
95
  contentctl/output/attack_nav_writer.py,sha256=AiQU3q8hzz_lJECI-sjyqOsWx64HUugg3aAHEeZl-qM,2750
94
- contentctl/output/conf_output.py,sha256=2_ofRqMro4xmFzf6ZmPRDd93pCG-LQhOiB_kE4owADc,10609
96
+ contentctl/output/conf_output.py,sha256=OJ4u38FEkuBHvIiK_57HoxUJasWV2rkX2abVamKh3Qg,11045
95
97
  contentctl/output/conf_writer.py,sha256=9eqt2tm1xjs397pwWLz5oPJcMHbs62ejRG7KghGQQCI,15137
96
- contentctl/output/data_source_writer.py,sha256=hjr0b5zfJ2UHcDLbCkmTrqma1ngu8F5vWFPJEwOZwU8,1860
97
98
  contentctl/output/doc_md_output.py,sha256=wlgbzBD2hUbQNIW2zv5sdrq2UdAKhOZJUYSObnpWQfY,3552
98
99
  contentctl/output/jinja_writer.py,sha256=5PbFrc8KuLWrlNIHDvMTyvJ18u_mtjd5Led6-9sn2Eo,1204
99
100
  contentctl/output/json_writer.py,sha256=waw73wOmalSrUFcr2K1CWR-xz5oW8il10zDAn56mtMg,1041
100
- contentctl/output/svg_output.py,sha256=5s9fjmKullMV6cCCGwP7_xvQwg9EZLOKRKMw_IyO6hY,2988
101
+ contentctl/output/runtime_csv_writer.py,sha256=ZlK3kDg6YwfR4Sj4xgJIhTHZ_GDVfDnQDHS0PqAnpt0,4336
102
+ contentctl/output/svg_output.py,sha256=8KClwfUIoLXnns-jYFzvv5OMs0-uwTFI0oZw9DWjTg4,2948
101
103
  contentctl/output/templates/analyticstories_detections.j2,sha256=6ZiQO8np6KkX8skVoIB0BN9_s8SBW3qeo8IBA8r8GQk,923
102
104
  contentctl/output/templates/analyticstories_investigations.j2,sha256=kqy9lR6W3avqETCM2tSZ8WWOlfiyOtFv6G5N4SZWSaQ,527
103
105
  contentctl/output/templates/analyticstories_stories.j2,sha256=MxkmwsgW1oge2YJhbgAzXVcTplSr5JjKIDxX4SBZV0E,676
@@ -121,7 +123,7 @@ contentctl/output/templates/header.j2,sha256=3usV7jm1q6J-QNnQrZzII9cN0XEGQjg_eVK
121
123
  contentctl/output/templates/macros.j2,sha256=SLcQQ5X7TZS8j-2qP06BTXqdIcnwoYqTAaBLX2Dge7Y,390
122
124
  contentctl/output/templates/panel.j2,sha256=Cw_W6p-14n6UivVfpS75KKJiJ2VpdGsSBceYsUYe9gk,221
123
125
  contentctl/output/templates/savedsearches_baselines.j2,sha256=WHZB4e0vmeym8832VxRmuUfDJ-YRYt6emcYaJrghI58,1709
124
- contentctl/output/templates/savedsearches_detections.j2,sha256=B7_b8aBjEKAV7mJm0DxUS_HyCt63bQZtpacCQfDgDqc,6033
126
+ contentctl/output/templates/savedsearches_detections.j2,sha256=mbOSSDoHwTfzpxQRVz03FvFNQJ26lWqcUtl6uW6tUZY,5874
125
127
  contentctl/output/templates/savedsearches_investigations.j2,sha256=KH2r8SgyAMiettSHypSbA2-1XmQ_8_8xzk3BkbZ1Re4,1196
126
128
  contentctl/output/templates/server.conf.j2,sha256=sPZUkiuJNGm9R8rpjfRKyuAvmmQb0C4w9Q6hpmvmPeU,127
127
129
  contentctl/output/templates/transforms.j2,sha256=TEKZi8DWpcCysRTNvuLEgAwx-g1SZ2E0CkLiu6v6AlU,1339
@@ -161,8 +163,8 @@ contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
161
163
  contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
162
164
  contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
163
165
  contentctl/templates/stories/cobalt_strike.yml,sha256=uj8idtDNOAIqpZ9p8usQg6mop1CQkJ5TlB4Q7CJdTIE,3082
164
- contentctl-5.2.0.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
165
- contentctl-5.2.0.dist-info/METADATA,sha256=SxtNJ4XnhZzf7fFzZR3NWsl7hPwh3gX1lqwRTzQFaGc,5097
166
- contentctl-5.2.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
167
- contentctl-5.2.0.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
168
- contentctl-5.2.0.dist-info/RECORD,,
166
+ contentctl-5.3.0.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
167
+ contentctl-5.3.0.dist-info/METADATA,sha256=qnEm__g-PB2TIjTXm7DFPJWrdgj8vlXQlE-qrHjtLTA,5134
168
+ contentctl-5.3.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
169
+ contentctl-5.3.0.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
+ contentctl-5.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,52 +0,0 @@
1
- import csv
2
- from contentctl.objects.data_source import DataSource
3
- from typing import List
4
- import pathlib
5
-
6
-
7
- class DataSourceWriter:
8
- @staticmethod
9
- def writeDataSourceCsv(
10
- data_source_objects: List[DataSource], file_path: pathlib.Path
11
- ):
12
- with open(file_path, mode="w", newline="") as file:
13
- writer = csv.writer(file)
14
- # Write the header
15
- writer.writerow(
16
- [
17
- "name",
18
- "id",
19
- "author",
20
- "source",
21
- "sourcetype",
22
- "separator",
23
- "supported_TA_name",
24
- "supported_TA_version",
25
- "supported_TA_url",
26
- "description",
27
- ]
28
- )
29
- # Write the data
30
- for data_source in data_source_objects:
31
- if len(data_source.supported_TA) > 0:
32
- supported_TA_name = data_source.supported_TA[0].name
33
- supported_TA_version = data_source.supported_TA[0].version
34
- supported_TA_url = data_source.supported_TA[0].url or ""
35
- else:
36
- supported_TA_name = ""
37
- supported_TA_version = ""
38
- supported_TA_url = ""
39
- writer.writerow(
40
- [
41
- data_source.name,
42
- data_source.id,
43
- data_source.author,
44
- data_source.source,
45
- data_source.sourcetype,
46
- data_source.separator,
47
- supported_TA_name,
48
- supported_TA_version,
49
- supported_TA_url,
50
- data_source.description,
51
- ]
52
- )