contentctl 5.0.1__py3-none-any.whl → 5.0.3__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/contentctl.py +34 -0
- contentctl/objects/annotated_types.py +5 -2
- contentctl/objects/config.py +3 -1
- contentctl/objects/data_source.py +6 -2
- contentctl/objects/detection_tags.py +57 -4
- contentctl/objects/mitre_attack_enrichment.py +16 -3
- contentctl/objects/story_tags.py +6 -5
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +0 -1
- {contentctl-5.0.1.dist-info → contentctl-5.0.3.dist-info}/METADATA +1 -1
- {contentctl-5.0.1.dist-info → contentctl-5.0.3.dist-info}/RECORD +14 -14
- {contentctl-5.0.1.dist-info → contentctl-5.0.3.dist-info}/LICENSE.md +0 -0
- {contentctl-5.0.1.dist-info → contentctl-5.0.3.dist-info}/WHEEL +0 -0
- {contentctl-5.0.1.dist-info → contentctl-5.0.3.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)
|
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:
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
from pydantic import Field
|
|
2
1
|
from typing import Annotated
|
|
3
2
|
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
4
5
|
CVE_TYPE = Annotated[str, Field(pattern=r"^CVE-[1|2]\d{3}-\d+$")]
|
|
5
|
-
|
|
6
|
+
MITRE_ATTACK_ID_TYPE_PARENT = Annotated[str, Field(pattern=r"^T\d{4}$")]
|
|
7
|
+
MITRE_ATTACK_ID_TYPE_SUBTYPE = Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})$")]
|
|
8
|
+
MITRE_ATTACK_ID_TYPE = MITRE_ATTACK_ID_TYPE_PARENT | MITRE_ATTACK_ID_TYPE_SUBTYPE
|
|
6
9
|
APPID_TYPE = Annotated[str, Field(pattern="^[a-zA-Z0-9_-]+$")]
|
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,
|
|
@@ -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):
|
|
@@ -33,7 +33,10 @@ from contentctl.objects.enums import (
|
|
|
33
33
|
SecurityContentProductName,
|
|
34
34
|
SecurityDomain,
|
|
35
35
|
)
|
|
36
|
-
from contentctl.objects.mitre_attack_enrichment import
|
|
36
|
+
from contentctl.objects.mitre_attack_enrichment import (
|
|
37
|
+
MitreAttackEnrichment,
|
|
38
|
+
MitreAttackGroup,
|
|
39
|
+
)
|
|
37
40
|
|
|
38
41
|
|
|
39
42
|
class DetectionTags(BaseModel):
|
|
@@ -44,7 +47,7 @@ class DetectionTags(BaseModel):
|
|
|
44
47
|
asset_type: AssetType = Field(...)
|
|
45
48
|
group: list[str] = []
|
|
46
49
|
|
|
47
|
-
mitre_attack_id:
|
|
50
|
+
mitre_attack_id: list[MITRE_ATTACK_ID_TYPE] = []
|
|
48
51
|
nist: list[NistCategory] = []
|
|
49
52
|
|
|
50
53
|
product: list[SecurityContentProductName] = Field(..., min_length=1)
|
|
@@ -68,6 +71,15 @@ class DetectionTags(BaseModel):
|
|
|
68
71
|
phases.add(phase)
|
|
69
72
|
return sorted(list(phases))
|
|
70
73
|
|
|
74
|
+
# We do not want this to be included in serialization. By default, @property
|
|
75
|
+
# objects are not included in dumps
|
|
76
|
+
@property
|
|
77
|
+
def unique_mitre_attack_groups(self) -> list[MitreAttackGroup]:
|
|
78
|
+
group_set: set[MitreAttackGroup] = set()
|
|
79
|
+
for enrichment in self.mitre_attack_enrichments:
|
|
80
|
+
group_set.update(set(enrichment.mitre_attack_group_objects))
|
|
81
|
+
return sorted(group_set, key=lambda k: k.group)
|
|
82
|
+
|
|
71
83
|
# enum is intentionally Cis18 even though field is named cis20 for legacy reasons
|
|
72
84
|
@computed_field
|
|
73
85
|
@property
|
|
@@ -134,8 +146,8 @@ class DetectionTags(BaseModel):
|
|
|
134
146
|
|
|
135
147
|
if len(missing_tactics) > 0:
|
|
136
148
|
raise ValueError(f"Missing Mitre Attack IDs. {missing_tactics} not found.")
|
|
137
|
-
|
|
138
|
-
|
|
149
|
+
|
|
150
|
+
self.mitre_attack_enrichments = mitre_enrichments
|
|
139
151
|
|
|
140
152
|
return self
|
|
141
153
|
|
|
@@ -159,6 +171,44 @@ class DetectionTags(BaseModel):
|
|
|
159
171
|
return enrichments
|
|
160
172
|
"""
|
|
161
173
|
|
|
174
|
+
@field_validator("mitre_attack_id", mode="after")
|
|
175
|
+
@classmethod
|
|
176
|
+
def sameTypeAndSubtypeNotPresent(
|
|
177
|
+
cls, techniques_and_subtechniques: list[MITRE_ATTACK_ID_TYPE]
|
|
178
|
+
) -> list[MITRE_ATTACK_ID_TYPE]:
|
|
179
|
+
techniques: list[str] = [
|
|
180
|
+
f"{unknown_technique}."
|
|
181
|
+
for unknown_technique in techniques_and_subtechniques
|
|
182
|
+
if "." not in unknown_technique
|
|
183
|
+
]
|
|
184
|
+
subtechniques: list[MITRE_ATTACK_ID_TYPE] = [
|
|
185
|
+
unknown_technique
|
|
186
|
+
for unknown_technique in techniques_and_subtechniques
|
|
187
|
+
if "." in unknown_technique
|
|
188
|
+
]
|
|
189
|
+
subtype_and_parent_exist_exceptions: list[ValueError] = []
|
|
190
|
+
|
|
191
|
+
for subtechnique in subtechniques:
|
|
192
|
+
for technique in techniques:
|
|
193
|
+
if subtechnique.startswith(technique):
|
|
194
|
+
subtype_and_parent_exist_exceptions.append(
|
|
195
|
+
ValueError(
|
|
196
|
+
f" Technique : {technique.split('.')[0]}\n"
|
|
197
|
+
f" SubTechnique: {subtechnique}\n"
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if len(subtype_and_parent_exist_exceptions):
|
|
202
|
+
error_string = "\n".join(
|
|
203
|
+
str(e) for e in subtype_and_parent_exist_exceptions
|
|
204
|
+
)
|
|
205
|
+
raise ValueError(
|
|
206
|
+
"Overlapping MITRE Attack ID Techniques and Subtechniques may not be defined. "
|
|
207
|
+
f"Remove the Technique and keep the Subtechnique:\n{error_string}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return techniques_and_subtechniques
|
|
211
|
+
|
|
162
212
|
@field_validator("analytic_story", mode="before")
|
|
163
213
|
@classmethod
|
|
164
214
|
def mapStoryNamesToStoryObjects(
|
|
@@ -238,3 +288,6 @@ class DetectionTags(BaseModel):
|
|
|
238
288
|
return matched_tests + [
|
|
239
289
|
AtomicTest.AtomicTestWhenTestIsMissing(test) for test in missing_tests
|
|
240
290
|
]
|
|
291
|
+
return matched_tests + [
|
|
292
|
+
AtomicTest.AtomicTestWhenTestIsMissing(test) for test in missing_tests
|
|
293
|
+
]
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import List
|
|
4
|
-
from enum import StrEnum
|
|
2
|
+
|
|
5
3
|
import datetime
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator
|
|
8
|
+
|
|
6
9
|
from contentctl.objects.annotated_types import MITRE_ATTACK_ID_TYPE
|
|
7
10
|
|
|
8
11
|
|
|
@@ -84,6 +87,16 @@ class MitreAttackGroup(BaseModel):
|
|
|
84
87
|
return []
|
|
85
88
|
return contributors
|
|
86
89
|
|
|
90
|
+
def __lt__(self, other: MitreAttackGroup) -> bool:
|
|
91
|
+
if not isinstance(object, MitreAttackGroup):
|
|
92
|
+
raise Exception(
|
|
93
|
+
f"Cannot compare object of type MitreAttackGroup to object of type [{type(object).__name__}]"
|
|
94
|
+
)
|
|
95
|
+
return self.group < other.group
|
|
96
|
+
|
|
97
|
+
def __hash__(self) -> int:
|
|
98
|
+
return hash(self.group)
|
|
99
|
+
|
|
87
100
|
|
|
88
101
|
class MitreAttackEnrichment(BaseModel):
|
|
89
102
|
ConfigDict(extra="forbid")
|
contentctl/objects/story_tags.py
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from pydantic import BaseModel, Field, model_serializer, ConfigDict
|
|
3
|
-
from typing import List, Set, Optional
|
|
4
2
|
|
|
5
3
|
from enum import Enum
|
|
4
|
+
from typing import List, Optional, Set
|
|
6
5
|
|
|
7
|
-
from
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, model_serializer
|
|
7
|
+
|
|
8
|
+
from contentctl.objects.annotated_types import CVE_TYPE, MITRE_ATTACK_ID_TYPE
|
|
8
9
|
from contentctl.objects.enums import (
|
|
9
|
-
StoryCategory,
|
|
10
10
|
DataModel,
|
|
11
11
|
KillChainPhase,
|
|
12
12
|
SecurityContentProductName,
|
|
13
|
+
StoryCategory,
|
|
13
14
|
)
|
|
14
|
-
from contentctl.objects.
|
|
15
|
+
from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class StoryUseCase(str, Enum):
|
|
@@ -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=
|
|
17
|
+
contentctl/actions/inspect.py,sha256=zFNbDXY7Bi1xTBHirNyHpH1-2A1n3rsOsRvu8E0xUao,19375
|
|
18
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
|
|
@@ -35,17 +35,17 @@ contentctl/input/yml_reader.py,sha256=ymmAqsWsf9Oj56waDOhCh_E4SomkSCmu4dAx7iURFt
|
|
|
35
35
|
contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=9uUb3BkH9d5vc9qn7RURhki9HZZJSGBTYfnqMImsiY4,43814
|
|
36
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
|
-
contentctl/objects/annotated_types.py,sha256=
|
|
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
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=
|
|
44
|
+
contentctl/objects/config.py,sha256=3l8tFVwrBDpAnS7aBgj6to0Kc8_s4bxuZY5Bm5vel8k,48605
|
|
45
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
|
|
@@ -56,7 +56,7 @@ contentctl/objects/deployment_slack.py,sha256=pC8-BB4qOD5fUqUi7Oj2Tre7-kKVqW2xEv
|
|
|
56
56
|
contentctl/objects/detection.py,sha256=GKjjhnwyFxm7139dOlPJ4c3vIW0675-NLCPWxEB5m-c,631
|
|
57
57
|
contentctl/objects/detection_metadata.py,sha256=JMz8rtcn5HfeEoaAx34kw2wXa35qsRIap_mXoY0Vbss,2237
|
|
58
58
|
contentctl/objects/detection_stanza.py,sha256=-BRQNib5NNhY7Z2fILS5xkpjNkGSLF7qBciTmgOgLV8,3112
|
|
59
|
-
contentctl/objects/detection_tags.py,sha256=
|
|
59
|
+
contentctl/objects/detection_tags.py,sha256=j92t4TWlNNVdFi4_DoHvEyvJuURlBp5_o1xv2w2pAVk,10699
|
|
60
60
|
contentctl/objects/drilldown.py,sha256=Vinw6UYlOl0YzoRA_0oBCfHA5Gvgu5p-rEsfBIgMCdI,4186
|
|
61
61
|
contentctl/objects/enums.py,sha256=HdaSQgEQ_T38BIlVYk1xdqMm05YyhQb0720nzBorXQw,13554
|
|
62
62
|
contentctl/objects/errors.py,sha256=xX_FDUaJbJiOWgjgrzjtYW5QsD41UZ2KWqH-yGkHaCU,5554
|
|
@@ -68,7 +68,7 @@ contentctl/objects/lookup.py,sha256=mzOPhMDyoNZKLAj8zf6Wg6i9FJKMu3qHWinATtH75I8,
|
|
|
68
68
|
contentctl/objects/macro.py,sha256=usbxyOPIRIJoDmvawfP2DxtFNf71GaDwffxiZsRkP5A,3594
|
|
69
69
|
contentctl/objects/manual_test.py,sha256=cx_XAtQ8VG8Ui_F553Xnut75vFEOtRwm1dDIIWNpOaM,952
|
|
70
70
|
contentctl/objects/manual_test_result.py,sha256=FyCVVf-f1DKs-qBkM4tbKfY6mkrW25NcIEBqyaDC2rE,156
|
|
71
|
-
contentctl/objects/mitre_attack_enrichment.py,sha256=
|
|
71
|
+
contentctl/objects/mitre_attack_enrichment.py,sha256=PCakRksW5qrTENIZ7JirEZplE9xpmvSvX2GKv7N8j_k,3683
|
|
72
72
|
contentctl/objects/notable_action.py,sha256=sW5XlpGznMHqyBmGXtXrl22hWLiCoKkfGCasGtK3rGo,1607
|
|
73
73
|
contentctl/objects/notable_event.py,sha256=2aOtmfnsdInTtN_fHAGIKmBTBritjHbS_Nc-pqL-GbY,689
|
|
74
74
|
contentctl/objects/playbook.py,sha256=mgYbWsD3OW86u11MbIFKvmyFueSoMJ1WBJm_rNrFvAo,2425
|
|
@@ -80,7 +80,7 @@ contentctl/objects/risk_object.py,sha256=5iUKW_UwQLjjLWiD_vlE78uwH9bkaMNCHRNmKM2
|
|
|
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
82
|
contentctl/objects/story.py,sha256=jalNgK4yYGRr_yVkzuO_YHIrPhpWHF0Q79PkTJhcLzY,5048
|
|
83
|
-
contentctl/objects/story_tags.py,sha256=
|
|
83
|
+
contentctl/objects/story_tags.py,sha256=IYumFuBF2Bt7HtW4lBfCRo2EUpjMYlnNjpx24jBErs4,2365
|
|
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
|
|
86
86
|
contentctl/objects/threat_object.py,sha256=CB3igcmiq06lqnEh7h-btxFrBfgZbHaA9p8kFDKY6lQ,712
|
|
@@ -155,14 +155,14 @@ contentctl/templates/deployments/escu_default_configuration_hunting.yml,sha256=h
|
|
|
155
155
|
contentctl/templates/deployments/escu_default_configuration_ttp.yml,sha256=1D-pvzaH1v3_yCZXaY6njmdvV4S2_Ak8uzzCOsnj9XY,548
|
|
156
156
|
contentctl/templates/detections/application/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
157
157
|
contentctl/templates/detections/cloud/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
158
|
-
contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml,sha256=
|
|
158
|
+
contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml,sha256=R8D8X_C_KzJ7_b5E5AU1FnEsi7wCJzesvMz-gUqyRng,3865
|
|
159
159
|
contentctl/templates/detections/network/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
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
163
|
contentctl/templates/stories/cobalt_strike.yml,sha256=uj8idtDNOAIqpZ9p8usQg6mop1CQkJ5TlB4Q7CJdTIE,3082
|
|
164
|
-
contentctl-5.0.
|
|
165
|
-
contentctl-5.0.
|
|
166
|
-
contentctl-5.0.
|
|
167
|
-
contentctl-5.0.
|
|
168
|
-
contentctl-5.0.
|
|
164
|
+
contentctl-5.0.3.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
|
|
165
|
+
contentctl-5.0.3.dist-info/METADATA,sha256=KUmcwcjNRAf0tyt2jpUWcjTBJf07usgeBsHmNShVHIo,21539
|
|
166
|
+
contentctl-5.0.3.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
167
|
+
contentctl-5.0.3.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
|
|
168
|
+
contentctl-5.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|