Skip to content

Commit 5226073

Browse files
committed
Merge branch 'main' into enable_acs_deploy
2 parents 8a9d8ec + 5488ca6 commit 5226073

Some content is hidden

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

53 files changed

+1797
-1021
lines changed

.github/workflows/test_against_escu.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ jobs:
5353
poetry install --no-interaction
5454
5555
56-
- name: Clone the AtomicRedTeam Repo (for extended validation)
56+
- name: Clone the AtomicRedTeam Repo and the Mitre/CTI repos for testing enrichments
5757
run: |
5858
cd security_content
59-
git clone --depth 1 https://github.com/redcanaryco/atomic-red-team
59+
git clone --single-branch https://github.com/redcanaryco/atomic-red-team external_repos/atomic-red-team
60+
git clone --single-branch https://github.com/mitre/cti external_repos/cti
6061
6162
6263
# We do not separately run validate and build

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ This section is under active development. It will allow you to a [MITRE Map](ht
134134
Choose TYPE {detection, story} to create new content for the Content Pack. The tool will interactively ask a series of questions required for generating a basic piece of content and automatically add it to the Content Pack.
135135

136136
### contentctl inspect
137-
This section is under development. It will enable the user to perform an appinspect of the content pack in preparation for deployment onto a Splunk Instance or via Splunk Cloud.
137+
This section is under development. The inspect action performs a number of post-build validations. Primarily, it will enable the user to perform an appinspect of the content pack in preparation for deployment onto a Splunk Instance or via Splunk Cloud. It also compares detections in the new build against a prior build, confirming that any changed detections have had their versions incremented (this comparison happens at the savedsearch.conf level, which is why it must happen after the build). Please also note that new versions of contentctl may result in the generation of different savedsearches.conf files without any content changes in YML (new keys at the .conf level which will necessitate bumping of the version in the YML file).
138138

139139
### contentctl deploy
140140
The reason to build content is so that it can be deployed to your environment. However, deploying content to multiple servers and different types of infrastructure can be tricky and time-consuming. contentctl makes this easy by supporting a number of different deployment mechanisms. Deployment targets can be defined in [contentctl.yml](/contentctl/templates/contentctl_default.yml).

contentctl/actions/build.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
5050
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.investigations, SecurityContentType.investigations))
5151
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.lookups, SecurityContentType.lookups))
5252
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.macros, SecurityContentType.macros))
53+
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.dashboards, SecurityContentType.dashboards))
5354
updated_conf_files.update(conf_output.writeMiscellaneousAppFiles())
5455

56+
57+
5558
#Ensure that the conf file we just generated/update is syntactically valid
5659
for conf_file in updated_conf_files:
5760
ConfWriter.validateConfFile(conf_file)

contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,17 +269,25 @@ def configure_imported_roles(
269269
):
270270
indexes.append(self.sync_obj.replay_index)
271271
indexes_encoded = ";".join(indexes)
272+
272273
try:
274+
# Set which roles should be configured. For Enterprise Security/Integration Testing,
275+
# we must add some extra foles.
276+
if self.global_config.enable_integration_testing:
277+
roles = imported_roles + enterprise_security_roles
278+
else:
279+
roles = imported_roles
280+
273281
self.get_conn().roles.post(
274282
self.infrastructure.splunk_app_username,
275-
imported_roles=imported_roles + enterprise_security_roles,
283+
imported_roles=roles,
276284
srchIndexesAllowed=indexes_encoded,
277285
srchIndexesDefault=self.sync_obj.replay_index,
278286
)
279287
return
280288
except Exception as e:
281289
self.pbar.write(
282-
f"Enterprise Security Roles do not exist:'{enterprise_security_roles}: {str(e)}"
290+
f"The following role(s) do not exist:'{enterprise_security_roles}: {str(e)}"
283291
)
284292

285293
self.get_conn().roles.post(
@@ -374,12 +382,6 @@ def execute(self):
374382
return
375383

376384
try:
377-
# NOTE: (THIS CODE HAS MOVED) we handle skipping entire detections differently than
378-
# we do skipping individual test cases; we skip entire detections by excluding
379-
# them to an entirely separate queue, while we skip individual test cases via the
380-
# BaseTest.skip() method, such as when we are skipping all integration tests (see
381-
# DetectionBuilder.skipIntegrationTests)
382-
# TODO: are we skipping by production status elsewhere?
383385
detection = self.sync_obj.inputQueue.pop()
384386
self.sync_obj.currentTestingQueue[self.get_name()] = detection
385387
except IndexError:

contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,17 @@ def get_docker_client(self):
4949
def check_for_teardown(self):
5050

5151
try:
52-
self.get_docker_client().containers.get(self.get_name())
52+
container: docker.models.containers.Container = self.get_docker_client().containers.get(self.get_name())
5353
except Exception as e:
5454
if self.sync_obj.terminate is not True:
5555
self.pbar.write(
5656
f"Error: could not get container [{self.get_name()}]: {str(e)}"
5757
)
5858
self.sync_obj.terminate = True
59+
else:
60+
if container.status != 'running':
61+
self.sync_obj.terminate = True
62+
self.container = None
5963

6064
if self.sync_obj.terminate:
6165
self.finish()

contentctl/actions/initialize.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import shutil
33
import os
44
import pathlib
5-
6-
from pydantic import RootModel
75
from contentctl.objects.config import test
86
from contentctl.output.yml_writer import YmlWriter
97

@@ -17,26 +15,44 @@ def execute(self, config: test) -> None:
1715

1816
YmlWriter.writeYmlFile(str(config.path/'contentctl.yml'), config.model_dump())
1917

18+
2019
#Create the following empty directories:
21-
for emptyDir in ['lookups', 'baselines', 'docs', 'reporting', 'investigations']:
20+
for emptyDir in ['lookups', 'baselines', 'data_sources', 'docs', 'reporting', 'investigations',
21+
'detections/application', 'detections/cloud', 'detections/endpoint',
22+
'detections/network', 'detections/web', 'macros', 'stories']:
2223
#Throw an error if this directory already exists
23-
(config.path/emptyDir).mkdir(exist_ok=False)
24+
(config.path/emptyDir).mkdir(exist_ok=False, parents=True)
25+
26+
# If this is not a bare config, then populate
27+
# a small amount of content into the directories
28+
if not config.bare:
29+
#copy the contents of all template directories
30+
for templateDir, targetDir in [
31+
('../templates/detections/', 'detections'),
32+
('../templates/data_sources/', 'data_sources'),
33+
('../templates/macros/', 'macros'),
34+
('../templates/stories/', 'stories'),
35+
]:
36+
source_directory = pathlib.Path(os.path.dirname(__file__))/templateDir
37+
target_directory = config.path/targetDir
38+
39+
# Do not throw an exception if the directory exists. In fact, it was
40+
# created above when the structure of the app was created.
41+
shutil.copytree(source_directory, target_directory, dirs_exist_ok=True)
2442

25-
26-
#copy the contents of all template directories
43+
# The contents of app_template must ALWAYS be copied because it contains
44+
# several special files.
45+
# For now, we also copy the deployments because the ability to create custom
46+
# deployment files is limited with built-in functionality.
2747
for templateDir, targetDir in [
2848
('../templates/app_template/', 'app_template'),
29-
('../templates/deployments/', 'deployments'),
30-
('../templates/detections/', 'detections'),
31-
('../templates/data_sources/', 'data_sources'),
32-
('../templates/macros/','macros'),
33-
('../templates/stories/', 'stories'),
49+
('../templates/deployments/', 'deployments')
3450
]:
3551
source_directory = pathlib.Path(os.path.dirname(__file__))/templateDir
3652
target_directory = config.path/targetDir
3753
#Throw an exception if the target exists
3854
shutil.copytree(source_directory, target_directory, dirs_exist_ok=False)
39-
55+
4056
# Create a README.md file. Note that this is the README.md for the repository, not the
4157
# one which will actually be packaged into the app. That is located in the app_template folder.
4258
shutil.copyfile(pathlib.Path(os.path.dirname(__file__))/'../templates/README.md','README.md')

0 commit comments

Comments
 (0)