contentctl 5.0.0a3__py3-none-any.whl → 5.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- contentctl/actions/inspect.py +69 -29
- contentctl/actions/new_content.py +12 -10
- contentctl/contentctl.py +34 -0
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +11 -15
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +53 -10
- contentctl/objects/baseline.py +12 -11
- contentctl/objects/config.py +3 -1
- contentctl/objects/constants.py +3 -0
- contentctl/objects/data_source.py +6 -2
- contentctl/objects/investigation.py +14 -7
- contentctl/objects/rba.py +55 -4
- contentctl/objects/story.py +11 -7
- contentctl/output/templates/analyticstories_detections.j2 +1 -1
- contentctl/output/templates/analyticstories_stories.j2 +1 -1
- contentctl/output/templates/es_investigations_investigations.j2 +1 -1
- contentctl/output/templates/es_investigations_stories.j2 +1 -1
- contentctl/output/templates/savedsearches_baselines.j2 +2 -2
- contentctl/output/templates/savedsearches_detections.j2 +2 -8
- contentctl/output/templates/savedsearches_investigations.j2 +2 -2
- contentctl/output/templates/transforms.j2 +0 -2
- contentctl/templates/stories/cobalt_strike.yml +1 -0
- {contentctl-5.0.0a3.dist-info → contentctl-5.0.2.dist-info}/METADATA +1 -1
- {contentctl-5.0.0a3.dist-info → contentctl-5.0.2.dist-info}/RECORD +26 -26
- {contentctl-5.0.0a3.dist-info → contentctl-5.0.2.dist-info}/LICENSE.md +0 -0
- {contentctl-5.0.0a3.dist-info → contentctl-5.0.2.dist-info}/WHEEL +0 -0
- {contentctl-5.0.0a3.dist-info → contentctl-5.0.2.dist-info}/entry_points.txt +0 -0
contentctl/actions/inspect.py
CHANGED
|
@@ -1,23 +1,45 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
import pathlib
|
|
4
|
-
import json
|
|
5
1
|
import datetime
|
|
6
|
-
import
|
|
2
|
+
import json
|
|
3
|
+
import pathlib
|
|
4
|
+
import sys
|
|
7
5
|
import time
|
|
6
|
+
import timeit
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from io import BufferedReader
|
|
8
9
|
|
|
9
|
-
from requests import Session,
|
|
10
|
+
from requests import Session, get, post
|
|
10
11
|
from requests.auth import HTTPBasicAuth
|
|
11
12
|
|
|
12
13
|
from contentctl.objects.config import inspect
|
|
13
|
-
from contentctl.objects.savedsearches_conf import SavedsearchesConf
|
|
14
14
|
from contentctl.objects.errors import (
|
|
15
|
-
MetadataValidationError,
|
|
16
15
|
DetectionIDError,
|
|
17
16
|
DetectionMissingError,
|
|
18
|
-
|
|
17
|
+
MetadataValidationError,
|
|
19
18
|
VersionBumpingError,
|
|
19
|
+
VersionDecrementedError,
|
|
20
20
|
)
|
|
21
|
+
from contentctl.objects.savedsearches_conf import SavedsearchesConf
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
The following list includes all appinspect tags available from:
|
|
25
|
+
https://dev.splunk.com/enterprise/reference/appinspect/appinspecttagreference/
|
|
26
|
+
|
|
27
|
+
This allows contentctl to be as forward-leaning as possible in catching
|
|
28
|
+
any potential issues on the widest variety of stacks.
|
|
29
|
+
"""
|
|
30
|
+
INCLUDED_TAGS_LIST = [
|
|
31
|
+
"aarch64_compatibility",
|
|
32
|
+
"ast",
|
|
33
|
+
"cloud",
|
|
34
|
+
"future",
|
|
35
|
+
"manual",
|
|
36
|
+
"packaging_standards",
|
|
37
|
+
"private_app",
|
|
38
|
+
"private_classic",
|
|
39
|
+
"private_victoria",
|
|
40
|
+
"splunk_appinspect",
|
|
41
|
+
]
|
|
42
|
+
INCLUDED_TAGS_STRING = ",".join(INCLUDED_TAGS_LIST)
|
|
21
43
|
|
|
22
44
|
|
|
23
45
|
@dataclass(frozen=True)
|
|
@@ -28,7 +50,6 @@ class InspectInputDto:
|
|
|
28
50
|
class Inspect:
|
|
29
51
|
def execute(self, config: inspect) -> str:
|
|
30
52
|
if config.build_app or config.build_api:
|
|
31
|
-
self.inspectAppCLI(config)
|
|
32
53
|
appinspect_token = self.inspectAppAPI(config)
|
|
33
54
|
|
|
34
55
|
if config.enable_metadata_validation:
|
|
@@ -49,10 +70,6 @@ class Inspect:
|
|
|
49
70
|
session.auth = HTTPBasicAuth(
|
|
50
71
|
config.splunk_api_username, config.splunk_api_password
|
|
51
72
|
)
|
|
52
|
-
if config.stack_type not in ["victoria", "classic"]:
|
|
53
|
-
raise Exception(
|
|
54
|
-
f"stack_type MUST be either 'classic' or 'victoria', NOT '{config.stack_type}'"
|
|
55
|
-
)
|
|
56
73
|
|
|
57
74
|
APPINSPECT_API_LOGIN = "https://api.splunk.com/2.0/rest/login/splunk"
|
|
58
75
|
|
|
@@ -64,10 +81,6 @@ class Inspect:
|
|
|
64
81
|
APPINSPECT_API_VALIDATION_REQUEST = (
|
|
65
82
|
"https://appinspect.splunk.com/v1/app/validate"
|
|
66
83
|
)
|
|
67
|
-
headers = {
|
|
68
|
-
"Authorization": f"bearer {authorization_bearer}",
|
|
69
|
-
"Cache-Control": "no-cache",
|
|
70
|
-
}
|
|
71
84
|
|
|
72
85
|
package_path = config.getPackageFilePath(include_version=False)
|
|
73
86
|
if not package_path.is_file():
|
|
@@ -77,18 +90,43 @@ class Inspect:
|
|
|
77
90
|
"trying to 'contentctl deploy_acs' the package BEFORE running 'contentctl build'?"
|
|
78
91
|
)
|
|
79
92
|
|
|
80
|
-
|
|
93
|
+
"""
|
|
94
|
+
Some documentation on "files" argument for requests.post exists here:
|
|
95
|
+
https://docs.python-requests.org/en/latest/api/
|
|
96
|
+
The type (None, INCLUDED_TAGS_STRING) is intentional, and the None is important.
|
|
97
|
+
In curl syntax, the request we make below is equivalent to
|
|
98
|
+
curl -X POST \
|
|
99
|
+
-H "Authorization: bearer <TOKEN>" \
|
|
100
|
+
-H "Cache-Control: no-cache" \
|
|
101
|
+
-F "app_package=@<PATH/APP-PACKAGE>" \
|
|
102
|
+
-F "included_tags=cloud" \
|
|
103
|
+
--url "https://appinspect.splunk.com/v1/app/validate"
|
|
104
|
+
|
|
105
|
+
This is confirmed by the great resource:
|
|
106
|
+
https://curlconverter.com/
|
|
107
|
+
"""
|
|
108
|
+
data: dict[str, tuple[None, str] | BufferedReader] = {
|
|
81
109
|
"app_package": open(package_path, "rb"),
|
|
82
|
-
"included_tags": (
|
|
110
|
+
"included_tags": (
|
|
111
|
+
None,
|
|
112
|
+
INCLUDED_TAGS_STRING,
|
|
113
|
+
), # tuple with None is intentional here
|
|
83
114
|
}
|
|
84
115
|
|
|
85
|
-
|
|
116
|
+
headers = {
|
|
117
|
+
"Authorization": f"bearer {authorization_bearer}",
|
|
118
|
+
"Cache-Control": "no-cache",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
res = post(APPINSPECT_API_VALIDATION_REQUEST, files=data, headers=headers)
|
|
86
122
|
|
|
87
123
|
res.raise_for_status()
|
|
88
124
|
|
|
89
125
|
request_id = res.json().get("request_id", None)
|
|
90
|
-
APPINSPECT_API_VALIDATION_STATUS =
|
|
91
|
-
|
|
126
|
+
APPINSPECT_API_VALIDATION_STATUS = (
|
|
127
|
+
f"https://appinspect.splunk.com/v1/app/validate/status/{request_id}"
|
|
128
|
+
)
|
|
129
|
+
|
|
92
130
|
startTime = timeit.default_timer()
|
|
93
131
|
# the first time, wait for 40 seconds. subsequent times, wait for less.
|
|
94
132
|
# this is because appinspect takes some time to return, so there is no sense
|
|
@@ -114,7 +152,9 @@ class Inspect:
|
|
|
114
152
|
raise Exception(f"Error - Unknown Appinspect API status '{status}'")
|
|
115
153
|
|
|
116
154
|
# We have finished running appinspect, so get the report
|
|
117
|
-
APPINSPECT_API_REPORT =
|
|
155
|
+
APPINSPECT_API_REPORT = (
|
|
156
|
+
f"https://appinspect.splunk.com/v1/app/report/{request_id}"
|
|
157
|
+
)
|
|
118
158
|
# Get human-readable HTML report
|
|
119
159
|
headers = headers = {
|
|
120
160
|
"Authorization": f"bearer {authorization_bearer}",
|
|
@@ -159,14 +199,14 @@ class Inspect:
|
|
|
159
199
|
"\t - https://dev.splunk.com/enterprise/docs/developapps/testvalidate/appinspect/useappinspectclitool/"
|
|
160
200
|
)
|
|
161
201
|
from splunk_appinspect.main import (
|
|
162
|
-
validate,
|
|
163
|
-
MODE_OPTION,
|
|
164
202
|
APP_PACKAGE_ARGUMENT,
|
|
165
|
-
OUTPUT_FILE_OPTION,
|
|
166
|
-
LOG_FILE_OPTION,
|
|
167
|
-
INCLUDED_TAGS_OPTION,
|
|
168
203
|
EXCLUDED_TAGS_OPTION,
|
|
204
|
+
INCLUDED_TAGS_OPTION,
|
|
205
|
+
LOG_FILE_OPTION,
|
|
206
|
+
MODE_OPTION,
|
|
207
|
+
OUTPUT_FILE_OPTION,
|
|
169
208
|
TEST_MODE,
|
|
209
|
+
validate,
|
|
170
210
|
)
|
|
171
211
|
except Exception as e:
|
|
172
212
|
print(e)
|
|
@@ -32,6 +32,14 @@ class NewContent:
|
|
|
32
32
|
},
|
|
33
33
|
]
|
|
34
34
|
|
|
35
|
+
DEFAULT_RBA = {
|
|
36
|
+
"message": "Risk Message goes here",
|
|
37
|
+
"risk_objects": [{"field": "dest", "type": "system", "score": 10}],
|
|
38
|
+
"threat_objects": [
|
|
39
|
+
{"field": "parent_process_name", "type": "parent_process_name"}
|
|
40
|
+
],
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
def buildDetection(self) -> tuple[dict[str, Any], str]:
|
|
36
44
|
questions = NewContentQuestions.get_questions_detection()
|
|
37
45
|
answers: dict[str, str] = questionary.prompt(
|
|
@@ -42,8 +50,8 @@ class NewContent:
|
|
|
42
50
|
raise ValueError("User didn't answer one or more questions!")
|
|
43
51
|
|
|
44
52
|
data_source_field = (
|
|
45
|
-
answers["
|
|
46
|
-
if len(answers["
|
|
53
|
+
answers["data_sources"]
|
|
54
|
+
if len(answers["data_sources"]) > 0
|
|
47
55
|
else [f"{NewContent.UPDATE_PREFIX} zero or more data_sources"]
|
|
48
56
|
)
|
|
49
57
|
file_name = (
|
|
@@ -83,19 +91,12 @@ class NewContent:
|
|
|
83
91
|
f"{NewContent.UPDATE_PREFIX} zero or more http references to provide more information about your search"
|
|
84
92
|
],
|
|
85
93
|
"drilldown_searches": NewContent.DEFAULT_DRILLDOWN_DEF,
|
|
86
|
-
"rba":
|
|
87
|
-
"message": "Risk Messages goes here",
|
|
88
|
-
"risk_objects": [],
|
|
89
|
-
"threat_objects": [],
|
|
90
|
-
},
|
|
94
|
+
"rba": NewContent.DEFAULT_RBA,
|
|
91
95
|
"tags": {
|
|
92
96
|
"analytic_story": [
|
|
93
97
|
f"{NewContent.UPDATE_PREFIX} by providing zero or more analytic stories"
|
|
94
98
|
],
|
|
95
99
|
"asset_type": f"{NewContent.UPDATE_PREFIX} by providing and asset type from {list(AssetType._value2member_map_)}",
|
|
96
|
-
"confidence": f"{NewContent.UPDATE_PREFIX} by providing a value between 1-100",
|
|
97
|
-
"impact": f"{NewContent.UPDATE_PREFIX} by providing a value between 1-100",
|
|
98
|
-
"message": f"{NewContent.UPDATE_PREFIX} by providing a risk message. Fields in your search results can be referenced using $fieldName$",
|
|
99
100
|
"mitre_attack_id": mitre_attack_ids,
|
|
100
101
|
"product": [
|
|
101
102
|
"Splunk Enterprise",
|
|
@@ -139,6 +140,7 @@ class NewContent:
|
|
|
139
140
|
del answers["story_name"]
|
|
140
141
|
answers["id"] = str(uuid.uuid4())
|
|
141
142
|
answers["version"] = 1
|
|
143
|
+
answers["status"] = "production"
|
|
142
144
|
answers["date"] = datetime.today().strftime("%Y-%m-%d")
|
|
143
145
|
answers["author"] = answers["story_author"]
|
|
144
146
|
del answers["story_author"]
|
contentctl/contentctl.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import pathlib
|
|
2
|
+
import random
|
|
2
3
|
import sys
|
|
3
4
|
import traceback
|
|
4
5
|
import warnings
|
|
6
|
+
from dataclasses import dataclass
|
|
5
7
|
|
|
6
8
|
import tyro
|
|
7
9
|
|
|
@@ -155,6 +157,35 @@ YOU HAVE BEEN WARNED!
|
|
|
155
157
|
"""
|
|
156
158
|
|
|
157
159
|
|
|
160
|
+
def get_random_compliment():
|
|
161
|
+
compliments = [
|
|
162
|
+
"Your detection rules are like a zero-day shield! 🛡️",
|
|
163
|
+
"You catch threats like it's child's play! 🎯",
|
|
164
|
+
"Your correlation rules are pure genius! 🧠",
|
|
165
|
+
"Threat actors fear your detection engineering! ⚔️",
|
|
166
|
+
"You're the SOC's secret weapon! 🦾",
|
|
167
|
+
"Your false positive rate is impressively low! 📊",
|
|
168
|
+
"Malware trembles at your detection logic! 🦠",
|
|
169
|
+
"You're the threat hunter extraordinaire! 🔍",
|
|
170
|
+
"Your MITRE mappings are a work of art! 🎨",
|
|
171
|
+
"APTs have nightmares about your detections! 👻",
|
|
172
|
+
"Your content testing is bulletproof! 🎯",
|
|
173
|
+
"You're the detection engineering MVP! 🏆",
|
|
174
|
+
]
|
|
175
|
+
return random.choice(compliments)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def recognize_func():
|
|
179
|
+
print(get_random_compliment())
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass
|
|
183
|
+
class RecognizeCommand:
|
|
184
|
+
"""Dummy subcommand for 'recognize' with no parameters."""
|
|
185
|
+
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
158
189
|
def main():
|
|
159
190
|
print(CONTENTCTL_5_WARNING)
|
|
160
191
|
try:
|
|
@@ -210,6 +241,7 @@ def main():
|
|
|
210
241
|
"test_servers": test_servers.model_construct(**t.__dict__),
|
|
211
242
|
"release_notes": release_notes.model_construct(**config_obj),
|
|
212
243
|
"deploy_acs": deploy_acs.model_construct(**t.__dict__),
|
|
244
|
+
"recognize": RecognizeCommand(),
|
|
213
245
|
}
|
|
214
246
|
)
|
|
215
247
|
|
|
@@ -240,6 +272,8 @@ def main():
|
|
|
240
272
|
deploy_acs_func(updated_config)
|
|
241
273
|
elif type(config) is test or type(config) is test_servers:
|
|
242
274
|
test_common_func(config)
|
|
275
|
+
elif type(config) is RecognizeCommand:
|
|
276
|
+
recognize_func()
|
|
243
277
|
else:
|
|
244
278
|
raise Exception(f"Unknown command line type '{type(config).__name__}'")
|
|
245
279
|
except FileNotFoundError as e:
|
|
@@ -383,21 +383,17 @@ class Detection_Abstract(SecurityContentObject):
|
|
|
383
383
|
@computed_field
|
|
384
384
|
@property
|
|
385
385
|
def risk(self) -> list[dict[str, Any]]:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
threat_object["threat_object_field"] = entity.field
|
|
398
|
-
threat_object["threat_object_type"] = entity.type
|
|
399
|
-
risk_objects.append(threat_object)
|
|
400
|
-
return risk_objects
|
|
386
|
+
if self.rba is None:
|
|
387
|
+
raise Exception(
|
|
388
|
+
f"Attempting to serialize rba section of [{self.name}], however RBA section is None"
|
|
389
|
+
)
|
|
390
|
+
"""
|
|
391
|
+
action.risk.param._risk
|
|
392
|
+
of the conf file only contains a list of dicts. We do not eant to
|
|
393
|
+
include the message here, so we do not return it.
|
|
394
|
+
"""
|
|
395
|
+
rba_dict = self.rba.model_dump()
|
|
396
|
+
return rba_dict["risk_objects"] + rba_dict["threat_objects"]
|
|
401
397
|
|
|
402
398
|
@computed_field
|
|
403
399
|
@property
|
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Self
|
|
3
4
|
|
|
4
5
|
if TYPE_CHECKING:
|
|
6
|
+
from contentctl.input.director import DirectorOutputDto
|
|
5
7
|
from contentctl.objects.deployment import Deployment
|
|
6
8
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
7
|
-
from contentctl.input.director import DirectorOutputDto
|
|
8
9
|
|
|
9
|
-
from contentctl.objects.enums import AnalyticsType
|
|
10
|
-
from contentctl.objects.constants import CONTENTCTL_MAX_STANZA_LENGTH
|
|
11
10
|
import abc
|
|
12
|
-
import uuid
|
|
13
11
|
import datetime
|
|
12
|
+
import pathlib
|
|
14
13
|
import pprint
|
|
14
|
+
import uuid
|
|
15
|
+
from functools import cached_property
|
|
16
|
+
from typing import List, Optional, Tuple, Union
|
|
17
|
+
|
|
15
18
|
from pydantic import (
|
|
16
19
|
BaseModel,
|
|
17
|
-
|
|
20
|
+
ConfigDict,
|
|
18
21
|
Field,
|
|
19
|
-
ValidationInfo,
|
|
20
22
|
FilePath,
|
|
21
23
|
HttpUrl,
|
|
22
24
|
NonNegativeInt,
|
|
23
|
-
|
|
25
|
+
ValidationInfo,
|
|
26
|
+
computed_field,
|
|
27
|
+
field_validator,
|
|
24
28
|
model_serializer,
|
|
25
29
|
)
|
|
26
|
-
from typing import Tuple, Optional, List, Union
|
|
27
|
-
import pathlib
|
|
28
30
|
|
|
31
|
+
from contentctl.objects.constants import (
|
|
32
|
+
CONTENTCTL_MAX_STANZA_LENGTH,
|
|
33
|
+
DEPRECATED_TEMPLATE,
|
|
34
|
+
EXPERIMENTAL_TEMPLATE,
|
|
35
|
+
)
|
|
36
|
+
from contentctl.objects.enums import AnalyticsType, DetectionStatus
|
|
29
37
|
|
|
30
38
|
NO_FILE_NAME = "NO_FILE_NAME"
|
|
31
39
|
|
|
@@ -44,6 +52,41 @@ class SecurityContentObject_Abstract(BaseModel, abc.ABC):
|
|
|
44
52
|
def model_post_init(self, __context: Any) -> None:
|
|
45
53
|
self.ensureFileNameMatchesSearchName()
|
|
46
54
|
|
|
55
|
+
@computed_field
|
|
56
|
+
@cached_property
|
|
57
|
+
def status_aware_description(self) -> str:
|
|
58
|
+
"""We need to be able to write out a description that includes information
|
|
59
|
+
about whether or not a detection has been deprecated or not. This is important
|
|
60
|
+
for providing information to the user as well as powering the deprecation
|
|
61
|
+
assistant dashboad(s). Make sure this information is output correctly, if
|
|
62
|
+
appropriate.
|
|
63
|
+
Otherwise, if a detection is not deprecated or experimental, just return th
|
|
64
|
+
unmodified description.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
NotImplementedError: This content type does not support status_aware_description.
|
|
68
|
+
This is because the object does not define a status field
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
str: description, which may or may not be prefixed with the deprecation/experimental message
|
|
72
|
+
"""
|
|
73
|
+
status = getattr(self, "status", None)
|
|
74
|
+
|
|
75
|
+
if not isinstance(status, DetectionStatus):
|
|
76
|
+
raise NotImplementedError(
|
|
77
|
+
f"Detection status is not implemented for [{self.name}] of type '{type(self).__name__}'"
|
|
78
|
+
)
|
|
79
|
+
if status == DetectionStatus.experimental:
|
|
80
|
+
return EXPERIMENTAL_TEMPLATE.format(
|
|
81
|
+
content_type=type(self).__name__, description=self.description
|
|
82
|
+
)
|
|
83
|
+
elif status == DetectionStatus.deprecated:
|
|
84
|
+
return DEPRECATED_TEMPLATE.format(
|
|
85
|
+
content_type=type(self).__name__, description=self.description
|
|
86
|
+
)
|
|
87
|
+
else:
|
|
88
|
+
return self.description
|
|
89
|
+
|
|
47
90
|
@model_serializer
|
|
48
91
|
def serialize_model(self):
|
|
49
92
|
return {
|
contentctl/objects/baseline.py
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Annotated, Any, List, Literal
|
|
3
4
|
|
|
4
5
|
if TYPE_CHECKING:
|
|
5
6
|
from contentctl.input.director import DirectorOutputDto
|
|
6
7
|
|
|
7
8
|
from pydantic import (
|
|
8
|
-
field_validator,
|
|
9
|
-
ValidationInfo,
|
|
10
9
|
Field,
|
|
11
|
-
|
|
10
|
+
ValidationInfo,
|
|
12
11
|
computed_field,
|
|
12
|
+
field_validator,
|
|
13
|
+
model_serializer,
|
|
13
14
|
)
|
|
14
|
-
from contentctl.objects.deployment import Deployment
|
|
15
|
-
from contentctl.objects.security_content_object import SecurityContentObject
|
|
16
|
-
from contentctl.objects.enums import DataModel
|
|
17
|
-
from contentctl.objects.baseline_tags import BaselineTags
|
|
18
15
|
|
|
16
|
+
from contentctl.objects.baseline_tags import BaselineTags
|
|
19
17
|
from contentctl.objects.config import CustomApp
|
|
20
|
-
|
|
21
|
-
from contentctl.objects.lookup import Lookup
|
|
22
18
|
from contentctl.objects.constants import (
|
|
23
|
-
CONTENTCTL_MAX_SEARCH_NAME_LENGTH,
|
|
24
19
|
CONTENTCTL_BASELINE_STANZA_NAME_FORMAT_TEMPLATE,
|
|
20
|
+
CONTENTCTL_MAX_SEARCH_NAME_LENGTH,
|
|
25
21
|
)
|
|
22
|
+
from contentctl.objects.deployment import Deployment
|
|
23
|
+
from contentctl.objects.enums import DataModel, DetectionStatus
|
|
24
|
+
from contentctl.objects.lookup import Lookup
|
|
25
|
+
from contentctl.objects.security_content_object import SecurityContentObject
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class Baseline(SecurityContentObject):
|
|
@@ -35,6 +35,7 @@ class Baseline(SecurityContentObject):
|
|
|
35
35
|
lookups: list[Lookup] = Field([], validate_default=True)
|
|
36
36
|
# enrichment
|
|
37
37
|
deployment: Deployment = Field({})
|
|
38
|
+
status: Literal[DetectionStatus.production, DetectionStatus.deprecated]
|
|
38
39
|
|
|
39
40
|
@field_validator("lookups", mode="before")
|
|
40
41
|
@classmethod
|
contentctl/objects/config.py
CHANGED
|
@@ -425,7 +425,6 @@ class inspect(build):
|
|
|
425
425
|
"enforcement (defaults to the latest release of the app published on Splunkbase)."
|
|
426
426
|
),
|
|
427
427
|
)
|
|
428
|
-
stack_type: StackType = Field(description="The type of your Splunk Cloud Stack")
|
|
429
428
|
|
|
430
429
|
@field_validator("enrichments", mode="after")
|
|
431
430
|
@classmethod
|
|
@@ -496,6 +495,9 @@ class new(Config_Base):
|
|
|
496
495
|
|
|
497
496
|
class deploy_acs(inspect):
|
|
498
497
|
model_config = ConfigDict(validate_default=False, arbitrary_types_allowed=True)
|
|
498
|
+
|
|
499
|
+
stack_type: StackType = Field(description="The type of your Splunk Cloud Stack")
|
|
500
|
+
|
|
499
501
|
# ignore linter error
|
|
500
502
|
splunk_cloud_jwt_token: str = Field(
|
|
501
503
|
exclude=True,
|
contentctl/objects/constants.py
CHANGED
|
@@ -144,3 +144,6 @@ CONTENTCTL_MAX_SEARCH_NAME_LENGTH = CONTENTCTL_MAX_STANZA_LENGTH - len(
|
|
|
144
144
|
app_label="ESCU", detection_name=""
|
|
145
145
|
)
|
|
146
146
|
)
|
|
147
|
+
|
|
148
|
+
DEPRECATED_TEMPLATE = "**WARNING**, this {content_type} has been marked **DEPRECATED** by the Splunk Threat Research Team. This means that it will no longer be maintained or supported. If you have any questions feel free to email us at: research@splunk.com. {description}"
|
|
149
|
+
EXPERIMENTAL_TEMPLATE = "**WARNING**, this {content_type} is marked **EXPERIMENTAL** by the Splunk Threat Research Team. This means that the {content_type} has been manually tested but we do not have the associated attack data to perform automated testing or cannot share this attack dataset due to its sensitive nature. If you have any questions feel free to email us at: research@splunk.com. {description}"
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, HttpUrl, model_serializer
|
|
6
|
+
|
|
4
7
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
5
8
|
|
|
6
9
|
|
|
@@ -20,6 +23,7 @@ class DataSource(SecurityContentObject):
|
|
|
20
23
|
field_mappings: None | list = None
|
|
21
24
|
convert_to_log_source: None | list = None
|
|
22
25
|
example_log: None | str = None
|
|
26
|
+
output_fields: list[str] = []
|
|
23
27
|
|
|
24
28
|
@model_serializer
|
|
25
29
|
def serialize_model(self):
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import re
|
|
3
|
-
from typing import List,
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
from contentctl.objects.
|
|
4
|
+
from typing import Any, List, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import ConfigDict, Field, computed_field, model_serializer
|
|
7
|
+
|
|
8
|
+
from contentctl.objects.config import CustomApp
|
|
8
9
|
from contentctl.objects.constants import (
|
|
9
10
|
CONTENTCTL_MAX_SEARCH_NAME_LENGTH,
|
|
10
|
-
CONTENTCTL_RESPONSE_TASK_NAME_FORMAT_TEMPLATE,
|
|
11
11
|
CONTENTCTL_MAX_STANZA_LENGTH,
|
|
12
|
+
CONTENTCTL_RESPONSE_TASK_NAME_FORMAT_TEMPLATE,
|
|
12
13
|
)
|
|
13
|
-
from contentctl.objects.
|
|
14
|
+
from contentctl.objects.enums import DataModel, DetectionStatus
|
|
15
|
+
from contentctl.objects.investigation_tags import InvestigationTags
|
|
16
|
+
from contentctl.objects.security_content_object import SecurityContentObject
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
class Investigation(SecurityContentObject):
|
|
@@ -21,6 +24,7 @@ class Investigation(SecurityContentObject):
|
|
|
21
24
|
how_to_implement: str = Field(...)
|
|
22
25
|
known_false_positives: str = Field(...)
|
|
23
26
|
tags: InvestigationTags
|
|
27
|
+
status: Literal[DetectionStatus.production, DetectionStatus.deprecated]
|
|
24
28
|
|
|
25
29
|
# enrichment
|
|
26
30
|
@computed_field
|
|
@@ -99,3 +103,6 @@ class Investigation(SecurityContentObject):
|
|
|
99
103
|
# back to itself
|
|
100
104
|
for story in self.tags.analytic_story:
|
|
101
105
|
story.investigations.append(self)
|
|
106
|
+
# back to itself
|
|
107
|
+
for story in self.tags.analytic_story:
|
|
108
|
+
story.investigations.append(self)
|
contentctl/objects/rba.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
3
|
from abc import ABC
|
|
4
|
-
from
|
|
5
|
-
from
|
|
4
|
+
from enum import Enum
|
|
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
10
|
|
|
8
11
|
RiskScoreValue_Type = Annotated[int, Field(ge=1, le=100)]
|
|
9
12
|
|
|
@@ -51,6 +54,28 @@ class RiskObject(BaseModel):
|
|
|
51
54
|
def __hash__(self):
|
|
52
55
|
return hash((self.field, self.type, self.score))
|
|
53
56
|
|
|
57
|
+
def __lt__(self, other: RiskObject) -> bool:
|
|
58
|
+
if (
|
|
59
|
+
f"{self.field}{self.type}{self.score}"
|
|
60
|
+
< f"{other.field}{other.type}{other.score}"
|
|
61
|
+
):
|
|
62
|
+
return True
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
@model_serializer
|
|
66
|
+
def serialize_risk_object(self) -> dict[str, str | int]:
|
|
67
|
+
"""
|
|
68
|
+
We define this explicitly for two reasons, even though the automatic
|
|
69
|
+
serialization works correctly. First we want to enforce a specific
|
|
70
|
+
field order for reasons of readability. Second, some of the fields
|
|
71
|
+
actually have different names than they do in the object.
|
|
72
|
+
"""
|
|
73
|
+
return {
|
|
74
|
+
"risk_object_field": self.field,
|
|
75
|
+
"risk_object_type": self.type,
|
|
76
|
+
"risk_score": self.score,
|
|
77
|
+
}
|
|
78
|
+
|
|
54
79
|
|
|
55
80
|
class ThreatObject(BaseModel):
|
|
56
81
|
field: str
|
|
@@ -59,6 +84,24 @@ class ThreatObject(BaseModel):
|
|
|
59
84
|
def __hash__(self):
|
|
60
85
|
return hash((self.field, self.type))
|
|
61
86
|
|
|
87
|
+
def __lt__(self, other: ThreatObject) -> bool:
|
|
88
|
+
if f"{self.field}{self.type}" < f"{other.field}{other.type}":
|
|
89
|
+
return True
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
@model_serializer
|
|
93
|
+
def serialize_threat_object(self) -> dict[str, str]:
|
|
94
|
+
"""
|
|
95
|
+
We define this explicitly for two reasons, even though the automatic
|
|
96
|
+
serialization works correctly. First we want to enforce a specific
|
|
97
|
+
field order for reasons of readability. Second, some of the fields
|
|
98
|
+
actually have different names than they do in the object.
|
|
99
|
+
"""
|
|
100
|
+
return {
|
|
101
|
+
"threat_object_field": self.field,
|
|
102
|
+
"threat_object_type": self.type,
|
|
103
|
+
}
|
|
104
|
+
|
|
62
105
|
|
|
63
106
|
class RBAObject(BaseModel, ABC):
|
|
64
107
|
message: str
|
|
@@ -94,3 +137,11 @@ class RBAObject(BaseModel, ABC):
|
|
|
94
137
|
raise Exception(
|
|
95
138
|
f"Error getting severity - risk_score must be between 0-100, but was actually {self.risk_score}"
|
|
96
139
|
)
|
|
140
|
+
|
|
141
|
+
@model_serializer
|
|
142
|
+
def serialize_rba(self) -> dict[str, str | list[dict[str, str | int]]]:
|
|
143
|
+
return {
|
|
144
|
+
"message": self.message,
|
|
145
|
+
"risk_objects": [obj.model_dump() for obj in sorted(self.risk_objects)],
|
|
146
|
+
"threat_objects": [obj.model_dump() for obj in sorted(self.threat_objects)],
|
|
147
|
+
}
|
contentctl/objects/story.py
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from contentctl.objects.story_tags import StoryTags
|
|
4
|
-
from pydantic import Field, model_serializer, computed_field, model_validator
|
|
2
|
+
|
|
5
3
|
import re
|
|
4
|
+
from typing import TYPE_CHECKING, List, Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import Field, computed_field, model_serializer, model_validator
|
|
7
|
+
|
|
8
|
+
from contentctl.objects.story_tags import StoryTags
|
|
6
9
|
|
|
7
10
|
if TYPE_CHECKING:
|
|
8
|
-
from contentctl.objects.detection import Detection
|
|
9
|
-
from contentctl.objects.investigation import Investigation
|
|
10
11
|
from contentctl.objects.baseline import Baseline
|
|
11
|
-
from contentctl.objects.data_source import DataSource
|
|
12
12
|
from contentctl.objects.config import CustomApp
|
|
13
|
+
from contentctl.objects.data_source import DataSource
|
|
14
|
+
from contentctl.objects.detection import Detection
|
|
15
|
+
from contentctl.objects.investigation import Investigation
|
|
13
16
|
|
|
17
|
+
from contentctl.objects.enums import DetectionStatus
|
|
14
18
|
from contentctl.objects.security_content_object import SecurityContentObject
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
class Story(SecurityContentObject):
|
|
18
22
|
narrative: str = Field(...)
|
|
19
23
|
tags: StoryTags = Field(...)
|
|
20
|
-
|
|
24
|
+
status: Literal[DetectionStatus.production, DetectionStatus.deprecated]
|
|
21
25
|
# These are updated when detection and investigation objects are created.
|
|
22
26
|
# Specifically in the model_post_init functions
|
|
23
27
|
detections: List[Detection] = []
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
type = detection
|
|
8
8
|
asset_type = {{ detection.tags.asset_type }}
|
|
9
9
|
confidence = medium
|
|
10
|
-
explanation = {{
|
|
10
|
+
explanation = {{ detection.status_aware_description | escapeNewlines() }}
|
|
11
11
|
{% if detection.how_to_implement is defined %}
|
|
12
12
|
how_to_implement = {{ detection.how_to_implement | escapeNewlines() }}
|
|
13
13
|
{% else %}
|
|
@@ -11,7 +11,7 @@ references = {{ story.getReferencesListForJson() | tojson }}
|
|
|
11
11
|
maintainers = [{"company": "{{ story.author_company }}", "email": "{{ story.author_email }}", "name": "{{ story.author_name }}"}]
|
|
12
12
|
spec_version = 3
|
|
13
13
|
searches = {{ story.storyAndInvestigationNamesWithApp(app) | tojson }}
|
|
14
|
-
description = {{ story.
|
|
14
|
+
description = {{ story.status_aware_description | escapeNewlines() }}
|
|
15
15
|
{% if story.narrative is defined %}
|
|
16
16
|
narrative = {{ story.narrative | escapeNewlines() }}
|
|
17
17
|
{% endif %}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
{% for response_task in objects %}
|
|
3
3
|
[panel://workbench_panel_{{ response_task.lowercase_name }}___response_task]
|
|
4
4
|
label = {{ response_task.name }}
|
|
5
|
-
description = {{ response_task.
|
|
5
|
+
description = {{ response_task.status_aware_description | escapeNewlines() }}
|
|
6
6
|
disabled = 0
|
|
7
7
|
tokens = {\
|
|
8
8
|
{% for token in response_task.inputs %}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
{% for story in objects %}
|
|
3
3
|
[panel_group://workbench_panel_group_{{ story.lowercase_name}}]
|
|
4
4
|
label = {{ story.name }}
|
|
5
|
-
description = {{ story.
|
|
5
|
+
description = {{ story.status_aware_description | escapeNewlines() }}
|
|
6
6
|
disabled = 0
|
|
7
7
|
|
|
8
8
|
{% if story.workbench_panels is defined %}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
action.escu = 0
|
|
9
9
|
action.escu.enabled = 1
|
|
10
10
|
action.escu.search_type = support
|
|
11
|
-
description = {{ detection.
|
|
11
|
+
description = {{ detection.status_aware_description | escapeNewlines() }}
|
|
12
12
|
action.escu.creation_date = {{ detection.date }}
|
|
13
13
|
action.escu.modification_date = {{ detection.date }}
|
|
14
14
|
{% if detection.tags.analytic_story is defined %}
|
|
@@ -29,7 +29,7 @@ action.escu.providing_technologies = {{ detection.providing_technologies | tojso
|
|
|
29
29
|
{% else %}
|
|
30
30
|
action.escu.providing_technologies = []
|
|
31
31
|
{% endif %}
|
|
32
|
-
action.escu.eli5 = {{ detection.
|
|
32
|
+
action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
|
|
33
33
|
{% if detection.how_to_implement is defined %}
|
|
34
34
|
action.escu.how_to_implement = {{ detection.how_to_implement | escapeNewlines() }}
|
|
35
35
|
{% else %}
|
|
@@ -5,16 +5,10 @@
|
|
|
5
5
|
[{{ detection.get_conf_stanza_name(app) }}]
|
|
6
6
|
action.escu = 0
|
|
7
7
|
action.escu.enabled = 1
|
|
8
|
-
{
|
|
9
|
-
description = **WARNING**, this detection has been marked **DEPRECATED** by the Splunk Threat Research Team. This means that it will no longer be maintained or supported. If you have any questions feel free to email us at: research@splunk.com. {{ detection.description | escapeNewlines() }}
|
|
10
|
-
{% elif detection.status == "experimental" %}
|
|
11
|
-
description = **WARNING**, this detection is marked **EXPERIMENTAL** by the Splunk Threat Research Team. This means that the detection has been manually tested but we do not have the associated attack data to perform automated testing or cannot share this attack dataset due to its sensitive nature. If you have any questions feel free to email us at: research@splunk.com. {{ detection.description | escapeNewlines() }}
|
|
12
|
-
{% else %}
|
|
13
|
-
description = {{ detection.description | escapeNewlines() }}
|
|
14
|
-
{% endif %}
|
|
8
|
+
description = {{ detection.status_aware_description | escapeNewlines() }}
|
|
15
9
|
action.escu.mappings = {{ detection.mappings | tojson }}
|
|
16
10
|
action.escu.data_models = {{ detection.datamodel | tojson }}
|
|
17
|
-
action.escu.eli5 = {{ detection.
|
|
11
|
+
action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
|
|
18
12
|
{% if detection.how_to_implement %}
|
|
19
13
|
action.escu.how_to_implement = {{ detection.how_to_implement | escapeNewlines() }}
|
|
20
14
|
{% else %}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
action.escu = 0
|
|
10
10
|
action.escu.enabled = 1
|
|
11
11
|
action.escu.search_type = investigative
|
|
12
|
-
description = {{ detection.
|
|
12
|
+
description = {{ detection.status_aware_description | escapeNewlines() }}
|
|
13
13
|
action.escu.creation_date = {{ detection.date }}
|
|
14
14
|
action.escu.modification_date = {{ detection.date }}
|
|
15
15
|
{% if detection.tags.analytic_story is defined %}
|
|
@@ -21,7 +21,7 @@ action.escu.earliest_time_offset = 3600
|
|
|
21
21
|
action.escu.latest_time_offset = 86400
|
|
22
22
|
action.escu.providing_technologies = []
|
|
23
23
|
action.escu.data_models = {{ detection.datamodel | tojson }}
|
|
24
|
-
action.escu.eli5 = {{ detection.
|
|
24
|
+
action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
|
|
25
25
|
action.escu.how_to_implement = none
|
|
26
26
|
action.escu.known_false_positives = None at this time
|
|
27
27
|
disabled = true
|
|
@@ -13,9 +13,7 @@ default_match = {{ lookup.default_match | lower }}
|
|
|
13
13
|
{% if lookup.case_sensitive_match is defined and lookup.case_sensitive_match != None %}
|
|
14
14
|
case_sensitive_match = {{ lookup.case_sensitive_match | lower }}
|
|
15
15
|
{% endif %}
|
|
16
|
-
{% if lookup.description is defined and lookup.description != None %}
|
|
17
16
|
# description = {{ lookup.description | escapeNewlines() }}
|
|
18
|
-
{% endif %}
|
|
19
17
|
{% if lookup.match_type | length > 0 %}
|
|
20
18
|
match_type = {{ lookup.match_type_to_conf_format }}
|
|
21
19
|
{% endif %}
|
|
@@ -3,6 +3,7 @@ id: bcfd17e8-5461-400a-80a2-3b7d1459220c
|
|
|
3
3
|
version: 1
|
|
4
4
|
date: '2021-02-16'
|
|
5
5
|
author: Michael Haag, Splunk
|
|
6
|
+
status: production
|
|
6
7
|
description: Cobalt Strike is threat emulation software. Red teams and penetration
|
|
7
8
|
testers use Cobalt Strike to demonstrate the risk of a breach and evaluate mature
|
|
8
9
|
security programs. Most recently, Cobalt Strike has become the choice tool by threat
|
|
@@ -14,14 +14,14 @@ contentctl/actions/detection_testing/views/DetectionTestingViewFile.py,sha256=G-
|
|
|
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
16
|
contentctl/actions/initialize.py,sha256=KaWSbrTaJA4vNSpKc_rwdlaaERnWw_hPlWwsPOA6Gy8,3191
|
|
17
|
-
contentctl/actions/inspect.py,sha256=
|
|
18
|
-
contentctl/actions/new_content.py,sha256=
|
|
17
|
+
contentctl/actions/inspect.py,sha256=zFNbDXY7Bi1xTBHirNyHpH1-2A1n3rsOsRvu8E0xUao,19375
|
|
18
|
+
contentctl/actions/new_content.py,sha256=xs0QvHzlrf0g-EgdUJTkdDdFaA-uEGmzMTixDt6NcTY,8212
|
|
19
19
|
contentctl/actions/release_notes.py,sha256=_Rdljg0tPSAFlw34LJ7dUsHLiH8tJTQ6B95X6MvxURo,17023
|
|
20
20
|
contentctl/actions/reporting.py,sha256=GF32i7sHdc47bw-VWSW-nZ1QBaUl6Ni1JjV5_SOyiAU,1660
|
|
21
21
|
contentctl/actions/test.py,sha256=GTtvHi1yB5yDm1jPMyuc4WxczOq-O7f2N8LpTmMaWgU,6014
|
|
22
22
|
contentctl/actions/validate.py,sha256=thnxanLj6mfw5g17C1FrzWlkpyIT_AjnDxv_wyNdspA,5837
|
|
23
23
|
contentctl/api.py,sha256=6s17vNOW1E1EzQqOCXAa5uWuhwwShu-JkGSgrsOFEMs,6329
|
|
24
|
-
contentctl/contentctl.py,sha256=
|
|
24
|
+
contentctl/contentctl.py,sha256=pKtjRHsdTHb505Xjvrdx8v_EGJQ9ZAMfKK4P5N4ge-I,12312
|
|
25
25
|
contentctl/enrichments/attack_enrichment.py,sha256=68C9xQ8Q3YX-luRdK2hLnwWtRFpheFA2kE4v5GOLGEo,6358
|
|
26
26
|
contentctl/enrichments/cve_enrichment.py,sha256=TsZ52ef2njt19lPf_VyclY_-5Z5iQ1boVOAxFbjGdSQ,2431
|
|
27
27
|
contentctl/enrichments/splunk_app_enrichment.py,sha256=Xynxjjkqlw0_RtQ1thGSFwy1I3HdmPAJmNKZezyqniU,3419
|
|
@@ -32,20 +32,20 @@ contentctl/helper/utils.py,sha256=rigwZzCwWzn11sKTVWDkYEtLmRSf0yBbJ671OSRQnOM,19
|
|
|
32
32
|
contentctl/input/director.py,sha256=asK4yUlSVdv0QDUzCrTEXQUm0j9hbzVu55o_-wD-eWc,11560
|
|
33
33
|
contentctl/input/new_content_questions.py,sha256=z2C4Mg7-EyxtiF2z9m4SnSbi6QO4CUPB3wg__JeMXIQ,4067
|
|
34
34
|
contentctl/input/yml_reader.py,sha256=ymmAqsWsf9Oj56waDOhCh_E4SomkSCmu4dAx7iURFt8,2050
|
|
35
|
-
contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=
|
|
36
|
-
contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=
|
|
35
|
+
contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=9uUb3BkH9d5vc9qn7RURhki9HZZJSGBTYfnqMImsiY4,43814
|
|
36
|
+
contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=P1v5n8hM4XzJOjNZbJ5m3y4Bu-cQYaddLPdbE6K-4Xw,12164
|
|
37
37
|
contentctl/objects/alert_action.py,sha256=iEvdEOT4TrTXT0z4rQ_W5v79hPJpPhFPSzo7TuHDxwA,1376
|
|
38
38
|
contentctl/objects/annotated_types.py,sha256=eAMm1Nm3_C5pwfCxhzL5ynDRsC_eK614bFuwUFxPVLw,261
|
|
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=
|
|
42
|
+
contentctl/objects/baseline.py,sha256=grzM56KCpROjMnJQIan-fG0LCYfRGA2GHui4FwBwb8A,3172
|
|
43
43
|
contentctl/objects/baseline_tags.py,sha256=Eomy8y3HV-E6Lym5B5ZZTtsmQJYi6Jd4y8GZpTWGYgQ,1643
|
|
44
|
-
contentctl/objects/config.py,sha256=
|
|
45
|
-
contentctl/objects/constants.py,sha256=
|
|
44
|
+
contentctl/objects/config.py,sha256=3l8tFVwrBDpAnS7aBgj6to0Kc8_s4bxuZY5Bm5vel8k,48605
|
|
45
|
+
contentctl/objects/constants.py,sha256=P4K07PX4_pL_n5jmBVn1BxtoFpjvhT8MuHOquhnEkdA,5465
|
|
46
46
|
contentctl/objects/correlation_search.py,sha256=ab6v-0nbzujhTMpwaXynQiInWpRO1zB5KR4eZLCav_M,45234
|
|
47
47
|
contentctl/objects/dashboard.py,sha256=qMSP76hkJo7PVsWr19hQW4eYoUqGTcRejaOEjlcA_DY,4198
|
|
48
|
-
contentctl/objects/data_source.py,sha256=
|
|
48
|
+
contentctl/objects/data_source.py,sha256=qt4W14DEwKGO69oLGdJeuYqbWvGkZ6j5Nz0R1RhDQEQ,1491
|
|
49
49
|
contentctl/objects/deployment.py,sha256=FRsgsX2T1gvA_0A44_sFPr22rsedxXVIhtO7o9F7eZM,2902
|
|
50
50
|
contentctl/objects/deployment_email.py,sha256=_Sdr_BNjvXECiFonRHLkiOrIQp3slnUaERbptqRbD0Q,206
|
|
51
51
|
contentctl/objects/deployment_notable.py,sha256=j5AniTRDcw32El5H91qKOXDVZvUYxnIuM4Zzlhrm9cM,258
|
|
@@ -62,7 +62,7 @@ contentctl/objects/enums.py,sha256=HdaSQgEQ_T38BIlVYk1xdqMm05YyhQb0720nzBorXQw,1
|
|
|
62
62
|
contentctl/objects/errors.py,sha256=xX_FDUaJbJiOWgjgrzjtYW5QsD41UZ2KWqH-yGkHaCU,5554
|
|
63
63
|
contentctl/objects/integration_test.py,sha256=TYjKyH4YinUnYXOse5BQGCa4-ez_5mtoMwvh1JJcb0o,1254
|
|
64
64
|
contentctl/objects/integration_test_result.py,sha256=_uUSgqgjFhEZM8UwOJI6Q9K-ekIrbKU6OPdqHZycl-s,279
|
|
65
|
-
contentctl/objects/investigation.py,sha256=
|
|
65
|
+
contentctl/objects/investigation.py,sha256=kmvQ0grCd1YVEW5wVF4-_Rx87PnOxPiNoN_ufTLc3ZM,3659
|
|
66
66
|
contentctl/objects/investigation_tags.py,sha256=qDGNusrWDvCX_GcBEzag2MydSV0LIhGxoXZGgxDXfHA,1317
|
|
67
67
|
contentctl/objects/lookup.py,sha256=mzOPhMDyoNZKLAj8zf6Wg6i9FJKMu3qHWinATtH75I8,13015
|
|
68
68
|
contentctl/objects/macro.py,sha256=usbxyOPIRIJoDmvawfP2DxtFNf71GaDwffxiZsRkP5A,3594
|
|
@@ -73,13 +73,13 @@ contentctl/objects/notable_action.py,sha256=sW5XlpGznMHqyBmGXtXrl22hWLiCoKkfGCas
|
|
|
73
73
|
contentctl/objects/notable_event.py,sha256=2aOtmfnsdInTtN_fHAGIKmBTBritjHbS_Nc-pqL-GbY,689
|
|
74
74
|
contentctl/objects/playbook.py,sha256=mgYbWsD3OW86u11MbIFKvmyFueSoMJ1WBJm_rNrFvAo,2425
|
|
75
75
|
contentctl/objects/playbook_tags.py,sha256=O5obkQyb82YdJEii8ZJEQtrHtLOSnAvAkT1qIgpCK2s,1547
|
|
76
|
-
contentctl/objects/rba.py,sha256=
|
|
76
|
+
contentctl/objects/rba.py,sha256=VudoJAqADAEflDqVDWlOIk8epRfg_rqvfCsyavjvcaU,4737
|
|
77
77
|
contentctl/objects/risk_analysis_action.py,sha256=v-TQktXEEzbGzmTtqwEykXoSKdGnIlK_JojnqvvAE1s,4370
|
|
78
78
|
contentctl/objects/risk_event.py,sha256=JQUmXriiwi5FetqVnhM0hf5cUp6LzLSNPuoecC2JKK0,12593
|
|
79
79
|
contentctl/objects/risk_object.py,sha256=5iUKW_UwQLjjLWiD_vlE78uwH9bkaMNCHRNmKM25W1Q,905
|
|
80
80
|
contentctl/objects/savedsearches_conf.py,sha256=Dn_Pxd9i3RT6DwNh6JrgmfxjsO3q15xzMksYr3wIGwQ,8624
|
|
81
81
|
contentctl/objects/security_content_object.py,sha256=iDnhq81P7m6Qkmc_Yi-wOyFm9gZUYnPy1GJxxyCtonA,245
|
|
82
|
-
contentctl/objects/story.py,sha256=
|
|
82
|
+
contentctl/objects/story.py,sha256=jalNgK4yYGRr_yVkzuO_YHIrPhpWHF0Q79PkTJhcLzY,5048
|
|
83
83
|
contentctl/objects/story_tags.py,sha256=SLwgkckLxBdtgJro0LnYgj5TFHZEgMiaqDI9q6OfNE0,2364
|
|
84
84
|
contentctl/objects/test_attack_data.py,sha256=7p-kOJguTZtG9y5th5U3qfPFvpiAWLST_OBw8dwWl_4,488
|
|
85
85
|
contentctl/objects/test_group.py,sha256=r-dXyddok4yslv8SIjwOpqylbN1rdjsRi-HIijvpWD0,2602
|
|
@@ -98,9 +98,9 @@ contentctl/output/doc_md_output.py,sha256=wlgbzBD2hUbQNIW2zv5sdrq2UdAKhOZJUYSObn
|
|
|
98
98
|
contentctl/output/jinja_writer.py,sha256=5PbFrc8KuLWrlNIHDvMTyvJ18u_mtjd5Led6-9sn2Eo,1204
|
|
99
99
|
contentctl/output/json_writer.py,sha256=waw73wOmalSrUFcr2K1CWR-xz5oW8il10zDAn56mtMg,1041
|
|
100
100
|
contentctl/output/svg_output.py,sha256=5s9fjmKullMV6cCCGwP7_xvQwg9EZLOKRKMw_IyO6hY,2988
|
|
101
|
-
contentctl/output/templates/analyticstories_detections.j2,sha256=
|
|
101
|
+
contentctl/output/templates/analyticstories_detections.j2,sha256=6ZiQO8np6KkX8skVoIB0BN9_s8SBW3qeo8IBA8r8GQk,923
|
|
102
102
|
contentctl/output/templates/analyticstories_investigations.j2,sha256=kqy9lR6W3avqETCM2tSZ8WWOlfiyOtFv6G5N4SZWSaQ,527
|
|
103
|
-
contentctl/output/templates/analyticstories_stories.j2,sha256=
|
|
103
|
+
contentctl/output/templates/analyticstories_stories.j2,sha256=MxkmwsgW1oge2YJhbgAzXVcTplSr5JjKIDxX4SBZV0E,676
|
|
104
104
|
contentctl/output/templates/app.conf.j2,sha256=UL80Px4IUGPD-DgcAiUrS4emHBIY7DxleSNyNXCH5tQ,623
|
|
105
105
|
contentctl/output/templates/app.manifest.j2,sha256=Q1803mcfgNvUs8s4e1zD1J3_mxfPYVtLkD8fhCO6d-I,1103
|
|
106
106
|
contentctl/output/templates/collections.j2,sha256=w2hkY7Yfm7AmY1O_7DP-znLS_whgKX79VbnW7QlvrNU,151
|
|
@@ -115,16 +115,16 @@ contentctl/output/templates/doc_playbooks.j2,sha256=CWsnm8F097oYT8anW3CE7JaX1haA
|
|
|
115
115
|
contentctl/output/templates/doc_playbooks_page.j2,sha256=2d5UNDSOxyMtxKGxGHzJ2Ny_UrqTq267NO1h-lmNduc,679
|
|
116
116
|
contentctl/output/templates/doc_stories.j2,sha256=0J3dAbfSZz-Ma1-C9B6vYPKGwrxoZryYoudy3wUIT4s,1827
|
|
117
117
|
contentctl/output/templates/doc_story_page.j2,sha256=jrf-As8GbqLarRoiDipfM9ZUVRl_bhdNsy-XaCrBaXE,874
|
|
118
|
-
contentctl/output/templates/es_investigations_investigations.j2,sha256=
|
|
119
|
-
contentctl/output/templates/es_investigations_stories.j2,sha256=
|
|
118
|
+
contentctl/output/templates/es_investigations_investigations.j2,sha256=vIWiQ6hNsb_-w8ChlWWbQXxGIx5jjWaQ6VoeBJxpHJk,1026
|
|
119
|
+
contentctl/output/templates/es_investigations_stories.j2,sha256=E-OvO7EFdqO3HKyfAY1jBS6VMsDVKuiVqg8SrDVXwls,401
|
|
120
120
|
contentctl/output/templates/header.j2,sha256=3usV7jm1q6J-QNnQrZzII9cN0XEGQjg_eVKrEQwfOG0,201
|
|
121
121
|
contentctl/output/templates/macros.j2,sha256=SLcQQ5X7TZS8j-2qP06BTXqdIcnwoYqTAaBLX2Dge7Y,390
|
|
122
122
|
contentctl/output/templates/panel.j2,sha256=Cw_W6p-14n6UivVfpS75KKJiJ2VpdGsSBceYsUYe9gk,221
|
|
123
|
-
contentctl/output/templates/savedsearches_baselines.j2,sha256=
|
|
124
|
-
contentctl/output/templates/savedsearches_detections.j2,sha256=
|
|
125
|
-
contentctl/output/templates/savedsearches_investigations.j2,sha256=
|
|
123
|
+
contentctl/output/templates/savedsearches_baselines.j2,sha256=WHZB4e0vmeym8832VxRmuUfDJ-YRYt6emcYaJrghI58,1709
|
|
124
|
+
contentctl/output/templates/savedsearches_detections.j2,sha256=B7_b8aBjEKAV7mJm0DxUS_HyCt63bQZtpacCQfDgDqc,6033
|
|
125
|
+
contentctl/output/templates/savedsearches_investigations.j2,sha256=KH2r8SgyAMiettSHypSbA2-1XmQ_8_8xzk3BkbZ1Re4,1196
|
|
126
126
|
contentctl/output/templates/server.conf.j2,sha256=sPZUkiuJNGm9R8rpjfRKyuAvmmQb0C4w9Q6hpmvmPeU,127
|
|
127
|
-
contentctl/output/templates/transforms.j2,sha256=
|
|
127
|
+
contentctl/output/templates/transforms.j2,sha256=mM9vIIGqxlhLk1W8sHoUgppfK1FO3ESQD1WCVAewhBw,1386
|
|
128
128
|
contentctl/output/templates/workflow_actions.j2,sha256=DFoZVnCa8dMRHjW2AdpoydBC0THgiH_W-Nx7WI4-uR4,925
|
|
129
129
|
contentctl/output/yml_writer.py,sha256=gGgbamHWunHKjj47TcqB04k0xliX6w3H7iajZtUZRSU,2124
|
|
130
130
|
contentctl/templates/README.md,sha256=GoRmywUqwnjaehY_GLmGqxsFXCLP9plpDYwB6W6nVPs,428
|
|
@@ -160,9 +160,9 @@ contentctl/templates/detections/network/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeu
|
|
|
160
160
|
contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
161
161
|
contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
|
|
162
162
|
contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
|
|
163
|
-
contentctl/templates/stories/cobalt_strike.yml,sha256=
|
|
164
|
-
contentctl-5.0.
|
|
165
|
-
contentctl-5.0.
|
|
166
|
-
contentctl-5.0.
|
|
167
|
-
contentctl-5.0.
|
|
168
|
-
contentctl-5.0.
|
|
163
|
+
contentctl/templates/stories/cobalt_strike.yml,sha256=uj8idtDNOAIqpZ9p8usQg6mop1CQkJ5TlB4Q7CJdTIE,3082
|
|
164
|
+
contentctl-5.0.2.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
|
|
165
|
+
contentctl-5.0.2.dist-info/METADATA,sha256=w4N00eZ7_Rn1nbpEbFDNCtIjij82KcNtZIe6YUflmuo,21539
|
|
166
|
+
contentctl-5.0.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
167
|
+
contentctl-5.0.2.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
|
|
168
|
+
contentctl-5.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|