contentctl 4.3.3__py3-none-any.whl → 4.3.5__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/detection_testing/infrastructures/DetectionTestingInfrastructure.py +0 -6
- contentctl/actions/initialize.py +28 -12
- contentctl/actions/inspect.py +189 -91
- contentctl/actions/validate.py +3 -7
- contentctl/api.py +1 -1
- contentctl/contentctl.py +3 -0
- contentctl/enrichments/attack_enrichment.py +51 -82
- contentctl/enrichments/cve_enrichment.py +2 -2
- contentctl/helper/splunk_app.py +141 -10
- contentctl/input/director.py +5 -12
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +11 -8
- contentctl/objects/annotated_types.py +6 -0
- contentctl/objects/atomic.py +51 -77
- contentctl/objects/config.py +145 -22
- contentctl/objects/constants.py +4 -1
- contentctl/objects/correlation_search.py +35 -28
- contentctl/objects/detection_metadata.py +71 -0
- contentctl/objects/detection_stanza.py +79 -0
- contentctl/objects/detection_tags.py +11 -9
- contentctl/objects/enums.py +0 -2
- contentctl/objects/errors.py +187 -0
- contentctl/objects/mitre_attack_enrichment.py +2 -1
- contentctl/objects/risk_event.py +94 -76
- contentctl/objects/savedsearches_conf.py +196 -0
- contentctl/objects/story_tags.py +3 -3
- contentctl/output/conf_writer.py +4 -1
- contentctl/output/new_content_yml_output.py +4 -9
- {contentctl-4.3.3.dist-info → contentctl-4.3.5.dist-info}/METADATA +4 -4
- {contentctl-4.3.3.dist-info → contentctl-4.3.5.dist-info}/RECORD +32 -32
- contentctl/objects/ssa_detection.py +0 -157
- contentctl/objects/ssa_detection_tags.py +0 -138
- contentctl/objects/unit_test_old.py +0 -10
- contentctl/objects/unit_test_ssa.py +0 -31
- {contentctl-4.3.3.dist-info → contentctl-4.3.5.dist-info}/LICENSE.md +0 -0
- {contentctl-4.3.3.dist-info → contentctl-4.3.5.dist-info}/WHEEL +0 -0
- {contentctl-4.3.3.dist-info → contentctl-4.3.5.dist-info}/entry_points.txt +0 -0
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
import re
|
|
3
|
-
from typing import List
|
|
4
|
-
from pydantic import BaseModel, validator, ValidationError, model_validator, Field
|
|
5
|
-
|
|
6
|
-
from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment
|
|
7
|
-
from contentctl.objects.constants import *
|
|
8
|
-
from contentctl.objects.enums import SecurityContentProductName
|
|
9
|
-
|
|
10
|
-
class SSADetectionTags(BaseModel):
|
|
11
|
-
# detection spec
|
|
12
|
-
#name: str
|
|
13
|
-
analytic_story: list
|
|
14
|
-
asset_type: str
|
|
15
|
-
automated_detection_testing: str = None
|
|
16
|
-
cis20: list = None
|
|
17
|
-
confidence: int
|
|
18
|
-
impact: int
|
|
19
|
-
kill_chain_phases: list = None
|
|
20
|
-
message: str
|
|
21
|
-
mitre_attack_id: list = None
|
|
22
|
-
nist: list = None
|
|
23
|
-
observable: list
|
|
24
|
-
product: List[SecurityContentProductName] = Field(...,min_length=1)
|
|
25
|
-
required_fields: list
|
|
26
|
-
risk_score: int
|
|
27
|
-
security_domain: str
|
|
28
|
-
risk_severity: str = None
|
|
29
|
-
cve: list = None
|
|
30
|
-
supported_tas: list = None
|
|
31
|
-
atomic_guid: list = None
|
|
32
|
-
drilldown_search: str = None
|
|
33
|
-
manual_test: str = None
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# enrichment
|
|
37
|
-
mitre_attack_enrichments: list[MitreAttackEnrichment] = []
|
|
38
|
-
confidence_id: int = None
|
|
39
|
-
impact_id: int = None
|
|
40
|
-
context_ids: list = None
|
|
41
|
-
risk_level_id: int = None
|
|
42
|
-
risk_level: str = None
|
|
43
|
-
observable_str: str = None
|
|
44
|
-
evidence_str: str = None
|
|
45
|
-
analytics_story_str: str = None
|
|
46
|
-
kill_chain_phases_id:dict = None
|
|
47
|
-
kill_chain_phases_str:str = None
|
|
48
|
-
research_site_url: str = None
|
|
49
|
-
event_schema: str = None
|
|
50
|
-
mappings: list = None
|
|
51
|
-
annotations: dict = None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@validator('cis20')
|
|
55
|
-
def tags_cis20(cls, v, values):
|
|
56
|
-
pattern = r'^CIS ([\d|1\d|20)$' #DO NOT match leading zeroes and ensure no extra characters before or after the string
|
|
57
|
-
for value in v:
|
|
58
|
-
if not re.match(pattern, value):
|
|
59
|
-
raise ValueError(f"CIS control '{value}' is not a valid Control ('CIS 1' -> 'CIS 20'): {values['name']}")
|
|
60
|
-
return v
|
|
61
|
-
|
|
62
|
-
@validator('nist')
|
|
63
|
-
def tags_nist(cls, v, values):
|
|
64
|
-
# Sourced Courtest of NIST: https://www.nist.gov/system/files/documents/cyberframework/cybersecurity-framework-021214.pdf (Page 19)
|
|
65
|
-
IDENTIFY = [f'ID.{category}' for category in ["AM", "BE", "GV", "RA", "RM"] ]
|
|
66
|
-
PROTECT = [f'PR.{category}' for category in ["AC", "AT", "DS", "IP", "MA", "PT"]]
|
|
67
|
-
DETECT = [f'DE.{category}' for category in ["AE", "CM", "DP"] ]
|
|
68
|
-
RESPOND = [f'RS.{category}' for category in ["RP", "CO", "AN", "MI", "IM"] ]
|
|
69
|
-
RECOVER = [f'RC.{category}' for category in ["RP", "IM", "CO"] ]
|
|
70
|
-
ALL_NIST_CATEGORIES = IDENTIFY + PROTECT + DETECT + RESPOND + RECOVER
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
for value in v:
|
|
74
|
-
if not value in ALL_NIST_CATEGORIES:
|
|
75
|
-
raise ValueError(f"NIST Category '{value}' is not a valid category")
|
|
76
|
-
return v
|
|
77
|
-
|
|
78
|
-
@validator('confidence')
|
|
79
|
-
def tags_confidence(cls, v, values):
|
|
80
|
-
v = int(v)
|
|
81
|
-
if not (v > 0 and v <= 100):
|
|
82
|
-
raise ValueError('confidence score is out of range 1-100.' )
|
|
83
|
-
else:
|
|
84
|
-
return v
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@validator('impact')
|
|
88
|
-
def tags_impact(cls, v, values):
|
|
89
|
-
if not (v > 0 and v <= 100):
|
|
90
|
-
raise ValueError('impact score is out of range 1-100.')
|
|
91
|
-
else:
|
|
92
|
-
return v
|
|
93
|
-
|
|
94
|
-
@validator('kill_chain_phases')
|
|
95
|
-
def tags_kill_chain_phases(cls, v, values):
|
|
96
|
-
valid_kill_chain_phases = SES_KILL_CHAIN_MAPPINGS.keys()
|
|
97
|
-
for value in v:
|
|
98
|
-
if value not in valid_kill_chain_phases:
|
|
99
|
-
raise ValueError('kill chain phase not valid. Valid options are ' + str(valid_kill_chain_phases))
|
|
100
|
-
return v
|
|
101
|
-
|
|
102
|
-
@validator('mitre_attack_id')
|
|
103
|
-
def tags_mitre_attack_id(cls, v, values):
|
|
104
|
-
pattern = 'T[0-9]{4}'
|
|
105
|
-
for value in v:
|
|
106
|
-
if not re.match(pattern, value):
|
|
107
|
-
raise ValueError('Mitre Attack ID are not following the pattern Txxxx:' )
|
|
108
|
-
return v
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
@validator('risk_score')
|
|
113
|
-
def tags_calculate_risk_score(cls, v, values):
|
|
114
|
-
calculated_risk_score = round(values['impact'] * values['confidence'] / 100)
|
|
115
|
-
if calculated_risk_score != int(v):
|
|
116
|
-
raise ValueError(f"Risk Score must be calculated as round(confidence * impact / 100)"
|
|
117
|
-
f"\n Expected risk_score={calculated_risk_score}, found risk_score={int(v)}: {values['name']}")
|
|
118
|
-
return v
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
@model_validator(mode="after")
|
|
122
|
-
def tags_observable(self):
|
|
123
|
-
valid_roles = SES_OBSERVABLE_ROLE_MAPPING.keys()
|
|
124
|
-
valid_types = SES_OBSERVABLE_TYPE_MAPPING.keys()
|
|
125
|
-
|
|
126
|
-
for value in self.observable:
|
|
127
|
-
if value['type'] in valid_types:
|
|
128
|
-
if 'Splunk Behavioral Analytics' in self.product:
|
|
129
|
-
continue
|
|
130
|
-
|
|
131
|
-
if 'role' not in value:
|
|
132
|
-
raise ValueError('Observable role is missing')
|
|
133
|
-
for role in value['role']:
|
|
134
|
-
if role not in valid_roles:
|
|
135
|
-
raise ValueError(f'Observable role ' + role + ' not valid. Valid options are {str(valid_roles)}')
|
|
136
|
-
else:
|
|
137
|
-
raise ValueError(f'Observable type ' + value['type'] + ' not valid. Valid options are {str(valid_types)}')
|
|
138
|
-
return self
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
from typing import Optional
|
|
3
|
-
from pydantic import BaseModel, Field
|
|
4
|
-
from pydantic import Field
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class UnitTestAttackDataSSA(BaseModel):
|
|
8
|
-
file_name:Optional[str] = None
|
|
9
|
-
data: str = Field(...)
|
|
10
|
-
# TODO - should source and sourcetype should be mapped to a list
|
|
11
|
-
# of supported source and sourcetypes in a given environment?
|
|
12
|
-
source: str = Field(...)
|
|
13
|
-
|
|
14
|
-
sourcetype: Optional[str] = None
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class UnitTestSSA(BaseModel):
|
|
18
|
-
"""
|
|
19
|
-
A unit test for a detection
|
|
20
|
-
"""
|
|
21
|
-
name: str
|
|
22
|
-
|
|
23
|
-
# The attack data to be ingested for the unit test
|
|
24
|
-
attack_data: list[UnitTestAttackDataSSA] = Field(...)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|