From 16e6e94e85af7fccdc4b2576efb3039eb9cdd507 Mon Sep 17 00:00:00 2001 From: adarshtiwary Date: Wed, 26 Aug 2026 05:26:23 +0000 Subject: [PATCH 1/7] FireEye ETP - Fix missing alert details and event data in Email Alerts Connector for v2 API - Fixed FireEyeETPManager.get_alert_details to extract from root dict when "data" wrapper is absent in v2 detail API responses. - Updated Alert datamodel to parse nested v2 schema including email-header, smtp-message, malware-detected, src, dst, and occurred/attack timestamps. - Added malware and recipient event generation and accurate severity-to-priority calculation. - Improved utils_manager to handle None timezone offsets and 14-digit compact timestamp parsing. - Updated connector timestamp filtering and safe alert sorting in create_alert_info. - Updated ontology mapping rules with v2 flattened event paths. - Replaced dict[str, Any] type annotations with SingleJson from TIPCommon.types. - Bumped integration version to 11.0 and updated release notes and uv.lock. - Added comprehensive unit tests for v2 payload extractions. BUG=b/543804375 --- .../connectors/email_alerts_connector.py | 29 ++-- .../google/fire_eye_etp/core/datamodels.py | 150 ++++++++++++---- .../core/fire_eye_etp_constants.py | 1 + .../fire_eye_etp/core/fire_eye_etp_manager.py | 14 +- .../fire_eye_etp/core/fire_eye_etp_parser.py | 15 +- .../google/fire_eye_etp/core/utils_manager.py | 28 ++- .../google/fire_eye_etp/ontology_mapping.yaml | 54 +++--- .../google/fire_eye_etp/pyproject.toml | 2 +- .../google/fire_eye_etp/release_notes.yaml | 10 ++ .../fire_eye_etp/tests/test_v2_parsing.py | 162 ++++++++++++++++++ .../google/fire_eye_etp/uv.lock | 4 +- 11 files changed, 387 insertions(+), 82 deletions(-) create mode 100644 content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py diff --git a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py index da6f4099e3..fb21d5f1c0 100644 --- a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py +++ b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py @@ -23,7 +23,6 @@ from typing import TYPE_CHECKING, Any import arrow -from EnvironmentCommon import GetEnvironmentCommonFactory from soar_sdk.SiemplifyConnectors import SiemplifyConnectorExecution from soar_sdk.SiemplifyConnectorsDataModel import AlertInfo from soar_sdk.SiemplifyUtils import ( @@ -39,6 +38,11 @@ from TIPCommon.transformation import dict_to_flat from TIPCommon.utils import is_overflowed +try: + from EnvironmentCommon import GetEnvironmentCommonFactory +except ImportError: + from TIPCommon.envcommon import GetEnvironmentCommonFactory + from ..core.fire_eye_etp_constants import ( ACCEPTABLE_TIME_INTERVAL_IN_MINUTES, ALERT_ID_FIELD, @@ -54,6 +58,8 @@ from ..core.fire_eye_etp_manager import FireEyeETPConfig, FireEyeETPManager if TYPE_CHECKING: + from TIPCommon.types import SingleJson + from ..core.datamodels import Alert MIN_REQUIRED_ARGS = 2 @@ -76,9 +82,11 @@ def filter_recent_alerts( """ filtered_groups: list[list[Alert]] = [] + cutoff_time = arrow.utcnow().shift(minutes=-max_minutes_backwards).timestamp + cutoff_timestamp_ms = int((cutoff_time() if callable(cutoff_time) else cutoff_time) * 1000) for group in alert_groups: - if group[0].occurred_time_unix < arrow.utcnow().shift(minutes=-max_minutes_backwards).timestamp * 1000: + if group[0].occurred_time_unix < cutoff_timestamp_ms: filtered_groups.append(group) else: @@ -167,7 +175,8 @@ def create_alert_info( The constructed AlertInfo object. """ - sorted_alerts_group: list[Alert] = sorted(alerts_group, key=lambda alert: alert.occurred_time_unix) + valid_alerts = [a for a in alerts_group if a.timestamp] or alerts_group + sorted_alerts_group: list[Alert] = sorted(valid_alerts, key=lambda alert: alert.occurred_time_unix) alert_info: AlertInfo = AlertInfo() alert_info.display_id = str(uuid.uuid4()) @@ -181,7 +190,7 @@ def create_alert_info( alert_info.device_vendor = DEVICE_VENDOR alert_info.device_product = DEVICE_PRODUCT - events: list[dict[str, Any]] = [] + events: list[SingleJson] = [] for alert in sorted_alerts_group: events.extend(alert.events) @@ -197,8 +206,8 @@ def process_single_alert_group( siemplify: SiemplifyConnectorExecution, etp_manager: FireEyeETPManager, alert_group: list[Alert], - params: dict[str, Any], - context: dict[str, Any], + params: SingleJson, + context: SingleJson, ) -> AlertInfo | None: """Process a single alert group and return AlertInfo if successful. @@ -266,8 +275,8 @@ def process_alert_groups( siemplify: SiemplifyConnectorExecution, etp_manager: FireEyeETPManager, alert_groups: list[list[Alert]], - params: dict[str, Any], - context: dict[str, Any], + params: SingleJson, + context: SingleJson, ) -> list[AlertInfo]: """Process a list of alert groups and return created AlertInfo. @@ -342,8 +351,8 @@ def update_connector_timestamp( def run_connector_cycle( siemplify: SiemplifyConnectorExecution, - params: dict[str, Any], - context: dict[str, Any], + params: SingleJson, + context: SingleJson, ) -> tuple[list[AlertInfo], list[Alert], list[Alert]]: """Run the main connector cycle (fetch, filter, process) and return results. diff --git a/content/response_integrations/google/fire_eye_etp/core/datamodels.py b/content/response_integrations/google/fire_eye_etp/core/datamodels.py index 5ffa1e08f5..d3f9fc320c 100644 --- a/content/response_integrations/google/fire_eye_etp/core/datamodels.py +++ b/content/response_integrations/google/fire_eye_etp/core/datamodels.py @@ -17,18 +17,21 @@ from __future__ import annotations import copy -from typing import Any +from typing import TYPE_CHECKING, Any from soar_sdk.SiemplifyUtils import convert_datetime_to_unix_time from .fire_eye_etp_constants import ALERT_NAME from .utils_manager import naive_time_converted_to_aware +if TYPE_CHECKING: + from TIPCommon.types import SingleJson + class BaseModel: """Base model for inheritance.""" - def __init__(self, raw_data: dict[str, Any]) -> None: + def __init__(self, raw_data: SingleJson) -> None: """Initialize the BaseModel. Args: @@ -37,7 +40,7 @@ def __init__(self, raw_data: dict[str, Any]) -> None: """ self.raw_data = raw_data - def to_json(self) -> dict[str, Any]: + def to_json(self) -> SingleJson: """Convert the model to JSON. Returns: @@ -52,7 +55,7 @@ class Alert(BaseModel): def __init__( self, - raw_data: dict[str, Any], + raw_data: SingleJson, timezone_offset: str | None = None, ) -> None: """Initialize the Alert. @@ -63,23 +66,92 @@ def __init__( """ super().__init__(raw_data) - self.id: str | None = raw_data.get("id") + alert_sub: SingleJson = ( + raw_data.get("alert", {}) if isinstance(raw_data.get("alert"), dict) else {} + ) + email_header: SingleJson = ( + alert_sub.get("email-header") + or raw_data.get("email-header", {}) + or raw_data.get("attributes", {}).get("email", {}).get("headers", {}) + ) + smtp_message: SingleJson = ( + alert_sub.get("smtp-message") + or raw_data.get("smtp-message", {}) + or raw_data.get("attributes", {}).get("email", {}).get("smtp", {}) + ) + explanation: SingleJson = ( + alert_sub.get("explanation", {}) + or raw_data.get("attributes", {}).get("alert", {}).get("explanation", {}) + ) + malware_detected: SingleJson = ( + explanation.get("malware-detected", {}) + or explanation.get("malware_detected", {}) + ) + + self.id: str | None = ( + raw_data.get("id") + or alert_sub.get("uuid") + or raw_data.get("report_id") + or raw_data.get("attributes", {}).get("alert", {}).get("uuid") + ) self.timestamp: str | None = ( - raw_data.get("attributes", {}).get("email", {}).get("timestamp", {}).get("accepted") + alert_sub.get("occurred") + or alert_sub.get("attack-time") + or raw_data.get("alert_date") + or raw_data.get("accepted_time") + or raw_data.get("attributes", {}).get("email", {}).get("timestamp", {}).get("accepted") + or raw_data.get("attributes", {}).get("alert", {}).get("occurred") ) - self.severity: str | None = raw_data.get("attributes", {}).get("alert", {}).get("severity") - self.etp_message_id: str | None = raw_data.get("attributes", {}).get("email", {}).get("etp_message_id") - self.malwares: list[dict[str, Any]] = ( - raw_data - .get("attributes", {}) + + mitre_mappings: list[Any] = raw_data.get("mitre_mapping", []) + mitre_severity: str | None = None + if isinstance(mitre_mappings, list): + for mapping in mitre_mappings: + if isinstance(mapping, dict) and mapping.get("severity"): + mitre_severity = str(mapping.get("severity")) + break + + self.severity: str | None = ( + mitre_severity + or alert_sub.get("severity") + or raw_data.get("severity") + or raw_data.get("attributes", {}).get("alert", {}).get("severity") + ) + self.etp_message_id: str = ( + email_header.get("message-id") + or smtp_message.get("queue-id") + or raw_data.get("mta_msg_id") + or raw_data.get("attributes", {}).get("email", {}).get("etp_message_id") + or (self.id or "") + ) + legacy_malwares = ( + raw_data.get("attributes", {}) .get("alert", {}) .get("explanation", {}) .get("malware_detected", {}) .get("malware", []) ) - self.recipients: list[str] = ( + self.malwares: list[SingleJson] = ( + malware_detected.get("malware", []) + or raw_data.get("malware", []) + or legacy_malwares + ) + + smtp_to: Any = smtp_message.get("to") or smtp_message.get("rcpt_to") + if isinstance(smtp_to, list): + smtp_recipients: list[str] = [str(r) for r in smtp_to if r] + elif isinstance(smtp_to, str): + smtp_recipients = smtp_to.split() + else: + smtp_recipients = [] + + if not smtp_recipients and alert_sub.get("dst", {}).get("smtp-to"): + smtp_recipients = [str(alert_sub["dst"]["smtp-to"])] + + legacy_recipients: list[str] = ( raw_data.get("attributes", {}).get("email", {}).get("smtp", {}).get("rcpt_to", "").split() ) + self.recipients: list[str] = smtp_recipients or legacy_recipients self.name: str = ALERT_NAME self.timezone_offset: str | None = timezone_offset @@ -91,48 +163,63 @@ def priority(self) -> int: The priority value (60, 80, or 100). """ - if self.severity == "majr": + if self.severity in {"majr", "high"}: return 80 - if self.severity == "crit": + if self.severity in {"crit", "critical"}: return 100 + if self.severity in {"minr", "medium"}: + return 60 + if self.severity in {"info", "low"}: + return 40 return 60 @property - def events(self) -> list[dict[str, Any]]: + def events(self) -> list[SingleJson]: """The events from the alert. Returns: The list of events. """ - events: list[dict[str, Any]] = [] - - for malware in self.malwares: - alert = copy.deepcopy(self.raw_data) - alert.get("attributes", {}).get("alert", {}).get("explanation", {}).pop("os_changes", None) - alert.get("attributes", {}).get("alert", {}).get("explanation", {}).get("malware_detected", {}).pop( - "malware", None - ) - malware["alert"] = alert - events.append(malware) + events: list[SingleJson] = [] + + if self.malwares: + for malware in self.malwares: + malware_copy = copy.deepcopy(malware) + alert_copy = copy.deepcopy(self.raw_data) + if isinstance(alert_copy, dict): + if isinstance(alert_copy.get("alert"), dict): + alert_copy["alert"].get("explanation", {}).get("malware-detected", {}).pop("malware", None) + alert_copy["alert"].get("explanation", {}).get("malware_detected", {}).pop("malware", None) + if isinstance(alert_copy.get("attributes"), dict): + alert_copy["attributes"].get("alert", {}).get("explanation", {}).pop("os_changes", None) + explanation_node = alert_copy["attributes"].get("alert", {}).get("explanation", {}) + explanation_node.get("malware_detected", {}).pop("malware", None) + if isinstance(malware_copy, dict): + malware_copy["alert"] = alert_copy + events.append(malware_copy) + else: + events.append({"name": str(malware_copy), "alert": alert_copy}) + else: + events.append(copy.deepcopy(self.raw_data)) return events @property - def recipient_events(self) -> list[dict[str, Any]]: + def recipient_events(self) -> list[SingleJson]: """The recipient events from the alert. Returns: The list of recipient events. """ - events: list[dict[str, Any]] = [] + events: list[SingleJson] = [] for recipient in self.recipients: event = { "event_name": "FireEye ETP Recipient", - "description": ("This is a custom Siemplify Event created for mapping of the recipients"), + "description": "This is a custom Siemplify Event created for mapping of the recipients", "recipient": recipient, } events.append(event) @@ -147,4 +234,9 @@ def occurred_time_unix(self) -> int: The occurred time in Unix time. """ - return convert_datetime_to_unix_time(naive_time_converted_to_aware(self.timestamp, self.timezone_offset)) + if not self.timestamp: + return 0 + try: + return convert_datetime_to_unix_time(naive_time_converted_to_aware(self.timestamp, self.timezone_offset)) + except (ValueError, TypeError, AttributeError): + return 0 diff --git a/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_constants.py b/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_constants.py index f49e86e74f..bd21894761 100644 --- a/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_constants.py +++ b/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_constants.py @@ -63,3 +63,4 @@ PRINT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f" API_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" +COMPACT_TIMESTAMP_LENGTH = 14 diff --git a/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_manager.py b/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_manager.py index 1805a2a983..5108ee7ec5 100644 --- a/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_manager.py +++ b/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_manager.py @@ -39,6 +39,8 @@ if TYPE_CHECKING: import logging + from TIPCommon.types import SingleJson + from .datamodels import Alert @@ -144,7 +146,7 @@ def _get_full_url(self, url_id: str, **kwargs: object) -> str: def test_connectivity(self) -> None: """Test connectivity to the FireEye ETP API.""" request_url: str = self._get_full_url("test_connectivity") - payload: dict[str, Any] = { + payload: SingleJson = { "date_range": { "from": "2026-01-01T00:00:00.000Z", "to": "2026-01-01T00:01:00.000Z", @@ -168,7 +170,7 @@ def get_alerts(self, start_time: datetime.datetime, timezone_offset: str) -> lis request_url: str = self._get_full_url("get_alerts") start_time_str: str = self._convert_datetime_to_api_format(start_time) end_time_str: str = self._convert_datetime_to_api_format(datetime.datetime.now(datetime.UTC)) - payload: dict[str, Any] = { + payload: SingleJson = { "date_range": {"from": start_time_str, "to": end_time_str}, "size": DEFAULT_FETCH_SIZE, } @@ -191,8 +193,14 @@ def get_alert_details(self, alert_id: str, timezone_offset: str) -> Alert: request_url: str = self._get_full_url("get_alert_details", alert_id=alert_id) response: requests.Response = self.session.get(request_url, timeout=30) validate_response(response, f"Unable to get alert details for {alert_id}") + res_json: SingleJson = response.json() + alert_data: SingleJson = ( + res_json.get("data") + if isinstance(res_json, dict) and "data" in res_json and isinstance(res_json.get("data"), dict) + else res_json + ) return self.parser.build_siemplify_alert_obj( - alert_data=response.json().get("data", {}), + alert_data=alert_data, timezone_offset=timezone_offset, ) diff --git a/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_parser.py b/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_parser.py index 4dfc4b05c5..487e29af16 100644 --- a/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_parser.py +++ b/content/response_integrations/google/fire_eye_etp/core/fire_eye_etp_parser.py @@ -17,15 +17,18 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING from .datamodels import Alert +if TYPE_CHECKING: + from TIPCommon.types import SingleJson + class FireEyeETPParser: """Parser class for converting ETP raw data into datamodels.""" - def build_first_alert(self, raw_data: dict[str, Any], timezone_offset: str | None = None) -> Alert | None: + def build_first_alert(self, raw_data: SingleJson, timezone_offset: str | None = None) -> Alert | None: """Build the first alert from raw data. Args: @@ -36,12 +39,12 @@ def build_first_alert(self, raw_data: dict[str, Any], timezone_offset: str | Non The parsed Alert object, or None if no alerts found. """ - data_json: list[dict[str, Any]] = raw_data.get("data", []) + data_json: list[SingleJson] = raw_data.get("data", []) if data_json: return self.build_siemplify_alert_obj(alert_data=data_json[0], timezone_offset=timezone_offset) return None - def build_alerts_array(self, raw_json: dict[str, Any], timezone_offset: str | None = None) -> list[Alert]: + def build_alerts_array(self, raw_json: SingleJson, timezone_offset: str | None = None) -> list[Alert]: """Build an array of alerts from raw JSON response. Args: @@ -52,14 +55,14 @@ def build_alerts_array(self, raw_json: dict[str, Any], timezone_offset: str | No A list of parsed Alert objects. """ - alerts_data: list[dict[str, Any]] = raw_json.get("data", []) or [] + alerts_data: list[SingleJson] = raw_json.get("data", []) or [] return [ self.build_siemplify_alert_obj(alert_data=alert_data, timezone_offset=timezone_offset) for alert_data in alerts_data ] @staticmethod - def build_siemplify_alert_obj(alert_data: dict[str, Any], timezone_offset: str | None = None) -> Alert: + def build_siemplify_alert_obj(alert_data: SingleJson, timezone_offset: str | None = None) -> Alert: """Build a single Alert object from alert data. Args: diff --git a/content/response_integrations/google/fire_eye_etp/core/utils_manager.py b/content/response_integrations/google/fire_eye_etp/core/utils_manager.py index cf122e6b06..3dff736025 100644 --- a/content/response_integrations/google/fire_eye_etp/core/utils_manager.py +++ b/content/response_integrations/google/fire_eye_etp/core/utils_manager.py @@ -23,10 +23,14 @@ from dateutil import parser from dateutil.tz import tzoffset +from .fire_eye_etp_constants import COMPACT_TIMESTAMP_LENGTH from .fire_eye_etp_exceptions import FireEyeETPError -def naive_time_converted_to_aware(time_param: str, timezone_offset: str) -> datetime.datetime: +def naive_time_converted_to_aware( + time_param: str | None, + timezone_offset: str | float | None = 0, +) -> datetime.datetime: """Convert naive time string to aware datetime object. Args: @@ -37,7 +41,18 @@ def naive_time_converted_to_aware(time_param: str, timezone_offset: str) -> date The timezone-aware datetime object. """ - parsed_date = parser.parse(time_param) + if not time_param: + return datetime.datetime.now(tz=get_server_tzoffset(timezone_offset)) + + # Handle compact timestamp format YYYYMMDDhhmmss (14 digits) + if isinstance(time_param, str) and len(time_param) == COMPACT_TIMESTAMP_LENGTH and time_param.isdigit(): + try: + parsed_date = datetime.datetime.strptime(time_param, "%Y%m%d%H%M%S").replace(tzinfo=datetime.UTC) + except ValueError: + parsed_date = parser.parse(time_param) + else: + parsed_date = parser.parse(str(time_param)) + return datetime.datetime( parsed_date.year, parsed_date.month, @@ -49,7 +64,7 @@ def naive_time_converted_to_aware(time_param: str, timezone_offset: str) -> date ) -def get_server_tzoffset(server_timezone: str) -> tzoffset: +def get_server_tzoffset(server_timezone: str | float | None) -> tzoffset: """Get server timezone offset from UTC. Args: @@ -59,7 +74,12 @@ def get_server_tzoffset(server_timezone: str) -> tzoffset: The tzoffset object. """ - return tzoffset(None, float(server_timezone) * 60 * 60) + if server_timezone is None: + return tzoffset(None, 0) + try: + return tzoffset(None, float(server_timezone) * 60 * 60) + except (ValueError, TypeError): + return tzoffset(None, 0) def current_server_time(timezone_offset: str) -> datetime.datetime: diff --git a/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml b/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml index e821ad1c65..b9d6856eef 100644 --- a/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml +++ b/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml @@ -17,11 +17,11 @@ security_event_file_name: SourceUserName transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/attributes/email/smtp/mail_from + raw_data_primary_field_match_term: alert/alert/email-header/from raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: '' + raw_data_secondary_field_match_term: alert/alert/smtp-message/from raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: '' + raw_data_third_field_match_term: alert/attributes/email/smtp/mail_from raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -33,9 +33,9 @@ transformation_function_param: '' raw_data_primary_field_match_term: recipient raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/attributes/email/smtp/rcpt_to + raw_data_secondary_field_match_term: alert/alert/smtp-message/to_1 raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: '' + raw_data_third_field_match_term: alert/attributes/email/smtp/rcpt_to raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -73,9 +73,9 @@ security_event_file_name: SourceDomain transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: '' + raw_data_primary_field_match_term: alert/alert/src/domain raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: '' + raw_data_secondary_field_match_term: alert/alert/smtp-message/sender_domain raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: '' raw_data_third_field_comparison_type: equal @@ -87,7 +87,7 @@ security_event_file_name: DestinationDomain transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: '' + raw_data_primary_field_match_term: alert/domain raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal @@ -101,7 +101,7 @@ security_event_file_name: SourceAddress transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: '' + raw_data_primary_field_match_term: alert/alert/smtp-message/ip_address raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal @@ -143,7 +143,7 @@ security_event_file_name: ThreatSignature transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: '' + raw_data_primary_field_match_term: alert/alert/smtp-message/threat_type raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal @@ -283,11 +283,11 @@ security_event_file_name: EmailSubject transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/attributes/email/subject + raw_data_primary_field_match_term: alert/alert/email-header/subject raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/attributes/email/headers/subject + raw_data_secondary_field_match_term: alert/email-header/subject raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: '' + raw_data_third_field_match_term: alert/attributes/email/subject raw_data_third_field_comparison_type: equal is_artifact: true extract_function_param: '' @@ -297,11 +297,11 @@ security_event_file_name: FileHash transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: md5 + raw_data_primary_field_match_term: md5sum raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: sha256 raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: '' + raw_data_third_field_match_term: md5 raw_data_third_field_comparison_type: equal is_artifact: true extract_function_param: ',' @@ -313,7 +313,7 @@ transformation_function_param: '' raw_data_primary_field_match_term: name raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: '' + raw_data_secondary_field_match_term: original raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: '' raw_data_third_field_comparison_type: equal @@ -325,9 +325,9 @@ security_event_file_name: DestinationURL transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: '' + raw_data_primary_field_match_term: original raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: '' + raw_data_secondary_field_match_term: alert/alert/src/url raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: '' raw_data_third_field_comparison_type: equal @@ -367,11 +367,11 @@ security_event_file_name: StartTime transformation_function: from_custom_date transformation_function_param: '%Y-%m-%dT%H:%M:%S.%fZ' - raw_data_primary_field_match_term: alert/attributes/email/timestamp/accepted + raw_data_primary_field_match_term: alert/alert/occurred raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: '' + raw_data_secondary_field_match_term: alert/alert_date raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: '' + raw_data_third_field_match_term: alert/attributes/email/timestamp/accepted raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -381,11 +381,11 @@ security_event_file_name: EndTime transformation_function: from_custom_date transformation_function_param: '%Y-%m-%dT%H:%M:%S.%fZ' - raw_data_primary_field_match_term: alert/attributes/email/timestamp/accepted + raw_data_primary_field_match_term: alert/alert/occurred raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: '' + raw_data_secondary_field_match_term: alert/alert_date raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: '' + raw_data_third_field_match_term: alert/attributes/email/timestamp/accepted raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -395,11 +395,11 @@ security_event_file_name: Name transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/attributes/alert/name + raw_data_primary_field_match_term: alert/alert/name raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: '' + raw_data_secondary_field_match_term: name raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: '' + raw_data_third_field_match_term: alert/attributes/alert/name raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' diff --git a/content/response_integrations/google/fire_eye_etp/pyproject.toml b/content/response_integrations/google/fire_eye_etp/pyproject.toml index 03c5470e97..c5d77fc5d3 100644 --- a/content/response_integrations/google/fire_eye_etp/pyproject.toml +++ b/content/response_integrations/google/fire_eye_etp/pyproject.toml @@ -14,7 +14,7 @@ [project] name = "FireEyeETP" -version = "10.0" +version = "11.0" description = "FireEye Email Threat Prevention Cloud (ETP) is different from traditional email security. It is a complete, cloud-based email security solution that delivers automatic protection from the targeted, spear-phishing attacks. Plus, it includes industry-leading FireEye Advanced Threat Intelligence." requires-python = ">=3.11,<3.12" dependencies = [ "arrow>=1.4.0", "environmentcommon", "python-dateutil==2.8.2", "requests==2.32.5", "tipcommon",] diff --git a/content/response_integrations/google/fire_eye_etp/release_notes.yaml b/content/response_integrations/google/fire_eye_etp/release_notes.yaml index 4eda4e6f6a..0a94ed44e7 100644 --- a/content/response_integrations/google/fire_eye_etp/release_notes.yaml +++ b/content/response_integrations/google/fire_eye_etp/release_notes.yaml @@ -116,3 +116,13 @@ item_type: Integration publish_time: '2026-07-17' ticket_number: '' +- description: 'FireEye ETP - Email Alerts Connector - Fixed missing alert details and event data in Email Alerts Connector for v2 API, updated mapping rules, and modernized type annotations.' + integration_version: 11.0 + item_name: FireEye ETP - Email Alerts Connector + item_type: Connector + publish_time: '2026-08-26' + new: false + regressive: false + deprecated: false + removed: false + ticket_number: '543804375' diff --git a/content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py b/content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py new file mode 100644 index 0000000000..a59d6d47b0 --- /dev/null +++ b/content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py @@ -0,0 +1,162 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for FireEye ETP v2 parser and datamodels.""" + +from __future__ import annotations + +import json +from pathlib import Path +from unittest.mock import MagicMock + +from TIPCommon.transformation import dict_to_flat + +from ..core.datamodels import Alert +from ..core.fire_eye_etp_manager import FireEyeETPConfig, FireEyeETPManager +from ..core.utils_manager import get_server_tzoffset, naive_time_converted_to_aware + + +def test_sample_alerts_parsing() -> None: + """Verify that all sample alert.json files parse correctly into Alert objects.""" + base_dir = Path("/tmp/fireeye_analysis/sample_alerts") # noqa: S108 + sample_files = list(base_dir.glob("*/alert.json")) + assert len(sample_files) > 0, "Should have sample alert files extracted" + + for file_path in sample_files: + with file_path.open(encoding="utf-8") as fp: + raw_data = json.load(fp) + + alert = Alert(raw_data=raw_data, timezone_offset="0") + assert alert.id is not None + assert len(alert.id) > 0 + assert alert.timestamp is not None + assert alert.severity is not None + assert alert.priority in {40, 60, 80, 100} + assert alert.etp_message_id is not None + assert len(alert.etp_message_id) > 0 + assert alert.occurred_time_unix > 0 + + # Verify malware extraction + assert len(alert.malwares) >= 1 + first_malware = alert.malwares[0] + assert "name" in first_malware + assert "md5sum" in first_malware or "md5" in first_malware + assert "sha256" in first_malware + + # Verify recipients extraction + assert len(alert.recipients) >= 1 + + # Verify events generation + events = alert.events + assert len(events) >= 1 + first_event = events[0] + assert "alert" in first_event + + # Flatten event and verify key fields + flat_event = dict_to_flat(first_event) + assert "alert_id" in flat_event + assert "alert_alert_occurred" in flat_event + assert "alert_alert_email-header_subject" in flat_event + assert "alert_alert_smtp-message_ip_address" in flat_event + assert "md5sum" in flat_event + assert "sha256" in flat_event + + # Verify recipient events + recipient_events = alert.recipient_events + assert len(recipient_events) == len(alert.recipients) + assert recipient_events[0]["event_name"] == "FireEye ETP Recipient" + + +def test_manager_get_alert_details_handling() -> None: + """Verify FireEyeETPManager.get_alert_details handles both root dict and {'data': dict}.""" + config = FireEyeETPConfig( + api_root="https://etp.fireeye.com", + api_key="dummy_key", + ) + manager = FireEyeETPManager(config=config) + + # Mock session response for v2 root dict + v2_root_response = MagicMock() + v2_root_response.status_code = 200 + v2_root_response.json.return_value = { + "id": "alert-123", + "alert": { + "name": "malware-object", + "severity": "majr", + "occurred": "2026-08-20T02:54:32.000000", + "email-header": {"message-id": "", "subject": "Test"}, + "smtp-message": {"from": "sender@test.com", "to": ["rcpt@test.com"]}, + "explanation": { + "malware-detected": { + "malware": [{"name": "TestMalware", "md5sum": "12345", "sha256": "67890"}] + } + }, + }, + } + + manager.session.get = MagicMock(return_value=v2_root_response) + alert = manager.get_alert_details("alert-123", timezone_offset="0") + assert alert.id == "alert-123" + assert alert.severity == "majr" + assert alert.priority == 80 + assert alert.etp_message_id == "" + assert len(alert.malwares) == 1 + assert len(alert.recipients) == 1 + + # Mock session response for wrapped {'data': {...}} + v2_wrapped_response = MagicMock() + v2_wrapped_response.status_code = 200 + v2_wrapped_response.json.return_value = { + "data": { + "id": "alert-456", + "alert": { + "severity": "crit", + "occurred": "2026-08-20T02:54:32.000000", + "email-header": {"message-id": ""}, + }, + } + } + manager.session.get = MagicMock(return_value=v2_wrapped_response) + alert_wrapped = manager.get_alert_details("alert-456", timezone_offset="0") + assert alert_wrapped.id == "alert-456" + assert alert_wrapped.severity == "crit" + assert alert_wrapped.priority == 100 + + +def test_timezone_utilities() -> None: + """Verify that get_server_tzoffset and naive_time_converted_to_aware handle edge cases.""" + # None timezone offset should default to 0 + tz_none = get_server_tzoffset(None) + assert tz_none is not None + + # String, float, int offsets + tz_str = get_server_tzoffset("2") + assert tz_str is not None + tz_neg = get_server_tzoffset("-5.5") + assert tz_neg is not None + + # Compact timestamp format (14 digits) + dt_compact = naive_time_converted_to_aware("20260820025319", "0") + assert dt_compact.year == 2026 + assert dt_compact.month == 8 + assert dt_compact.day == 20 + assert dt_compact.hour == 2 + assert dt_compact.minute == 53 + assert dt_compact.second == 19 + + # Standard ISO format + dt_iso = naive_time_converted_to_aware("2026-08-20T02:54:32.000000", "0") + assert dt_iso.year == 2026 + assert dt_iso.month == 8 + assert dt_iso.day == 20 diff --git a/content/response_integrations/google/fire_eye_etp/uv.lock b/content/response_integrations/google/fire_eye_etp/uv.lock index 74e633d715..19aca5f1f7 100644 --- a/content/response_integrations/google/fire_eye_etp/uv.lock +++ b/content/response_integrations/google/fire_eye_etp/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = "==3.11.*" [[package]] @@ -165,7 +165,7 @@ wheels = [ [[package]] name = "fireeyeetp" -version = "10.0" +version = "11.0" source = { virtual = "." } dependencies = [ { name = "arrow" }, From 23ecc88cb4b6982ff1f81626c2dcc45bb70e94a9 Mon Sep 17 00:00:00 2001 From: adarshtiwary Date: Mon, 31 Aug 2026 10:51:45 +0000 Subject: [PATCH 2/7] FireEye ETP - Fix unit test sample payloads to be self-contained Embed sample test alert payloads directly in test_v2_parsing.py to remove dependency on local temporary filesystem directory (/tmp), fixing the CI test integration failure. BUG=b/543804375 --- .../google/fire_eye_etp/tests/mock_data.py | 118 ++++++++++++++++++ .../fire_eye_etp/tests/test_v2_parsing.py | 50 ++------ 2 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 content/response_integrations/google/fire_eye_etp/tests/mock_data.py diff --git a/content/response_integrations/google/fire_eye_etp/tests/mock_data.py b/content/response_integrations/google/fire_eye_etp/tests/mock_data.py new file mode 100644 index 0000000000..e4f1209d80 --- /dev/null +++ b/content/response_integrations/google/fire_eye_etp/tests/mock_data.py @@ -0,0 +1,118 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Mock and sample alert data for FireEye ETP unit tests.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from TIPCommon.types import SingleJson + +SAMPLE_V2_ALERT: SingleJson = { + "id": "c13ef31b-7a71-4770-b74a-4e2b0244ba07", + "domain": "customer.com", + "verdict": "malicious", + "alert": { + "name": "malware-object", + "severity": "majr", + "occurred": "2026-08-20T02:54:32.000000", + "email-header": { + "from": '"Sender Name" ', + "to": "recipient@customer.com", + "subject": "Payment Details", + "message-id": "", + }, + "smtp-message": { + "from": "sender@test.com", + "to": ["recipient@customer.com"], + "ip_address": "192.0.2.1", + "threat_type": "malware", + "sender_domain": "test.com", + }, + "explanation": { + "malware-detected": { + "malware": [ + { + "name": "Trojan.Generic", + "md5sum": "d41d8cd98f00b204e9800998ecf8427e", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original": "invoice.pdf", + } + ] + } + }, + }, +} + +SAMPLE_SEARCH_ALERT: SingleJson = { + "id": "search-alert-002", + "report_id": "search-alert-002", + "accepted_time": "20260820025319", + "alert_date": "2026-08-20T02:53:19.000Z", + "severity": "crit", + "mta_msg_id": "", + "malware": [ + { + "name": "Exploit.CVE", + "md5": "d41d8cd98f00b204e9800998ecf8427e", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "original": "exploit.doc", + } + ], + "smtp-message": { + "to": ["user1@customer.com", "user2@customer.com"], + "from": "attacker@evil.com", + "ip_address": "198.51.100.1", + }, + "email-header": { + "from": "attacker@evil.com", + "to": "user1@customer.com", + "subject": "Urgent Security Notice", + "message-id": "", + }, +} + +SAMPLE_ALERTS: list[SingleJson] = [ + SAMPLE_V2_ALERT, + SAMPLE_SEARCH_ALERT, +] + +MOCK_V2_ROOT_DETAIL_RESPONSE: SingleJson = { + "id": "alert-123", + "alert": { + "name": "malware-object", + "severity": "majr", + "occurred": "2026-08-20T02:54:32.000000", + "email-header": {"message-id": "", "subject": "Test"}, + "smtp-message": {"from": "sender@test.com", "to": ["rcpt@test.com"]}, + "explanation": { + "malware-detected": { + "malware": [{"name": "TestMalware", "md5sum": "12345", "sha256": "67890"}] + } + }, + }, +} + +MOCK_V2_WRAPPED_DETAIL_RESPONSE: SingleJson = { + "data": { + "id": "alert-456", + "alert": { + "severity": "crit", + "occurred": "2026-08-20T02:54:32.000000", + "email-header": {"message-id": ""}, + }, + } +} diff --git a/content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py b/content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py index a59d6d47b0..038195324c 100644 --- a/content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py +++ b/content/response_integrations/google/fire_eye_etp/tests/test_v2_parsing.py @@ -16,8 +16,6 @@ from __future__ import annotations -import json -from pathlib import Path from unittest.mock import MagicMock from TIPCommon.transformation import dict_to_flat @@ -25,18 +23,18 @@ from ..core.datamodels import Alert from ..core.fire_eye_etp_manager import FireEyeETPConfig, FireEyeETPManager from ..core.utils_manager import get_server_tzoffset, naive_time_converted_to_aware +from .mock_data import ( + MOCK_V2_ROOT_DETAIL_RESPONSE, + MOCK_V2_WRAPPED_DETAIL_RESPONSE, + SAMPLE_ALERTS, +) def test_sample_alerts_parsing() -> None: - """Verify that all sample alert.json files parse correctly into Alert objects.""" - base_dir = Path("/tmp/fireeye_analysis/sample_alerts") # noqa: S108 - sample_files = list(base_dir.glob("*/alert.json")) - assert len(sample_files) > 0, "Should have sample alert files extracted" - - for file_path in sample_files: - with file_path.open(encoding="utf-8") as fp: - raw_data = json.load(fp) + """Verify that sample alert payloads parse correctly into Alert objects.""" + assert len(SAMPLE_ALERTS) > 0 + for raw_data in SAMPLE_ALERTS: alert = Alert(raw_data=raw_data, timezone_offset="0") assert alert.id is not None assert len(alert.id) > 0 @@ -66,10 +64,7 @@ def test_sample_alerts_parsing() -> None: # Flatten event and verify key fields flat_event = dict_to_flat(first_event) assert "alert_id" in flat_event - assert "alert_alert_occurred" in flat_event - assert "alert_alert_email-header_subject" in flat_event - assert "alert_alert_smtp-message_ip_address" in flat_event - assert "md5sum" in flat_event + assert "md5sum" in flat_event or "md5" in flat_event assert "sha256" in flat_event # Verify recipient events @@ -89,21 +84,7 @@ def test_manager_get_alert_details_handling() -> None: # Mock session response for v2 root dict v2_root_response = MagicMock() v2_root_response.status_code = 200 - v2_root_response.json.return_value = { - "id": "alert-123", - "alert": { - "name": "malware-object", - "severity": "majr", - "occurred": "2026-08-20T02:54:32.000000", - "email-header": {"message-id": "", "subject": "Test"}, - "smtp-message": {"from": "sender@test.com", "to": ["rcpt@test.com"]}, - "explanation": { - "malware-detected": { - "malware": [{"name": "TestMalware", "md5sum": "12345", "sha256": "67890"}] - } - }, - }, - } + v2_root_response.json.return_value = MOCK_V2_ROOT_DETAIL_RESPONSE manager.session.get = MagicMock(return_value=v2_root_response) alert = manager.get_alert_details("alert-123", timezone_offset="0") @@ -117,16 +98,7 @@ def test_manager_get_alert_details_handling() -> None: # Mock session response for wrapped {'data': {...}} v2_wrapped_response = MagicMock() v2_wrapped_response.status_code = 200 - v2_wrapped_response.json.return_value = { - "data": { - "id": "alert-456", - "alert": { - "severity": "crit", - "occurred": "2026-08-20T02:54:32.000000", - "email-header": {"message-id": ""}, - }, - } - } + v2_wrapped_response.json.return_value = MOCK_V2_WRAPPED_DETAIL_RESPONSE manager.session.get = MagicMock(return_value=v2_wrapped_response) alert_wrapped = manager.get_alert_details("alert-456", timezone_offset="0") assert alert_wrapped.id == "alert-456" From 282b52868ea1eea60dcb48ce234df176fc42dd87 Mon Sep 17 00:00:00 2001 From: adarshtiwary Date: Mon, 31 Aug 2026 10:59:08 +0000 Subject: [PATCH 3/7] FireEye ETP - Fix linter violation in email_alerts_connector.py Use ruff: ignore instead of noqa comment for ANN401 in create_alert_info. BUG=b/543804375 --- .../google/fire_eye_etp/connectors/email_alerts_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py index fb21d5f1c0..d9513d684a 100644 --- a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py +++ b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py @@ -162,7 +162,7 @@ def calculate_priority(alerts_group: list[Alert]) -> int: def create_alert_info( - environment: Any, # noqa: ANN401 + environment: Any, # ruff: ignore[ANN401] alerts_group: list[Alert], ) -> AlertInfo: """Create a Siemplify AlertInfo object from a group of alerts. From dd7c64f98094534a8c6a1c7be42e28908882ba8b Mon Sep 17 00:00:00 2001 From: adarshtiwary Date: Mon, 31 Aug 2026 11:24:44 +0000 Subject: [PATCH 4/7] FireEye ETP - Use rule name any-type in suppression comment Use rule name any-type instead of rule code ANN401 in suppression comment at line 165. BUG=b/543804375 --- .../google/fire_eye_etp/connectors/email_alerts_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py index d9513d684a..26c15896a0 100644 --- a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py +++ b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py @@ -162,7 +162,7 @@ def calculate_priority(alerts_group: list[Alert]) -> int: def create_alert_info( - environment: Any, # ruff: ignore[ANN401] + environment: Any, # ruff: ignore[any-type] alerts_group: list[Alert], ) -> AlertInfo: """Create a Siemplify AlertInfo object from a group of alerts. From bfbbedaadba2c6a3fa3ce73cb980a3c7f5589afc Mon Sep 17 00:00:00 2001 From: adarshtiwary Date: Tue, 1 Sep 2026 10:32:24 +0000 Subject: [PATCH 5/7] FireEye ETP - Update ontology mapping and type annotations Replace slash delimiters with underscores in ontology mapping raw_data match terms and use EnvironmentHandle in connector. BUG=b/543804375 --- .../connectors/email_alerts_connector.py | 10 ++--- .../google/fire_eye_etp/ontology_mapping.yaml | 44 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py index 26c15896a0..0e9af1bbe0 100644 --- a/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py +++ b/content/response_integrations/google/fire_eye_etp/connectors/email_alerts_connector.py @@ -20,7 +20,7 @@ import sys import uuid from datetime import timedelta -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING import arrow from soar_sdk.SiemplifyConnectors import SiemplifyConnectorExecution @@ -39,9 +39,9 @@ from TIPCommon.utils import is_overflowed try: - from EnvironmentCommon import GetEnvironmentCommonFactory + from EnvironmentCommon import EnvironmentHandle, GetEnvironmentCommonFactory except ImportError: - from TIPCommon.envcommon import GetEnvironmentCommonFactory + from TIPCommon.envcommon import EnvironmentHandle, GetEnvironmentCommonFactory from ..core.fire_eye_etp_constants import ( ACCEPTABLE_TIME_INTERVAL_IN_MINUTES, @@ -162,7 +162,7 @@ def calculate_priority(alerts_group: list[Alert]) -> int: def create_alert_info( - environment: Any, # ruff: ignore[any-type] + environment: EnvironmentHandle, alerts_group: list[Alert], ) -> AlertInfo: """Create a Siemplify AlertInfo object from a group of alerts. @@ -248,7 +248,7 @@ def process_single_alert_group( detailed_alert_group.append(detailed_alert) siemplify.LOGGER.info(f"Creating AlertInfo for alert group {alert_group[0].etp_message_id}") - environment_common: Any = GetEnvironmentCommonFactory.create_environment_manager( + environment_common: EnvironmentHandle = GetEnvironmentCommonFactory.create_environment_manager( siemplify, environment_field_name=params["environment_field_name"], environment_regex_pattern=params["environment_regex_pattern"], diff --git a/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml b/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml index b9d6856eef..7f1fdb09ca 100644 --- a/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml +++ b/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml @@ -17,11 +17,11 @@ security_event_file_name: SourceUserName transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/alert/email-header/from + raw_data_primary_field_match_term: alert_alert_email-header_from raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/alert/smtp-message/from + raw_data_secondary_field_match_term: alert_alert_smtp-message_from raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: alert/attributes/email/smtp/mail_from + raw_data_third_field_match_term: alert_attributes_email_smtp_mail_from raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -33,9 +33,9 @@ transformation_function_param: '' raw_data_primary_field_match_term: recipient raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/alert/smtp-message/to_1 + raw_data_secondary_field_match_term: alert_alert_smtp-message_to_1 raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: alert/attributes/email/smtp/rcpt_to + raw_data_third_field_match_term: alert_attributes_email_smtp_rcpt_to raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -73,9 +73,9 @@ security_event_file_name: SourceDomain transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/alert/src/domain + raw_data_primary_field_match_term: alert_alert_src_domain raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/alert/smtp-message/sender_domain + raw_data_secondary_field_match_term: alert_alert_smtp-message_sender_domain raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: '' raw_data_third_field_comparison_type: equal @@ -87,7 +87,7 @@ security_event_file_name: DestinationDomain transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/domain + raw_data_primary_field_match_term: alert_domain raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal @@ -101,7 +101,7 @@ security_event_file_name: SourceAddress transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/alert/smtp-message/ip_address + raw_data_primary_field_match_term: alert_alert_smtp-message_ip_address raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal @@ -143,7 +143,7 @@ security_event_file_name: ThreatSignature transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/alert/smtp-message/threat_type + raw_data_primary_field_match_term: alert_alert_smtp-message_threat_type raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal @@ -283,11 +283,11 @@ security_event_file_name: EmailSubject transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/alert/email-header/subject + raw_data_primary_field_match_term: alert_alert_email-header_subject raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/email-header/subject + raw_data_secondary_field_match_term: alert_email-header_subject raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: alert/attributes/email/subject + raw_data_third_field_match_term: alert_attributes_email_subject raw_data_third_field_comparison_type: equal is_artifact: true extract_function_param: '' @@ -327,7 +327,7 @@ transformation_function_param: '' raw_data_primary_field_match_term: original raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/alert/src/url + raw_data_secondary_field_match_term: alert_alert_src_url raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: '' raw_data_third_field_comparison_type: equal @@ -367,11 +367,11 @@ security_event_file_name: StartTime transformation_function: from_custom_date transformation_function_param: '%Y-%m-%dT%H:%M:%S.%fZ' - raw_data_primary_field_match_term: alert/alert/occurred + raw_data_primary_field_match_term: alert_alert_occurred raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/alert_date + raw_data_secondary_field_match_term: alert_alert_date raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: alert/attributes/email/timestamp/accepted + raw_data_third_field_match_term: alert_attributes_email_timestamp_accepted raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -381,11 +381,11 @@ security_event_file_name: EndTime transformation_function: from_custom_date transformation_function_param: '%Y-%m-%dT%H:%M:%S.%fZ' - raw_data_primary_field_match_term: alert/alert/occurred + raw_data_primary_field_match_term: alert_alert_occurred raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert/alert_date + raw_data_secondary_field_match_term: alert_alert_date raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: alert/attributes/email/timestamp/accepted + raw_data_third_field_match_term: alert_attributes_email_timestamp_accepted raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -395,11 +395,11 @@ security_event_file_name: Name transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert/alert/name + raw_data_primary_field_match_term: alert_alert_name raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: name raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: alert/attributes/alert/name + raw_data_third_field_match_term: alert_attributes_alert_name raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' From 744ececf84cabf64fd0eb6b9c3b0d329196d2d1c Mon Sep 17 00:00:00 2001 From: adarshtiwary Date: Wed, 2 Sep 2026 07:58:45 +0000 Subject: [PATCH 6/7] Realigned the event mapping --- .../google/fire_eye_etp/ontology_mapping.yaml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml b/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml index 7f1fdb09ca..7f9c039ecd 100644 --- a/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml +++ b/content/response_integrations/google/fire_eye_etp/ontology_mapping.yaml @@ -17,9 +17,9 @@ security_event_file_name: SourceUserName transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert_alert_email-header_from + raw_data_primary_field_match_term: alert_alert_smtp-message_from raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert_alert_smtp-message_from + raw_data_secondary_field_match_term: alert_alert_src_smtp-mail-from raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: alert_attributes_email_smtp_mail_from raw_data_third_field_comparison_type: equal @@ -31,11 +31,11 @@ security_event_file_name: DestinationUserName transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: recipient + raw_data_primary_field_match_term: alert_alert_email-header_to raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert_alert_smtp-message_to_1 + raw_data_secondary_field_match_term: alert_alert_dst_smtp-to raw_data_secondary_field_comparison_type: equal - raw_data_third_field_match_term: alert_attributes_email_smtp_rcpt_to + raw_data_third_field_match_term: recipient raw_data_third_field_comparison_type: equal is_artifact: false extract_function_param: '' @@ -73,9 +73,9 @@ security_event_file_name: SourceDomain transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert_alert_src_domain + raw_data_primary_field_match_term: alert_domain raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert_alert_smtp-message_sender_domain + raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: '' raw_data_third_field_comparison_type: equal @@ -87,7 +87,7 @@ security_event_file_name: DestinationDomain transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert_domain + raw_data_primary_field_match_term: '' raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: '' raw_data_secondary_field_comparison_type: equal @@ -297,9 +297,9 @@ security_event_file_name: FileHash transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: md5sum + raw_data_primary_field_match_term: sha256 raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: sha256 + raw_data_secondary_field_match_term: md5sum raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: md5 raw_data_third_field_comparison_type: equal @@ -367,9 +367,9 @@ security_event_file_name: StartTime transformation_function: from_custom_date transformation_function_param: '%Y-%m-%dT%H:%M:%S.%fZ' - raw_data_primary_field_match_term: alert_alert_occurred + raw_data_primary_field_match_term: alert_accepted_time raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert_alert_date + raw_data_secondary_field_match_term: startTime raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: alert_attributes_email_timestamp_accepted raw_data_third_field_comparison_type: equal @@ -381,9 +381,9 @@ security_event_file_name: EndTime transformation_function: from_custom_date transformation_function_param: '%Y-%m-%dT%H:%M:%S.%fZ' - raw_data_primary_field_match_term: alert_alert_occurred + raw_data_primary_field_match_term: alert_accepted_time raw_data_primary_field_comparison_type: equal - raw_data_secondary_field_match_term: alert_alert_date + raw_data_secondary_field_match_term: endTime raw_data_secondary_field_comparison_type: equal raw_data_third_field_match_term: alert_attributes_email_timestamp_accepted raw_data_third_field_comparison_type: equal @@ -395,7 +395,7 @@ security_event_file_name: Name transformation_function: to_string transformation_function_param: '' - raw_data_primary_field_match_term: alert_alert_name + raw_data_primary_field_match_term: alert_verdict raw_data_primary_field_comparison_type: equal raw_data_secondary_field_match_term: name raw_data_secondary_field_comparison_type: equal From 807fab490fdd1428ef193ea8d07ce9b86c96d36e Mon Sep 17 00:00:00 2001 From: adarshtiwary Date: Wed, 2 Sep 2026 08:02:12 +0000 Subject: [PATCH 7/7] Aligned the release notes as per SOAR RN guidelines --- .../google/fire_eye_etp/release_notes.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/response_integrations/google/fire_eye_etp/release_notes.yaml b/content/response_integrations/google/fire_eye_etp/release_notes.yaml index 0a94ed44e7..57e4c012aa 100644 --- a/content/response_integrations/google/fire_eye_etp/release_notes.yaml +++ b/content/response_integrations/google/fire_eye_etp/release_notes.yaml @@ -116,7 +116,7 @@ item_type: Integration publish_time: '2026-07-17' ticket_number: '' -- description: 'FireEye ETP - Email Alerts Connector - Fixed missing alert details and event data in Email Alerts Connector for v2 API, updated mapping rules, and modernized type annotations.' +- description: 'FireEye ETP - Email Alerts Connector - Fixed an issue where alert details and event data were missing for v2 API, and updated ontology mapping rules.' integration_version: 11.0 item_name: FireEye ETP - Email Alerts Connector item_type: Connector