Skip to content

Commit 9ba9300

Browse files
committed
Add the possibility
to automatically create drilldowns. We will likely remove this, but let's keep it now for purposes of discussion.
1 parent 72e3354 commit 9ba9300

File tree

4 files changed

+46
-7
lines changed

4 files changed

+46
-7
lines changed

contentctl/contentctl.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ def main():
193193
t.__dict__.update(config.__dict__)
194194
init_func(t)
195195
elif type(config) == validate:
196-
validate_func(config)
196+
v=validate_func(config)
197+
import code
198+
code.interact(local=
199+
locals())
197200
elif type(config) == report:
198201
report_func(config)
199202
elif type(config) == build:

contentctl/objects/abstract_security_content_objects/detection_abstract.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from contentctl.objects.integration_test import IntegrationTest
3636
from contentctl.objects.data_source import DataSource
3737
from contentctl.objects.base_test_result import TestResultStatus
38-
38+
from contentctl.objects.drilldown import Drilldown
3939
# from contentctl.objects.playbook import Playbook
4040
from contentctl.objects.enums import ProvidingTechnology
4141
from contentctl.enrichments.cve_enrichment import CveEnrichmentObj
@@ -73,6 +73,7 @@ class Detection_Abstract(SecurityContentObject):
7373
test_groups: Union[list[TestGroup], None] = Field(None, validate_default=True)
7474

7575
data_source_objects: list[DataSource] = []
76+
drilldown_searches: list[Drilldown] = Field(default=[], description="A list of Drilldowns that should be included with this search")
7677

7778
@field_validator("search", mode="before")
7879
@classmethod
@@ -525,6 +526,15 @@ def model_post_init(self, __context: Any) -> None:
525526
# Derive TestGroups and IntegrationTests, adjust for ManualTests, skip as needed
526527
self.adjust_tests_and_groups()
527528

529+
#Add the default drilldown
530+
#self.drilldown_searches.append(Drilldown.constructDrilldownFromDetection(self))
531+
532+
# Update the search fields with the original search, if required
533+
for drilldown in self.drilldown_searches:
534+
drilldown.perform_search_substitutions(self)
535+
print("adding default drilldown?")
536+
self.drilldown_searches.append(Drilldown.constructDrilldownFromDetection(self))
537+
528538
@field_validator('lookups', mode="before")
529539
@classmethod
530540
def getDetectionLookups(cls, v:list[str], info:ValidationInfo) -> list[Lookup]:

contentctl/objects/detection_tags.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
SecurityContentProductName
3434
)
3535
from contentctl.objects.atomic import AtomicTest
36-
from contentctl.objects.drilldown import Drilldown
3736
from contentctl.objects.annotated_types import MITRE_ATTACK_ID_TYPE, CVE_TYPE
3837

3938
# TODO (#266): disable the use_enum_values configuration
@@ -113,7 +112,6 @@ def cis20(self) -> list[Cis18Value]:
113112

114113
# TODO (#268): Validate manual_test has length > 0 if not None
115114
manual_test: Optional[str] = None
116-
drilldown: Drilldown | None = None
117115

118116
# The following validator is temporarily disabled pending further discussions
119117
# @validator('message')

contentctl/objects/drilldown.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
1+
from __future__ import annotations
12
from pydantic import BaseModel, Field
3+
from typing import TYPE_CHECKING
4+
if TYPE_CHECKING:
5+
from contentctl.objects.detection import Detection
6+
from contentctl.objects.enums import AnalyticsType
7+
SEARCH_PLACEHOLDER = "%original_detection_search%"
8+
29
class Drilldown(BaseModel):
3-
name: str = Field(...,min_length=5)
10+
name: str = Field(..., description="The name of the drilldown search", min_length=5)
411
search: str = Field(..., description="The text of a drilldown search. This must be valid SPL.", min_length=1)
5-
earliest_offset:str = "$info_min_time$"
6-
latest_offset:str = "$info_max_time$"
12+
earliest_offset:str = Field(default="$info_min_time$", description="Earliest offset time for the drilldown search", min_length= 1)
13+
latest_offset:str = Field(default="$info_max_time$", description="Latest offset time for the driolldown search", min_length= 1)
14+
15+
@classmethod
16+
def constructDrilldownFromDetection(cls, detection: Detection) -> Drilldown:
17+
if len([f"${o.name}$" for o in detection.tags.observable if o.role[0] == "Victim"]) == 0 and detection.type != AnalyticsType.Hunting:
18+
print("no victim!")
19+
# print(detection.tags.observable)
20+
# print(detection.file_path)
21+
name_field = "View the detection results for " + ' and ' + ''.join([f"${o.name}$" for o in detection.tags.observable if o.type[0] == "Victim"])
22+
search_field = f"{detection.search} | search " + ' '.join([f"o.name = ${o.name}$" for o in detection.tags.observable])
23+
return cls(name=name_field, search=search_field)
24+
25+
26+
def perform_search_substitutions(self, detection:Detection)->None:
27+
if (self.search.count("%") % 2) or (self.search.count("$") % 2):
28+
print("\n\nWarning - a non-even number of '%' or '$' characters were found in the\n"
29+
f"drilldown search '{self.search}' for Detection {detection.file_path}.\n"
30+
"If this was intentional, then please ignore this warning.\n")
31+
self.search = self.search.replace(SEARCH_PLACEHOLDER, detection.search)
32+
33+
34+

0 commit comments

Comments
 (0)