diff --git a/bin/contentctl_project/contentctl_core/application/use_cases/initialize.py b/bin/contentctl_project/contentctl_core/application/use_cases/initialize.py index c0850bcdc0..0205cfe569 100644 --- a/bin/contentctl_project/contentctl_core/application/use_cases/initialize.py +++ b/bin/contentctl_project/contentctl_core/application/use_cases/initialize.py @@ -106,6 +106,10 @@ class Initialize: #Information that will be used for generation of a custom manifest self.app_title = args.title self.app_name = args.name + if not self.app_name.replace('-','').isalnum() and len(self.app_name.replace('-','')) > 0: + # Basic check to see if the app_name is alphanumeric (no spaces or symbols) and, after any + # - character(s) are removed it is still non-zero length + raise(Exception(f"Error - app_name {self.app_name} is not valid. Name must be alphanumeric (no symbols or spaces). The only allowed special character is -.")) self.app_version = args.version self.app_description = args.description self.app_author_name = args.author_name @@ -171,7 +175,25 @@ class Initialize: for fname in ["savedsearches_investigations.j2", "savedsearches_detections.j2", "analyticstories_investigations.j2", "analyticstories_detections.j2", "savedsearches_baselines.j2"]: full_path = os.path.join(filename_root, fname) self.simple_replace_line(full_path, original, updated) - #Generate directories? + + raw ='''.{app_name}''' + original = raw.format(app_name="ESCU".lower()) # + updated = raw.format(app_name=self.app_name.lower()) + filename_root = os.path.join(self.path,"bin/contentctl_project/contentctl_infrastructure/adapter/templates/") + for fname in ["savedsearches_investigations.j2", "savedsearches_detections.j2", "savedsearches_baselines.j2"]: + full_path = os.path.join(filename_root, fname) + self.simple_replace_line(full_path, original, updated) + + + raw ='''.{app_name}.''' + original = raw.format(app_name="ESCU".lower()) # + updated = raw.format(app_name=self.app_name.lower()) + filename_root = os.path.join(self.path,f"dist/{self.app_name}/default/data/ui/views/") + for fname in ["escu_summary.xml"]: + full_path = os.path.join(filename_root, fname) + self.simple_replace_line(full_path, original, updated) + + def generate_content_version_file(self): new_content_version = CONTENT_VERSION_FILE.format(version=self.app_version) diff --git a/bin/contentctl_project/contentctl_infrastructure/builder/security_content_story_builder.py b/bin/contentctl_project/contentctl_infrastructure/builder/security_content_story_builder.py index 670bd3a9fa..f6a35f1c58 100644 --- a/bin/contentctl_project/contentctl_infrastructure/builder/security_content_story_builder.py +++ b/bin/contentctl_project/contentctl_infrastructure/builder/security_content_story_builder.py @@ -1,8 +1,11 @@ import re import sys +import json +import pathlib +import os from pydantic import ValidationError - +from typing import Union from bin.contentctl_project.contentctl_core.application.builder.story_builder import StoryBuilder from bin.contentctl_project.contentctl_core.domain.entities.story import Story from bin.contentctl_project.contentctl_core.domain.entities.enums.enums import SecurityContentType @@ -13,9 +16,33 @@ class SecurityContentStoryBuilder(StoryBuilder): story: Story check_references: bool - def __init__(self, check_references: bool = False): + def __init__(self, output_path:Union[str,None]=None, check_references: bool = False): self.check_references = check_references + self.app_name = self.get_app_name_from_manifest(output_path) + def get_app_name_from_manifest(self, output_path:Union[str,None])->str: + if output_path is None: + return "ESCU" + + try: + manifest_path = pathlib.Path(os.path.join(output_path, "app.manifest")) + except Exception as e: + raise(Exception(f"Failed to convert string {output_path} to path: {str(e)}")) + try: + with open(manifest_path, "r") as manifestFile: + manifest_obj = json.load(manifestFile) + except Exception as e: + raise(Exception(f"Failed to open manifest at path {manifest_path}: {str(e)}")) + + try: + app_name_from_manifest = manifest_obj['info']['id']['name'] + #Minor fix to shorten the name of ESCU detections and match with everything else + if app_name_from_manifest == "DA-ESS-ContentUpdate": + app_name_from_manifest = "ESCU" + return app_name_from_manifest + except Exception as e: + raise(Exception(f"Manifest file {manifest_path} missing nested object ['info']['id']['name']: {str(e)}")) + def setObject(self, path: str) -> None: yml_dict = YmlReader.load_file(path) yml_dict["tags"]["name"] = yml_dict["name"] @@ -46,7 +73,7 @@ class SecurityContentStoryBuilder(StoryBuilder): if detection: for detection_analytic_story in detection.tags.analytic_story: if detection_analytic_story == self.story.name: - matched_detection_names.append(str('ESCU - ' + detection.name + ' - Rule')) + matched_detection_names.append(str(f'{self.app_name} - ' + detection.name + ' - Rule')) # SSE-638: detections object should at least contain the name attribute. # We also need a minimal set of the following attributes to satisfy docgen (doc_stories.j2): # name, source, type, tags.mitre_attack_enrichments.mitre_attack_technique @@ -84,7 +111,7 @@ class SecurityContentStoryBuilder(StoryBuilder): for baseline in baselines: for baseline_analytic_story in baseline.tags.analytic_story: if baseline_analytic_story == self.story.name: - matched_baseline_names.append(str('ESCU - ' + baseline.name)) + matched_baseline_names.append(str(f'{self.app_name} - ' + baseline.name)) self.story.baseline_names = matched_baseline_names @@ -94,7 +121,7 @@ class SecurityContentStoryBuilder(StoryBuilder): for investigation in investigations: for investigation_analytic_story in investigation.tags.analytic_story: if investigation_analytic_story == self.story.name: - matched_investigation_names.append(str('ESCU - ' + investigation.name + ' - Response Task')) + matched_investigation_names.append(str(f'{self.app_name} - ' + investigation.name + ' - Response Task')) matched_investigations.append(investigation) self.story.investigation_names = matched_investigation_names diff --git a/contentctl.py b/contentctl.py index b0ca086944..c82f3c1833 100644 --- a/contentctl.py +++ b/contentctl.py @@ -110,11 +110,12 @@ def generate(args) -> None: factory_input_dto = None ba_factory_input_dto = None if args.product in ["ESCU", "API"]: + factory_input_dto = FactoryInputDto( os.path.abspath(args.path), SecurityContentBasicBuilder(), SecurityContentDetectionBuilder(force_cached_or_offline=args.cached_and_offline, skip_enrichment=args.skip_enrichment), - SecurityContentStoryBuilder(), + SecurityContentStoryBuilder(output_path=args.output), SecurityContentBaselineBuilder(), SecurityContentInvestigationBuilder(), SecurityContentPlaybookBuilder(input_path=args.path),