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..d6a60472 100644 --- a/contentctl/objects/alert_action.py +++ b/contentctl/objects/alert_action.py @@ -23,7 +23,7 @@ def serialize_model(self): model = {} if self.email is not None: - raise Exception("Email not implemented") + 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..c9e3ecd7 --- /dev/null +++ b/contentctl/objects/email.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from abc import ABC + +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 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 %}