From 7a3c91fa742254260f4af1c7605549c4d3e485c9 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 14:03:11 -0500 Subject: [PATCH 01/10] fleshing out init dirs --- contentctl/templates/detections/application/.gitkeep | 0 contentctl/templates/detections/cloud/.gitkeep | 0 .../detections/{ => endpoint}/anomalous_usage_of_7zip.yml | 0 contentctl/templates/detections/network/.gitkeep | 0 contentctl/templates/detections/web/.gitkeep | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 contentctl/templates/detections/application/.gitkeep create mode 100644 contentctl/templates/detections/cloud/.gitkeep rename contentctl/templates/detections/{ => endpoint}/anomalous_usage_of_7zip.yml (100%) create mode 100644 contentctl/templates/detections/network/.gitkeep create mode 100644 contentctl/templates/detections/web/.gitkeep diff --git a/contentctl/templates/detections/application/.gitkeep b/contentctl/templates/detections/application/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/contentctl/templates/detections/cloud/.gitkeep b/contentctl/templates/detections/cloud/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/contentctl/templates/detections/anomalous_usage_of_7zip.yml b/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml similarity index 100% rename from contentctl/templates/detections/anomalous_usage_of_7zip.yml rename to contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml diff --git a/contentctl/templates/detections/network/.gitkeep b/contentctl/templates/detections/network/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/contentctl/templates/detections/web/.gitkeep b/contentctl/templates/detections/web/.gitkeep new file mode 100644 index 00000000..e69de29b From fd80a669a9457f8c99e3c539a75fc6cef80f9d9e Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 14:13:32 -0500 Subject: [PATCH 02/10] grab the right field --- contentctl/actions/new_content.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contentctl/actions/new_content.py b/contentctl/actions/new_content.py index 71e597c2..1c0b8aa9 100644 --- a/contentctl/actions/new_content.py +++ b/contentctl/actions/new_content.py @@ -28,6 +28,7 @@ def buildDetection(self)->dict[str,Any]: answers['status'] = "production" #start everything as production since that's what we INTEND the content to become answers['description'] = 'UPDATE_DESCRIPTION' file_name = answers['name'].replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower() + answers['kind'] = answers['detection_kind'] answers['search'] = answers['detection_search'] + ' | `' + file_name + '_filter`' answers['how_to_implement'] = 'UPDATE_HOW_TO_IMPLEMENT' answers['known_false_positives'] = 'UPDATE_KNOWN_FALSE_POSITIVES' @@ -84,7 +85,7 @@ def buildStory(self)->dict[str,Any]: def execute(self, input_dto: new) -> None: if input_dto.type == NewContentType.detection: content_dict = self.buildDetection() - subdirectory = pathlib.Path('detections') / content_dict.get('type') + subdirectory = pathlib.Path('detections') / content_dict.get('detection_kind') elif input_dto.type == NewContentType.story: content_dict = self.buildStory() subdirectory = pathlib.Path('stories') From c1dcea89026cf7407029bbbb6b759a6eb846e4aa Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 14:25:57 -0500 Subject: [PATCH 03/10] Version bump for release --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a25db6c2..ba7a0abd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "contentctl" -version = "4.0.4" +version = "4.0.5" description = "Splunk Content Control Tool" authors = ["STRT "] license = "Apache 2.0" From f8dae07bb12d750fc7930fa7588840122b624ec7 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 14:27:25 -0500 Subject: [PATCH 04/10] only ask author once --- contentctl/input/new_content_questions.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contentctl/input/new_content_questions.py b/contentctl/input/new_content_questions.py index 007dfffb..dc450936 100644 --- a/contentctl/input/new_content_questions.py +++ b/contentctl/input/new_content_questions.py @@ -27,11 +27,6 @@ def get_questions_detection(self) -> list: 'message': 'enter author name', 'name': 'detection_author', }, - { - "type": "text", - "message": "enter author name", - "name": "detection_author", - }, { "type": "select", "message": "select a detection type", From 52ccfe75a58a1d6b303a11bfa8f6230bc042e6ae Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 14:34:11 -0500 Subject: [PATCH 05/10] Improved story creation --- contentctl/input/new_content_questions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contentctl/input/new_content_questions.py b/contentctl/input/new_content_questions.py index dc450936..a97a6636 100644 --- a/contentctl/input/new_content_questions.py +++ b/contentctl/input/new_content_questions.py @@ -130,7 +130,7 @@ def get_questions_story(self) -> list: "name": "story_author", }, { - "type": "checkbox", + "type": "select", "message": "select a category", "name": "category", "choices": [ From adc88f8d06e81bfa2c1c5d6dec15c68dd85e075a Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 14:52:06 -0500 Subject: [PATCH 06/10] Removed extraneous fields --- contentctl/actions/new_content.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/contentctl/actions/new_content.py b/contentctl/actions/new_content.py index 1c0b8aa9..0ea8cfc1 100644 --- a/contentctl/actions/new_content.py +++ b/contentctl/actions/new_content.py @@ -19,17 +19,21 @@ def buildDetection(self)->dict[str,Any]: answers = questionary.prompt(questions) answers.update(answers) answers['name'] = answers['detection_name'] + del answers['detection_name'] answers['id'] = str(uuid.uuid4()) answers['version'] = 1 answers['date'] = datetime.today().strftime('%Y-%m-%d') answers['author'] = answers['detection_author'] + del answers['detection_author'] answers['data_source'] = answers['data_source'] answers['type'] = answers['detection_type'] + del answers['detection_type'] answers['status'] = "production" #start everything as production since that's what we INTEND the content to become answers['description'] = 'UPDATE_DESCRIPTION' file_name = answers['name'].replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower() answers['kind'] = answers['detection_kind'] answers['search'] = answers['detection_search'] + ' | `' + file_name + '_filter`' + del answers['detection_search'] answers['how_to_implement'] = 'UPDATE_HOW_TO_IMPLEMENT' answers['known_false_positives'] = 'UPDATE_KNOWN_FALSE_POSITIVES' answers['references'] = ['REFERENCE'] @@ -66,32 +70,37 @@ def buildStory(self)->dict[str,Any]: questions = NewContentQuestions.get_questions_story() answers = questionary.prompt(questions) answers['name'] = answers['story_name'] + del answers['story_name'] answers['id'] = str(uuid.uuid4()) answers['version'] = 1 answers['date'] = datetime.today().strftime('%Y-%m-%d') answers['author'] = answers['story_author'] + del answers['story_author'] answers['description'] = 'UPDATE_DESCRIPTION' answers['narrative'] = 'UPDATE_NARRATIVE' answers['references'] = [] answers['tags'] = dict() answers['tags']['analytic_story'] = answers['name'] answers['tags']['category'] = answers['category'] + del answers['category'] answers['tags']['product'] = ['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud'] answers['tags']['usecase'] = answers['usecase'] + del answers['usecase'] answers['tags']['cve'] = ['UPDATE WITH CVE(S) IF APPLICABLE'] + del answers['detection_kind'] return answers def execute(self, input_dto: new) -> None: if input_dto.type == NewContentType.detection: content_dict = self.buildDetection() - subdirectory = pathlib.Path('detections') / content_dict.get('detection_kind') + subdirectory = pathlib.Path('detections') / content_dict.pop('detection_kind') elif input_dto.type == NewContentType.story: content_dict = self.buildStory() subdirectory = pathlib.Path('stories') else: raise Exception(f"Unsupported new content type: [{input_dto.type}]") - + full_output_path = input_dto.path / subdirectory / SecurityContentObject_Abstract.contentNameToFileName(content_dict.get('name')) YmlWriter.writeYmlFile(str(full_output_path), content_dict) From d854699217e817848b25a13025cf89afc39f0880 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 15:13:43 -0500 Subject: [PATCH 07/10] Change default testing data to link --- contentctl/actions/new_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contentctl/actions/new_content.py b/contentctl/actions/new_content.py index 0ea8cfc1..39a96667 100644 --- a/contentctl/actions/new_content.py +++ b/contentctl/actions/new_content.py @@ -57,7 +57,7 @@ def buildDetection(self)->dict[str,Any]: 'name': "True Positive Test", 'attack_data': [ { - 'data': "Enter URL for Dataset Here. This may also be a relative or absolute path on your local system for testing.", + 'data': "https://github.com/splunk/contentctl/wiki", "sourcetype": "UPDATE SOURCETYPE", "source": "UPDATE SOURCE" } From 578700b5c7cfc38cf69f4ec42f7fc880f812bef1 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 16:51:47 -0500 Subject: [PATCH 08/10] Accidentally a line --- contentctl/actions/new_content.py | 1 - 1 file changed, 1 deletion(-) diff --git a/contentctl/actions/new_content.py b/contentctl/actions/new_content.py index 39a96667..3bffb118 100644 --- a/contentctl/actions/new_content.py +++ b/contentctl/actions/new_content.py @@ -87,7 +87,6 @@ def buildStory(self)->dict[str,Any]: answers['tags']['usecase'] = answers['usecase'] del answers['usecase'] answers['tags']['cve'] = ['UPDATE WITH CVE(S) IF APPLICABLE'] - del answers['detection_kind'] return answers From 8fda2e20f500c2f9f09760bac9c6ab1ee282e505 Mon Sep 17 00:00:00 2001 From: ljstella Date: Tue, 11 Jun 2024 17:25:48 -0500 Subject: [PATCH 09/10] category needs checkbox to be a list, removed extra tag --- contentctl/actions/new_content.py | 1 - contentctl/input/new_content_questions.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/contentctl/actions/new_content.py b/contentctl/actions/new_content.py index 3bffb118..e8f6787c 100644 --- a/contentctl/actions/new_content.py +++ b/contentctl/actions/new_content.py @@ -80,7 +80,6 @@ def buildStory(self)->dict[str,Any]: answers['narrative'] = 'UPDATE_NARRATIVE' answers['references'] = [] answers['tags'] = dict() - answers['tags']['analytic_story'] = answers['name'] answers['tags']['category'] = answers['category'] del answers['category'] answers['tags']['product'] = ['Splunk Enterprise','Splunk Enterprise Security','Splunk Cloud'] diff --git a/contentctl/input/new_content_questions.py b/contentctl/input/new_content_questions.py index a97a6636..dc450936 100644 --- a/contentctl/input/new_content_questions.py +++ b/contentctl/input/new_content_questions.py @@ -130,7 +130,7 @@ def get_questions_story(self) -> list: "name": "story_author", }, { - "type": "select", + "type": "checkbox", "message": "select a category", "name": "category", "choices": [ From 86088c838c6acf913311964a90a6865158ffb712 Mon Sep 17 00:00:00 2001 From: ljstella Date: Wed, 12 Jun 2024 07:43:53 -0500 Subject: [PATCH 10/10] Expanded enum to match prompt, this needs more discussion long term --- contentctl/objects/story_tags.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contentctl/objects/story_tags.py b/contentctl/objects/story_tags.py index 859a8a98..0dfc0fd7 100644 --- a/contentctl/objects/story_tags.py +++ b/contentctl/objects/story_tags.py @@ -14,6 +14,8 @@ class StoryUseCase(str,Enum): APPLICATION_SECURITY = "Application Security" SECURITY_MONITORING = "Security Monitoring" ADVANCED_THREAD_DETECTION = "Advanced Threat Detection" + INSIDER_THREAT = "Insider Threat" + OTHER = "Other" class StoryTags(BaseModel): model_config = ConfigDict(extra='forbid', use_enum_values=True)