From 3bf2c2222c3aab587a014a80cb0341a23e5f2b78 Mon Sep 17 00:00:00 2001 From: 0xC0FFEEEE <119874251+0xC0FFEEEE@users.noreply.github.com> Date: Wed, 9 Apr 2025 21:47:04 +0100 Subject: [PATCH 1/4] first stab --- .../detection_abstract.py | 4 ++++ contentctl/objects/alert_action.py | 4 ++-- contentctl/objects/email.py | 11 +++++++++++ .../output/templates/savedsearches_detections.j2 | 8 ++++---- 4 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 contentctl/objects/email.py diff --git a/contentctl/objects/abstract_security_content_objects/detection_abstract.py b/contentctl/objects/abstract_security_content_objects/detection_abstract.py index 036ac5c4..66a80784 100644 --- a/contentctl/objects/abstract_security_content_objects/detection_abstract.py +++ b/contentctl/objects/abstract_security_content_objects/detection_abstract.py @@ -46,6 +46,7 @@ ) from contentctl.objects.integration_test import IntegrationTest from contentctl.objects.manual_test import ManualTest +from contentctl.objects.email import EmailObject from contentctl.objects.rba import RBAObject from contentctl.objects.security_content_object import SecurityContentObject from contentctl.objects.test_group import TestGroup @@ -66,6 +67,7 @@ class Detection_Abstract(SecurityContentObject): how_to_implement: str = Field(..., min_length=4) known_false_positives: str = Field(..., min_length=4) rba: Optional[RBAObject] = Field(default=None) + email: Optional[EmailObject] = Field(default=None) explanation: None | str = Field( default=None, exclude=True, # Don't serialize this value when dumping the object @@ -441,6 +443,8 @@ def serialize_model(self): model["tags"]["risk_score"] = self.rba.risk_score else: model["tags"]["risk_score"] = 0 + if self.email is not None: + model["email"] = self.email # Only a subset of macro fields are required: all_macros: list[dict[str, str | list[str]]] = [] diff --git a/contentctl/objects/alert_action.py b/contentctl/objects/alert_action.py index c50e9bdb..c52a273b 100644 --- a/contentctl/objects/alert_action.py +++ b/contentctl/objects/alert_action.py @@ -22,8 +22,8 @@ def serialize_model(self): # Call serializer for parent model = {} - if self.email is not None: - raise Exception("Email not implemented") + if self.email is not None and self.rba.enabled: + model["email"] = self.email if self.notable is not None: model["notable"] = self.notable diff --git a/contentctl/objects/email.py b/contentctl/objects/email.py new file mode 100644 index 00000000..52f906f0 --- /dev/null +++ b/contentctl/objects/email.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from abc import ABC + +from pydantic import BaseModel + + +class EmailObject(BaseModel, ABC): + to: str + subject: str + message: str diff --git a/contentctl/output/templates/savedsearches_detections.j2 b/contentctl/output/templates/savedsearches_detections.j2 index b75df793..87a92de0 100644 --- a/contentctl/output/templates/savedsearches_detections.j2 +++ b/contentctl/output/templates/savedsearches_detections.j2 @@ -72,11 +72,11 @@ action.notable.param.severity = {{ detection.rba.severity }} action.notable.param.severity = high {% endif %} {% endif %} -{% if detection.deployment.alert_action.email %} +{% if detection.email %} action.email = 1 -action.email.subject.alert = {{ detection.deployment.alert_action.email.subject | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }} -action.email.to = {{ detection.deployment.alert_action.email.to }} -action.email.message.alert = {{ detection.deployment.alert_action.email.message | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }} +action.email.subject.alert = {{ detection.email.subject | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }} +action.email.to = {{ detection.email.to }} +action.email.message.alert = {{ detection.email.message | custom_jinja2_enrichment_filter(detection) | escapeNewlines() }} action.email.useNSSubject = 1 {% endif %} {% if detection.deployment.alert_action.slack %} From f8c53c29af3d6d3b834afa11633beb679e475ea1 Mon Sep 17 00:00:00 2001 From: 0xC0FFEEEE <119874251+0xC0FFEEEE@users.noreply.github.com> Date: Wed, 9 Apr 2025 22:28:36 +0100 Subject: [PATCH 2/4] minor fix --- contentctl/objects/alert_action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contentctl/objects/alert_action.py b/contentctl/objects/alert_action.py index c52a273b..d6a60472 100644 --- a/contentctl/objects/alert_action.py +++ b/contentctl/objects/alert_action.py @@ -22,7 +22,7 @@ def serialize_model(self): # Call serializer for parent model = {} - if self.email is not None and self.rba.enabled: + if self.email is not None: model["email"] = self.email if self.notable is not None: From a3faba717dcc3010ce5c3e030fcd5134c0c91722 Mon Sep 17 00:00:00 2001 From: 0xC0FFEEEE <119874251+0xC0FFEEEE@users.noreply.github.com> Date: Wed, 9 Apr 2025 22:54:28 +0100 Subject: [PATCH 3/4] basic email validation --- contentctl/objects/email.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contentctl/objects/email.py b/contentctl/objects/email.py index 52f906f0..3de0352d 100644 --- a/contentctl/objects/email.py +++ b/contentctl/objects/email.py @@ -1,11 +1,20 @@ from __future__ import annotations from abc import ABC +from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, model_validator class EmailObject(BaseModel, ABC): to: str subject: str message: str + + @model_validator(mode="before") + # Validate the email address + def validate_email(cls, data: str) -> str: + if data.get("to"): + if "@" not in data.get("to"): + raise ValueError("Invalid email address") + return data From 81e2c2fd090a3a183f9194fb5d7d0b6c90f6f69d Mon Sep 17 00:00:00 2001 From: 0xC0FFEEEE <119874251+0xC0FFEEEE@users.noreply.github.com> Date: Wed, 9 Apr 2025 23:01:21 +0100 Subject: [PATCH 4/4] import cleanup --- contentctl/objects/email.py | 1 - 1 file changed, 1 deletion(-) diff --git a/contentctl/objects/email.py b/contentctl/objects/email.py index 3de0352d..c9e3ecd7 100644 --- a/contentctl/objects/email.py +++ b/contentctl/objects/email.py @@ -1,7 +1,6 @@ from __future__ import annotations from abc import ABC -from typing import Any from pydantic import BaseModel, model_validator