Skip to content

Commit 5752f49

Browse files
authored
Merge pull request #58 from splunk/merge_changes
Bring together the latest improvements
2 parents b92d758 + 00f9219 commit 5752f49

File tree

97 files changed

+3392
-2030
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+3392
-2030
lines changed

.DS_Store

6 KB
Binary file not shown.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ dist/*
66
apps*
77
test_results*
88
attack_data*
9-
9+
security_content/
1010

1111
# Byte-compiled / optimized / DLL files
1212
__pycache__/

contentctl/actions/convert.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
import sys
3+
import shutil
4+
import os
5+
6+
from dataclasses import dataclass
7+
8+
from contentctl.input.sigma_converter import *
9+
from contentctl.output.yml_output import YmlOutput
10+
11+
@dataclass(frozen=True)
12+
class ConvertInputDto:
13+
sigma_converter_input_dto: SigmaConverterInputDto
14+
output_path : str
15+
16+
17+
class Convert:
18+
19+
def execute(self, input_dto: ConvertInputDto) -> None:
20+
sigma_converter_output_dto = SigmaConverterOutputDto([])
21+
sigma_converter = SigmaConverter(sigma_converter_output_dto)
22+
sigma_converter.execute(input_dto.sigma_converter_input_dto)
23+
24+
yml_output = YmlOutput()
25+
yml_output.writeDetections(sigma_converter_output_dto.detections, input_dto.output_path)

contentctl/actions/detection_testing/DetectionTestingManager.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ def sigint_handler(signum, frame):
8989
signal.signal(signal.SIGINT, sigint_handler)
9090

9191
with concurrent.futures.ThreadPoolExecutor(
92-
max_workers=self.input_dto.config.num_containers,
92+
max_workers=len(self.input_dto.config.infrastructure_config.infrastructures),
9393
) as instance_pool, concurrent.futures.ThreadPoolExecutor(
9494
max_workers=len(self.input_dto.views)
9595
) as view_runner, concurrent.futures.ThreadPoolExecutor(
96-
max_workers=self.input_dto.config.num_containers,
96+
max_workers=len(self.input_dto.config.infrastructure_config.infrastructures),
9797
) as view_shutdowner:
9898

9999
# Start all the views
@@ -151,39 +151,33 @@ def sigint_handler(signum, frame):
151151
def create_DetectionTestingInfrastructureObjects(self):
152152
import sys
153153

154-
for index in range(self.input_dto.config.num_containers):
155-
instanceConfig = deepcopy(self.input_dto.config)
156-
instanceConfig.api_port += index * 2
157-
instanceConfig.hec_port += index * 2
158-
instanceConfig.web_ui_port += index
159-
160-
instanceConfig.container_name = instanceConfig.container_name % (index,)
154+
for infrastructure in self.input_dto.config.infrastructure_config.infrastructures:
161155

162156
if (
163-
self.input_dto.config.target_infrastructure
157+
self.input_dto.config.infrastructure_config.infrastructure_type
164158
== DetectionTestingTargetInfrastructure.container
165159
):
166160

167161
self.detectionTestingInfrastructureObjects.append(
168162
DetectionTestingInfrastructureContainer(
169-
config=instanceConfig, sync_obj=self.output_dto
163+
global_config=self.input_dto.config, infrastructure=infrastructure, sync_obj=self.output_dto
170164
)
171165
)
172166

173167
elif (
174-
self.input_dto.config.target_infrastructure
168+
self.input_dto.config.infrastructure_config.infrastructure_type
175169
== DetectionTestingTargetInfrastructure.server
176170
):
177171

178172
self.detectionTestingInfrastructureObjects.append(
179173
DetectionTestingInfrastructureServer(
180-
config=instanceConfig, sync_obj=self.output_dto
174+
global_config=self.input_dto.config, infrastructure=infrastructure, sync_obj=self.output_dto
181175
)
182176
)
183177

184178
else:
185179

186180
print(
187-
f"Unsupported target infrastructure '{self.input_dto.config.target_infrastructure}'"
181+
f"Unsupported target infrastructure '{self.input_dto.config.infrastructure_config.infrastructure_type}'"
188182
)
189183
sys.exit(1)

contentctl/actions/detection_testing/GitHubService.py

Lines changed: 65 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def get_all_content(self, director: DirectorOutputDto) -> DirectorOutputDto:
4949
self.get_macros(director),
5050
self.get_lookups(director),
5151
[],
52+
[]
5253
)
5354

5455
def get_stories(self, director: DirectorOutputDto) -> list[Story]:
@@ -137,15 +138,63 @@ def get_detections_changed(self, director: DirectorOutputDto) -> list[Detection]
137138
f"Error: self.repo must be initialized before getting changed detections."
138139
)
139140
)
140-
raise (Exception("not implemented"))
141-
return []
141+
142+
differences = self.repo.git.diff("--name-status", f"origin/{self.config.version_control_config.main_branch}").split("\n")
143+
new_content = []
144+
modified_content = []
145+
deleted_content = []
146+
for difference in differences:
147+
mode, filename = difference.split("\t")
148+
if mode == "A":
149+
new_content.append(filename)
150+
elif mode == "M":
151+
modified_content.append(filename)
152+
elif mode == "D":
153+
deleted_content.append(filename)
154+
else:
155+
raise Exception(f"Unknown mode in determining differences: {difference}")
156+
157+
#Changes to detections, macros, and lookups should trigger a re-test for anything which uses them
158+
changed_lookups_list = list(filter(lambda x: x.startswith("lookups"), new_content+modified_content))
159+
changed_lookups = set()
160+
161+
#We must account for changes to the lookup yml AND for the underlying csv
162+
for lookup in changed_lookups_list:
163+
if lookup.endswith(".csv"):
164+
lookup = lookup.replace(".csv", ".yml")
165+
changed_lookups.add(lookup)
166+
167+
# At some point we should account for macros which contain other macros...
168+
changed_macros = set(filter(lambda x: x.startswith("macros"), new_content+modified_content))
169+
changed_macros_and_lookups = set([str(pathlib.Path(filename).absolute()) for filename in changed_lookups.union(changed_macros)])
170+
171+
changed_detections = set(filter(lambda x: x.startswith("detections"), new_content+modified_content))
172+
173+
#Check and see if content that has been modified uses any of the changed macros or lookups
174+
for detection in director.detections:
175+
deps = set([content.file_path for content in detection.get_content_dependencies()])
176+
if not deps.isdisjoint(changed_macros_and_lookups):
177+
changed_detections.add(detection.file_path)
178+
179+
return Detection.get_detections_from_filenames(changed_detections, director.detections)
142180

143181
def __init__(self, config: TestConfig):
144-
self.repo = None
182+
145183
self.requested_detections: list[pathlib.Path] = []
146184
self.config = config
147-
148-
if config.mode == DetectionTestingMode.selected:
185+
if config.version_control_config is not None:
186+
self.repo = git.Repo(config.version_control_config.repo_path)
187+
else:
188+
self.repo = None
189+
190+
191+
if config.mode == DetectionTestingMode.changes:
192+
if self.repo is None:
193+
raise Exception("You are using detection mode 'changes', but the app does not have a version_control_config in contentctl_test.yml.")
194+
return
195+
elif config.mode == DetectionTestingMode.all:
196+
return
197+
elif config.mode == DetectionTestingMode.selected:
149198
if config.detections_list is None or len(config.detections_list) < 1:
150199
raise (
151200
Exception(
@@ -171,63 +220,12 @@ def __init__(self, config: TestConfig):
171220
pathlib.Path(detection_file_name)
172221
for detection_file_name in config.detections_list
173222
]
174-
return
175-
176-
elif config.mode == DetectionTestingMode.changes:
177-
# Changes is ONLY possible if the app is version controlled
178-
# in a github repo. Ensure that this is the case and, if not
179-
# raise an exception
180-
raise (Exception("Mode [changes] is not yet supported."))
181-
try:
182-
repo = git.Repo(config.repo_path)
183-
except Exception as e:
184-
raise (
185-
Exception(
186-
f"Error: detection mode [{config.mode}] REQUIRES that [{config.repo_path}] is a git repository, but it is not."
187-
)
188-
)
189-
if config.main_branch == config.test_branch:
190-
raise (
191-
Exception(
192-
f"Error: test_branch [{config.test_branch}] is the same as the main_branch [{config.main_branch}]. When using mode [{config.mode}], these two branches MUST be different."
193-
)
194-
)
195-
196-
# Ensure that the test branch is checked out
197-
if self.repo.active_branch.name != config.test_branch:
198-
raise (
199-
Exception(
200-
f"Error: detection mode [{config.mode}] REQUIRES that the test_branch [{config.test_branch}] be checked out at the beginning of the test, but it is not."
201-
)
202-
)
203-
204-
# Ensure that the base branch exists
205-
206-
if Utils.validate_git_branch_name(
207-
config.repo_path, "NO_URL", config.main_branch
208-
):
209-
return
210-
211-
elif config.mode == DetectionTestingMode.all:
212-
return
223+
213224
else:
214-
raise (
215-
Exception(
216-
f"Unsupported detection testing mode [{config.mode}]. Supported detection testing modes are [{DetectionTestingMode._member_names_}]"
217-
)
218-
)
219-
220-
def __init2__(self, config: TestConfig):
221-
222-
self.repo = git.Repo(config.repo_path)
223-
224-
if self.repo.active_branch.name != config.test_branch:
225-
print(
226-
f"Error - test_branch is '{config.test_branch}', but the current active branch in '{config.repo_path}' is '{self.repo.active_branch}'. Checking out the branch you specified..."
227-
)
228-
self.repo.git.checkout(config.test_branch)
229-
230-
self.config = config
225+
raise Exception(f"Unsupported detection testing mode [{config.mode}]. "\
226+
"Supported detection testing modes are [{DetectionTestingMode._member_names_}]")
227+
return
228+
231229

232230
def clone_project(self, url, project, branch):
233231
LOGGER.info(f"Clone Security Content Project")
@@ -252,7 +250,7 @@ def get_detections_to_test(
252250
]
253251
if ignore_deprecated:
254252
director.detections = [
255-
d for d in director.detections if not (d.deprecated == True)
253+
d for d in director.detections if not (d.status == "deprecated")
256254
]
257255
if ignore_ssa:
258256
director.detections = [
@@ -352,29 +350,29 @@ def get_all_modified_content(
352350
# Because we have not passed -all as a kwarg, we will have a MAX of one commit returned:
353351
# https://gitpython.readthedocs.io/en/stable/reference.html?highlight=merge_base#git.repo.base.Repo.merge_base
354352
base_commits = self.repo.merge_base(
355-
self.config.main_branch, self.config.test_branch
353+
self.config.version_control_config.main_branch, self.config.version_control_config.test_branch
356354
)
357355
if len(base_commits) == 0:
358356
raise (
359357
Exception(
360-
f"Error, main branch '{self.config.main_branch}' and test branch '{self.config.test_branch}' do not share a common ancestor"
358+
f"Error, main branch '{self.config.version_control_config.main_branch}' and test branch '{self.config.version_control_config.test_branch}' do not share a common ancestor"
361359
)
362360
)
363361
base_commit = base_commits[0]
364362
if base_commit is None:
365363
raise (
366364
Exception(
367-
f"Error, main branch '{self.config.main_branch}' and test branch '{self.config.test_branch}' common ancestor commit was 'None'"
365+
f"Error, main branch '{self.config.version_control_config.main_branch}' and test branch '{self.config.version_control_config.test_branch}' common ancestor commit was 'None'"
368366
)
369367
)
370368

371369
all_changes = base_commit.diff(
372-
self.config.test_branch, paths=[str(path) for path in paths]
370+
self.config.version_control_config.test_branch, paths=[str(path) for path in paths]
373371
)
374372

375373
# distill changed files down to the paths of added or modified files
376374
all_changes_paths = [
377-
os.path.join(self.config.repo_path, change.b_path)
375+
os.path.join(self.config.version_control_config.repo_path, change.b_path)
378376
for change in all_changes
379377
if change.change_type in ["M", "A"]
380378
]

0 commit comments

Comments
 (0)