From 07a92c11a453a9e32bb8c7b1e1be8589366a99d6 Mon Sep 17 00:00:00 2001 From: pyth0n1c Date: Tue, 4 Feb 2025 17:15:26 -0800 Subject: [PATCH] Initial support for vetter validation and uniqueness enforcement for groups and mitre tactics. The errors still are not high quality and will take some time to fix. --- contentctl/objects/annotated_types.py | 7 ++- contentctl/objects/detection_tags.py | 6 +++ contentctl/objects/mitre_attack_enrichment.py | 47 +++++++++++++++++-- contentctl/objects/story_tags.py | 11 +++-- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/contentctl/objects/annotated_types.py b/contentctl/objects/annotated_types.py index f1291af5..1b81f773 100644 --- a/contentctl/objects/annotated_types.py +++ b/contentctl/objects/annotated_types.py @@ -1,6 +1,9 @@ -from pydantic import Field from typing import Annotated +from pydantic import Field + CVE_TYPE = Annotated[str, Field(pattern=r"^CVE-[1|2]\d{3}-\d+$")] -MITRE_ATTACK_ID_TYPE = Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})?$")] +MITRE_ATTACK_ID_TYPE_PARENT = Annotated[str, Field(pattern=r"^T\d{4}$")] +MITRE_ATTACK_ID_TYPE_SUBTYPE = Annotated[str, Field(pattern=r"^T\d{4}(.\d{3})$")] +MITRE_ATTACK_ID_TYPE = MITRE_ATTACK_ID_TYPE_PARENT | MITRE_ATTACK_ID_TYPE_SUBTYPE APPID_TYPE = Annotated[str, Field(pattern="^[a-zA-Z0-9_-]+$")] diff --git a/contentctl/objects/detection_tags.py b/contentctl/objects/detection_tags.py index cfac4726..ceaa6974 100644 --- a/contentctl/objects/detection_tags.py +++ b/contentctl/objects/detection_tags.py @@ -135,6 +135,12 @@ def addAttackEnrichment(self, info: ValidationInfo): if len(missing_tactics) > 0: raise ValueError(f"Missing Mitre Attack IDs. {missing_tactics} not found.") else: + try: + MitreAttackEnrichment.checkParentTypeNotDefinedWhenSubtypeDefined( + mitre_enrichments + ) + except Exception as e: + raise ValueError(e) self.mitre_attack_enrichments = mitre_enrichments return self diff --git a/contentctl/objects/mitre_attack_enrichment.py b/contentctl/objects/mitre_attack_enrichment.py index 082ad41f..b77fbb24 100644 --- a/contentctl/objects/mitre_attack_enrichment.py +++ b/contentctl/objects/mitre_attack_enrichment.py @@ -108,10 +108,47 @@ def __hash__(self) -> int: return id(self) @staticmethod - def getUniqueGroups(groups: list[MitreAttackGroup]) -> list[MitreAttackGroup]: + def getUniqueGroups( + enrichments: list[MitreAttackEnrichment], + ) -> list[MitreAttackGroup]: group_set: set[MitreAttackGroup] = set() - for group in groups: - g = set(group) - group_set.update(g) - + for enrichment in enrichments: + group_set.update(set(enrichment.mitre_attack_group_objects)) return sorted(group_set) + + @staticmethod + def checkParentTypeNotDefinedWhenSubtypeDefined( + enrichments: list[MitreAttackEnrichment], + ) -> None: + # Get all the mitre_attack_ids + mitre_attack_ids = [enrichment.mitre_attack_id for enrichment in enrichments] + # Split these into two groups - one that just has parents and one which has subtypes as well + mitre_attack_id_parents = [ + mitre_attack_id + for mitre_attack_id in mitre_attack_ids + if "." not in mitre_attack_id + ] + mitre_attack_id_subtypes = [ + mitre_attack_id + for mitre_attack_id in mitre_attack_ids + if "." in mitre_attack_id + ] + + subtype_and_parent_exist_exceptions: list[Exception] = [] + + for mitre_attack_id in mitre_attack_id_subtypes: + parent_id = mitre_attack_id.split(".")[0] + if parent_id in mitre_attack_id_parents: + subtype_and_parent_exist_exceptions.append( + Exception( + f"Overlapping parent and subtype tactic: [Parent: '{parent_id}', Subtype: '{mitre_attack_id}']" + ) + ) + if len(subtype_and_parent_exist_exceptions): + raise ExceptionGroup( + "Error: both MITRE Attack ID Subtype and Parent are defined. " + "A parent tactic OR a subtype tactic may be defined, but not both", + subtype_and_parent_exist_exceptions, + ) + + return None diff --git a/contentctl/objects/story_tags.py b/contentctl/objects/story_tags.py index e611c596..1abf30f3 100644 --- a/contentctl/objects/story_tags.py +++ b/contentctl/objects/story_tags.py @@ -1,17 +1,18 @@ from __future__ import annotations -from pydantic import BaseModel, Field, model_serializer, ConfigDict -from typing import List, Set, Optional from enum import Enum +from typing import List, Optional, Set -from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment +from pydantic import BaseModel, ConfigDict, Field, model_serializer + +from contentctl.objects.annotated_types import CVE_TYPE, MITRE_ATTACK_ID_TYPE from contentctl.objects.enums import ( - StoryCategory, DataModel, KillChainPhase, SecurityContentProductName, + StoryCategory, ) -from contentctl.objects.annotated_types import CVE_TYPE, MITRE_ATTACK_ID_TYPE +from contentctl.objects.mitre_attack_enrichment import MitreAttackEnrichment class StoryUseCase(str, Enum):