FireEye ETP
Trellix Email Security - Cloud is a cloud-based platform that protects against advanced email attacks.
Network Security · Trellix Email Security - Cloud
Details
| ID | FireEye ETP |
|---|---|
| Provider | Trellix |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10404775 |
| Supported Modules | Agentix XSIAM |
README
Trellix Email Security - Cloud
Overview
Use the Trellix Email Security - Cloud integration to import messages as incidents, search for messages with specific attributes, and retrieve alert data.
Use Cases
- Search for messages using specific message attributes as indicators.
- Import messages as Cortex incidents/issues, using the message status as indicator.
Authentication and Authorization
Configuring API Keys
If the IAM domain you use to access the Trellix UI ends in fireeye.com, follow these steps to configure API keys:
- Log in to the Email Security — Cloud Web Portal or IAM console.
- Click My Settings in the top navigation bar.
- Click the API Keys tab in the IAM console.
- Click Create API Key.
- On the Manage API Key page, specify the following:
- API key name
- Expiration time for the API key.
The expiration time should be set as"100d"for 100 days or"1y"for 1 year, for example. - Products
Select both Email Threat Prevention and Identity Access Management.
-
Select all required entitlements. For any API access, the following entitlements are mandatory:
iam.users.browseiam.orgs.self.read
Alerts APIs
For accessing alerts APIs, the following additional entitlements are required:
etp.alerts.read
Trace APIs
For accessing trace APIs, the following additional entitlements are required:
etp.email_trace.read
Quarantine APIs
For accessing quarantine APIs, the following additional entitlements are required:
etp.quarantine.updateetp.quar
Authentication Prerequisites
To ensure a successful connection, select the authentication method that matches the Server URL (Instance URL) you are configuring.
Dual Authentication Methods
We support two different authentication methods depending on the endpoint domain:
| Domain Used in Server URL | Authentication Method | Required Parameters |
|---|---|---|
Ends in trellix.com |
OAuth 2.0 | Client ID, Client Secret, and OAuth Scopes |
Ends in fireeye.com |
API Key | API Key (only) |
Authentication Setup (Choose One)
You must configure only one of the two authentication approaches below based on your Server URL domain.
- 1. API Key Method (For
fireeye.comURLs):- Configure an API key on the ETP Web portal. Select the product as both Email Threat Prevention and Identity Access Management. Select all entitlements.
- 2. OAuth 2.0 Method (For
trellix.comURLs):- When creating the Client ID and Client Secret, ensure the corresponding user/role has explicit permission to access the API.
- Note: If API access permissions are not properly set for the user/role, the authentication attempt will fail with a
400 Client Error: Bad Requesteven if the Client ID and Secret are correct.
- Contact Trellix Email Security - Cloud Technical Support to let them know the IP address of your Cortex Server and the URL you are accessing, e.g.
https://etp.us.fireeye.com. Trellix will add these details to their Firewall rules so that the bidirectional traffic can be allowed between Cortex and Trellix Email Security - Cloud.
Configure Trellix Email Security - Cloud in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | Valid URLs (US, EMEA, USGOV): https://us.etp.trellix.com / https://etp.us.fireeye.com, https://eu.etp.trellix.com / https://etp.eu.fireeye.com, https://etp.us.fireeyegov.com | True |
| Client ID (OAuth) | Use the Client ID and Client Secret for the Trellix base URL. | |
| Client Secret (OAuth) | ||
| OAuth Scopes (OAuth) | Space-separated list of OAuth scopes. Note: Only include scopes that your application’s Client ID has already been authorized to use. | False |
| Token URL | Override the OAuth 2.0 token endpoint base URL. Leave empty to use the default Trellix IAM endpoint. For Trellix GovCloud tenants, set to https://iam.us.trellix-gov.com. |
False |
| API Key | Use the Api key for the FireEye base URL. | False |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch incidents | False | |
| First fetch timestamp. | False | |
| Max incidents per fetch | Input a value between 1 and 59. Values above 59 will be internally capped to avoid exceeding API rate limits. | False |
| Incident type | False | |
| Alerts statuses to import | All alerts with a status specified here will be imported as incidents. Valid values are: accepted, deleted, delivered, delivered (retroactive), dropped, dropped oob, dropped (oob retroactive), permanent failure, processing quarantined, rejected, temporary failure | False |
Fetched Incidents Data
To use Fetch incidents:
- Configure a new instance.
- Navigate to instance settings, and specify the message status (using the valid values).
- Select Fetch incidents option.
The integration will fetch alerts as incidents. It is possible to filter alerts using the specified message status.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
fireeye-etp-search-messages
Search for messages using specific message attributes as indicators.
Search for messages that include specified message attributes that are accessible in the ETP portal.
Base Command
fireeye-etp-search-messages
Input
| Argument Name | Description | Required |
|---|---|---|
| from_email | List of ‘From’ email-addresses, max limit of entries is 10. | Optional |
| from_email_not_in | List of ‘From’ email-addresses not to be included, max limit of entries is 10. | Optional |
| recipients | List of ‘To’/’Cc’ email-addresses, max limit of entries is 10. | Optional |
| recipients_not_in | list of ‘To’/’Cc’ email-addresses not to be included, max limit of entries is 10. | Optional |
| subject | List of strings, max limit of entries is 10. | Optional |
| from_accepted_date_time | The time stamp of the email-accepted date to specify the beginning of the date range to search, e.g. 2017-10-24T10:48:51.000Z . Specify ‘to_accepted_date_time’ as well to set the complete date range for the search. | Optional |
| to_accepted_date_time | The time stamp of the email-accepted date to specify the end of the date range to search, e.g. 2017-10-24T10:48:51.000Z . Specify ‘from_accepted_date_time’ as well to set the complete date range for the search. | Optional |
| rejection_reason | List of ETP rejection reason codes ( “ETP102”, “ETP103”, “ETP104”, “ETP200”, “ETP201”, “ETP203”, “ETP204”, “ETP205”, “ETP300”, “ETP301”, “ETP302”, “ETP401”, “ETP402”, “ETP403”, “ETP404”, “ETP405”). | Optional |
| sender_ip | List of sender IP addresses, max limit of entries is 10. | Optional |
| status | List of email status values( “accepted”, “deleted”, “delivered”, “delivered (retroactive)”, “dropped”, “dropped oob”, “dropped (oob retroactive)”, “permanent failure”, “processing”, “quarantined”, “rejected”, “temporary failure”). | Optional |
| status_not_in | List of email status values not to include( “accepted”, “deleted”, “delivered”, “delivered (retroactive)”, “dropped”, “dropped oob”, “dropped (oob retroactive)”, “permanent failure”, “processing”, “quarantined”, “rejected”, “temporary failure”). | Optional |
| last_modified_date_time | Date corresponding to last modified date, along with one of the following operators: “>”, “<”, “>=”, “<=”. E.g. use value “<2017-10-24T18:00:00.000Z” to search for messages that were last modified after the specified time stamp. | Optional |
| domain | List of domain names. | Optional |
| has_attachments | Boolean value to indicate if the message has attachments. Possible values are: true, false. | Optional |
| max_message_size | The default value is 20kb and maximum value is 100kb. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FireEyeETP.Message.acceptedDateTime | unknown | Message accepted date. |
| FireEyeETP.Message.countryCode | unknown | Sender country code. |
| FireEyeETP.Message.domain | unknown | Domain. |
| FireEyeETP.Message.emailSize | unknown | Email size in kb. |
| FireEyeETP.Message.lastModifiedDateTime | unknown | Message last modified date. |
| FireEyeETP.Message.recipientHeader | unknown | List of message recipients header (includes the display name of the user). |
| FireEyeETP.Message.recipients | unknown | List of message recipients. |
| FireEyeETP.Message.senderHeader | unknown | Message sender header (includes the display name of the user). |
| FireEyeETP.Message.sender | unknown | Message sender address. |
| FireEyeETP.Message.senderSMTP | unknown | Message sender SMTP. |
| FireEyeETP.Message.senderIP | unknown | Message sender IP. |
| FireEyeETP.Message.status | unknown | Message status. |
| FireEyeETP.Message.subject | unknown | Message subject. |
| FireEyeETP.Message.verdicts.AS | unknown | pass/fail verdict for AS. |
| FireEyeETP.Message.verdicts.AV | unknown | pass/fail verdict for AV. |
| FireEyeETP.Message.verdicts.AT | unknown | pass/fail verdict for AT. |
| FireEyeETP.Message.verdicts.PV | unknown | pass/fail verdict for PV. |
| FireEyeETP.Message.id | unknown | Message ID. |
Command example 1
!fireeye-etp-search-messages to_accepted_date_time=2017-10-24T10:00:00.000Z from_accepted_date_time=2017-10-24T10:30:00.000Z
Command example 2
!fireeye-etp-search-messages from_email=diana@corp.com,charles@corp.com
Raw Output
{
"data": [
{
"attributes": {
"acceptedDateTime": "2018-06-09T10:49:32.000Z",
"countryCode": "US",
"domain": "test.com",
"downStreamMsgID": "250 2.0.0 OK 100041373 d14-v6si970000qtb.70 - gsmtp",
"emailSize": 9.89,
"lastModifiedDateTime": "2018-06-09T10:49:33.329Z",
"recipientHeader": [
"Security Operations Center <SOC@corp.com>"
],
"recipientSMTP": [
"jason@demisto.com"
],
"senderHeader": "\"soc@demisto.com\" <bot@demisto.com >",
"senderSMTP": "prvs=691a94fds62a=demisto@demisto.com ",
"senderIP": "***.***.***.***",
"status": "delivered",
"subject": "Attack TCP: SYN Host Sweep (Medium)",
"verdicts": {
"AS": "",
"AV": "",
"AT": "pass",
"PV": ""
}
},
"included": [
{
"type": "domain",
"id": 29074,
"attributes": {
"name": "test.com "
}
}
],
"id": "C88B18749AAAAB1B55fc0fa78",
"type": "trace"
}
],
"meta": {
"total": 85347,
"copyright": "Copyright 2018 Fireeye Inc",
"fromLastModifiedOn": {
"start": "2018-06-09T10:49:33.329Z",
"end": "2018-06-09T10:50:59.034Z"
}
}
}
fireeye-etp-get-message
Get the data of a specific message.
Base Command
fireeye-etp-get-message
Input
| Argument Name | Description | Required |
|---|---|---|
| message_id | The message ID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| FireEyeETP.Message.acceptedDateTime | unknown | Message accepted date. |
| FireEyeETP.Message.countryCode | unknown | Sender country code. |
| FireEyeETP.Message.domain | unknown | Domain. |
| FireEyeETP.Message.emailSize | unknown | Email size in kb. |
| FireEyeETP.Message.lastModifiedDateTime | unknown | Message last modified date. |
| FireEyeETP.Message.recipientHeader | unknown | List of message recipients header (includes the display name of the user). |
| FireEyeETP.Message.recipients | unknown | List of message recipients. |
| FireEyeETP.Message.senderHeader | unknown | Message sender header (includes the display name of the user). |
| FireEyeETP.Message.sender | unknown | Message sender address. |
| FireEyeETP.Message.senderSMTP | unknown | Message sender SMTP. |
| FireEyeETP.Message.senderIP | unknown | Message sender IP. |
| FireEyeETP.Message.status | unknown | Message status. |
| FireEyeETP.Message.subject | unknown | Message subject. |
| FireEyeETP.Message.verdicts.AS | unknown | pass/fail verdict for AS. |
| FireEyeETP.Message.verdicts.AV | unknown | pass/fail verdict for AV. |
| FireEyeETP.Message.verdicts.AT | unknown | pass/fail verdict for AT. |
| FireEyeETP.Message.verdicts.PV | unknown | pass/fail verdict for PV. |
| FireEyeETP.Message.id | unknown | Message ID. |
Command example
!fireeye-etp-get-message message_id= C88B18749AAAAB1B55fc0fa78
Raw Output
There is no raw output for this command.
fireeye-etp-list-alerts
Get summary format information about the alerts.
Base Command
fireeye-etp-list-alerts
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The alert ID. | Optional |
| date_from | Supports ISO format (e.g., 2025-09-02T06:45:01Z) or natural language (“7 days ago”, “now”). | Optional |
| date_to | Supports ISO format (e.g., 2025-09-02T06:45:01Z) or natural language (“7 days ago”, “now”). | Optional |
| domain | List of domain names. | Optional |
| domain_group | List of domain groups names. | Optional |
| email_header_subject | List of message subject headers. | Optional |
| is_read | is_read flag. | Optional |
| is_retro | is_retro flag. | Optional |
| malwarename | List of malware names. | Optional |
| malwarestype | List of malware types. | Optional |
| md5 | List of md5. | Optional |
| mta_msg_id | List of mta_msg_id. | Optional |
| traffic_type | Traffic type defaults to inbound. To handle outbound traffic, set the traffic_type parameter to outbound. Possible values are: inbound, outbound. | Optional |
| verdict | List of verdicts. | Optional |
| limit | Number of alerts to include in response. Valid range: 1-200. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| FireEyeETP.Alerts.domain | unknown | Domain. |
| FireEyeETP.Alerts.report_id | unknown | Report_id. |
| FireEyeETP.Alerts.id | unknown | The alert unique ID. |
| FireEyeETP.Alerts.alert_date | unknown | The alert date. |
fireeye-etp-download-alert-case-files
Downloads all case files for the specified alert ID as a ZIP file. You can obtain the alert ID from the Alert Summary response, for example: “id”: “AV7zzRy7kvIwrKcfu0I”.
The downloaded zip is password protected, and the password is “infected”.
Base Command
fireeye-etp-download-alert-case-files
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The alert ID. | Required |
Context Output
There is no context output for this command.
fireeye-etp-list-yara-rulesets
Fetch the list of YARA rulesets and return a list with all the rules.
Base Command
fireeye-etp-list-yara-rulesets
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_uuid | Universally unique identifier (UUID) of the policy. (Can be found in the URL of the ETP Policies). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| FireEyeETP.Policy | unknown | The policy id. |
fireeye-etp-download-yara-file
Downloads a YARA file.
Base Command
fireeye-etp-download-yara-file
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_uuid | Universally unique identifier (UUID) of the policy. (Can be found in the URL of the ETP Policies). | Required |
| ruleset_uuid | Universally unique identifier (UUID) of the ruleset. | Required |
Context Output
There is no context output for this command.
fireeye-etp-upload-yara-file
Update or replace the YARA rule file in the existing ruleset.
Base Command
fireeye-etp-upload-yara-file
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_uuid | Universally unique identifier (UUID) of the policy. (Can be found in the URL of the ETP Policies). | Required |
| ruleset_uuid | Universally unique identifier (UUID) of the ruleset. | Required |
| entryID | Entry ID of yara file to upload. | Required |
Context Output
There is no context output for this command.
fireeye-etp-get-events-data
Returns all events of the alert by the alert ID.
Base Command
fireeye-etp-get-events-data
Input
| Argument Name | Description | Required |
|---|---|---|
| message_id | Message ID of alert. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| FireEyeETP.Events | unknown | The events of the alert. |
| FireEyeETP.Events.Delivered_msg | unknown | Display if event is delivered successfully or not. |
| FireEyeETP.Events.Delivered_status | unknown | The status of the message. |
| FireEyeETP.Events.InternetMessageId | unknown | The internet message ID of the alert. |
| FireEyeETP.Events.Logs | unknown | The logs of the alert. |
fireeye-etp-quarantine-release
Releases the email file present in the quarantine for the given email. Cloud message ID.
Base Command
fireeye-etp-quarantine-release
Input
| Argument Name | Description | Required |
|---|---|---|
| message_id | The message ID. | Optional |
Context Output
There is no context output for this command.
Configuration parameters
server— Server URL (required)credentials— Client ID (OAuth)oauth_scopes— OAuth Scopes (OAuth)token_url— Token URLapi_key— API keycredentials_api_key—unsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsfetch_time— First fetch timestamp.incidentFetchInterval— Incidents Fetch Intervalincidents_per_fetch— Max incidents per fetchincidentType— Incident typemessage_status— Alerts statuses to import
Commands (12)
-
fireeye-etp-download-alert-artifactDeprecatedDownloads all case files of the alert specified by the alert ID, in a zip file. You can obtain the ID from the Alert Summary response, for example "id": "AV7zzRy7kvIwrKcfu0I". Deprecated. Use the fireeye-etp-download-alert-case-files instead.
-
fireeye-etp-download-alert-case-filesDownloads all case files of the alert specified by the alert ID, in a zip file. You can obtain the ID from the Alert Summary response, for example "id": "AV7zzRy7kvIwrKcfu0I". The downloaded zip is password protected, and the password is "infected".
-
fireeye-etp-download-yara-fileDownloads a YARA file.
-
fireeye-etp-get-alertDeprecatedDetailed information from any particular alert. Alerts more than 90 days old are not available. Deprecated. Use the fireeye-etp-list-alerts instead.
-
fireeye-etp-get-alertsDeprecatedGet summary format information about the alerts. Deprecated. Use the fireeye-etp-list-alerts instead.
-
fireeye-etp-get-events-dataReturns all events of the alert by the alert ID.
-
fireeye-etp-get-messageGet the data of a specific message.
-
fireeye-etp-list-alertsGet summary format information about the alerts.
-
fireeye-etp-list-yara-rulesetsFetch the list of YARA rulesets and return a list with all the rules.
-
fireeye-etp-quarantine-releaseReleases the email file present in the quarantine for the given email. Cloud message ID.
-
fireeye-etp-search-messagesSearch for messages that include specified message attributes that are accessible in the ETP portal.
-
fireeye-etp-upload-yara-fileUpdate or replace the YARA rule file in the existing ruleset.
import json from unittest.mock import MagicMock, mock_open, patch import demistomock as demisto import FireEyeETP import pytest from CommonServerPython import CommandResults, EntryType, tableToMarkdown from FireEyeETP import Client def test_malware_readable_data(): """ Given: A dict with only "name" key When: calling malware_readable_data method on it Then: Ensure execution does not raise exception on it """ from FireEyeETP import malware_readable_data try: malware_readable_data({"name": "some-name"}) except KeyError: raise AssertionError("malware_readable_data method should not fail on dict with name key only") def test_get_alert_command(mocker, requests_mock): """ Given: - ID of alert to get - The alert object contain unicode When: - Running get-alert command Then: - Ensure command runs successfully - Ensure results are returned """ import FireEyeETP base_url = "https://server_url/api/v1" mocker.patch("FireEyeETP.BASE_PATH_V1", base_url) mocker.patch("FireEyeETP.API_KEY", "fake-api-key") mocker.patch.object(demisto, "args", return_value={"alert_id": "KgBdei7RQS4u4m8Jl7mG"}) mocker.patch.object(demisto, "results") requests_mock.get( base_url + "/alerts/KgBdei7RQS4u4m8Jl7mG?", json={ "meta": {"total": 1}, "data": [ { "alert": {"explanation": {"malware_detected": {"malware": {}}}}, "email": {"headers": {"to": "\u200b"}, "timestamp": {}}, } ], }, ) FireEyeETP.get_alert_command() results = demisto.results.call_args[0][0] assert results def test_fetch_incident_by_status_messages(mocker): """ Given: - A status message similar to the alert's status When: - Running fetch-incidents command (v2 implementation) Then: - Ensure one incident was fetched as expected - Ensure the correct v2 request functions were called """ from FireEyeETP import Client mock_client = MagicMock(spec=Client) response_1 = { "meta": {"search_after": "token123", "size": 1}, "data": [{"id": "alert1", "email_status": "delivered (retroactive)"}], } response_2 = {"id": "alert1", "alert": {"occurred": "2023-02-09T19:34:17Z", "severity": "minr"}} mocker.patch.object(FireEyeETP, "MESSAGE_STATUS", ["delivered (retroactive)"]) mocker.patch("FireEyeETP.demisto.getLastRun", return_value={}) mock_client.get_alerts_request_v2.return_value = response_1 mock_client.get_alert_request_v2.return_value = response_2 mocker.patch("FireEyeETP.demisto.setLastRun") incidents, last_run = FireEyeETP.fetch_incidents(mock_client) assert incidents[0].get("name") == "alert1" assert incidents[0].get("rawJSON") == json.dumps(response_2) assert last_run.get("pagination_token") == "token123" def test_fetch_incident_by_status_messages_mismatch_status(mocker): """ Given: - A status message differs from the alert's status When: - Running fetch-incidents command (v2 implementation) Then: - Ensure no incidents were fetched as expected due to filtering """ from FireEyeETP import Client mock_client = MagicMock(spec=Client) response_1 = { "meta": {"search_after": "token123", "size": 1}, "data": [{"id": "alert1", "email_status": "deleted"}], } mocker.patch.object(FireEyeETP, "MESSAGE_STATUS", ["delivered (retroactive)"]) mocker.patch("FireEyeETP.demisto.getLastRun", return_value={}) mock_client.get_alerts_request_v2.return_value = response_1 mocker.patch("FireEyeETP.demisto.setLastRun") incidents, _ = FireEyeETP.fetch_incidents(mock_client) assert incidents == [] def test_fetch_incident_by_status_messages_with_two_status(mocker): """ Given: - A list of status messages matching the alerts' statuses When: - Running fetch-incidents command (v2 implementation) Then: - Ensure 2 incidents were fetched as expected """ from FireEyeETP import Client mock_client = MagicMock(spec=Client) response_1 = { "meta": {"search_after": "token456", "size": 2}, "data": [{"id": "alert1", "email_status": "delivered (retroactive)"}, {"id": "alert2", "email_status": "deleted"}], } response_alert1 = {"id": "alert1", "alert": {"occurred": "2023-02-09T19:34:17Z", "severity": "minr"}} response_alert2 = {"id": "alert2", "alert": {"occurred": "2023-02-09T19:34:18Z", "severity": "majr"}} mocker.patch.object(FireEyeETP, "MESSAGE_STATUS", ["delivered (retroactive)", "deleted"]) mocker.patch("FireEyeETP.demisto.getLastRun", return_value={}) mock_client.get_alerts_request_v2.return_value = response_1 mock_client.get_alert_request_v2.side_effect = [response_alert1, response_alert2] mocker.patch("FireEyeETP.demisto.setLastRun") incidents, _ = FireEyeETP.fetch_incidents(mock_client) assert len(incidents) == 2 assert incidents[0].get("name") == "alert1" assert incidents[1].get("name") == "alert2" @pytest.fixture def FireEyeETP_client(): return Client(base_url="https://fireeyeetp", verify=False, headers={}, proxy=False) @patch("FireEyeETP.fileResult") def test_download_alert_artifacts_command(mock_file_result): """ Given: - ID of alert to get When: - Running download-alert-artifact command Then: - Ensure 1 zip file fetched as expected """ from FireEyeETP import Client, download_alert_artifacts_command args = {"alert_id": "12345"} mock_client = MagicMock(spec=Client) mock_response = MagicMock() mock_response.content = b"fake_zip_content" mock_client.get_artifacts.return_value = mock_response mock_file_result.return_value = {"File": "12345.zip", "Type": EntryType.FILE, "Contents": "fake_zip_content"} results = download_alert_artifacts_command(mock_client, args) mock_client.get_artifacts.assert_called_once_with("12345") mock_file_result.assert_called_once_with("12345.zip", data=b"fake_zip_content", file_type=EntryType.FILE) assert isinstance(results[0], CommandResults) assert results[0].readable_output == "Download alert artifact completed successfully" assert results[1] == {"File": "12345.zip", "Type": EntryType.FILE, "Contents": "fake_zip_content"} def test_list_yara_rulesets_command(): """ Given: - Policy UUID to get When: - Running list-yara-rulesets command Then: - Ensure command runs successfully - Ensure results are returned """ from FireEyeETP import Client, list_yara_rulesets_command args = {"policy_uuid": "abc-123-uuid"} mock_client = MagicMock(spec=Client) mock_response = { "data": { "rulesets": [ {"name": "Test Ruleset", "description": "Test Description", "uuid": "uuid-123", "yara_file_name": "test.yara"} ] } } mock_client.get_yara_rulesets.return_value = mock_response result = list_yara_rulesets_command(mock_client, args) mock_client.get_yara_rulesets.assert_called_once_with("abc-123-uuid") assert isinstance(result, CommandResults) assert result.outputs == [ {"name": "Test Ruleset", "description": "Test Description", "uuid": "uuid-123", "yara_file_name": "test.yara"} ] assert result.outputs_prefix == "FireEyeETP.Policy.abc-123-uuid" assert result.readable_output == ( "### Rulesets\n|name|description|uuid|yara_file_name|\n|---|---|---|---|\n| Test Ruleset" " | Test Description | uuid-123 | test.yara |\n" ) @patch("FireEyeETP.fileResult") def test_download_yara_file_command(mock_file_result): """ Given: - Policy UUID to get - Ruleset UUID to get When: - Running download-yara-file command Then: - Ensure command runs successfully - Ensure 1 yara file fetched as expected """ from FireEyeETP import Client, download_yara_file_command args = {"policy_uuid": "policy-12345", "ruleset_uuid": "ruleset-67890"} mock_client = MagicMock(spec=Client) mock_response = MagicMock() mock_response.content = b"fake_yara_file_content" mock_file_result.return_value = {"File": "original.yara", "Type": EntryType.FILE, "Contents": "fake_yara_file_content"} mock_client.get_yara_file.return_value = mock_response results = download_yara_file_command(mock_client, args) mock_file_result.assert_called_once_with("original.yara", data=b"fake_yara_file_content", file_type=EntryType.FILE) assert isinstance(results[0], CommandResults) assert results[0].readable_output == "Download yara file completed successfully." assert results[1] == {"File": "original.yara", "Type": EntryType.FILE, "Contents": "fake_yara_file_content"} @patch("FireEyeETP.demisto.getFilePath") @patch("FireEyeETP.open", new_callable=mock_open, read_data=b"fake_yara_file_content") def test_upload_yara_file_command_success(mock_open_file, mock_getFilePath): """ Given: - Policy UUID to get - Ruleset UUID to get - EntryID of context file to put When: - Running upload-yara-file command Then: - Ensure command runs successfully - Ensure 1 yara file uploaded as expected """ from FireEyeETP import Client, upload_yara_file_command args = {"entryID": "1", "policy_uuid": "policy-12345", "ruleset_uuid": "ruleset-67890"} mock_getFilePath.return_value = {"path": "/path/to/file"} mock_response = MagicMock() mock_response.status_code = 202 mock_client = MagicMock(spec=Client) mock_client.upload_yara_file.return_value = mock_response results = upload_yara_file_command(mock_client, args) mock_getFilePath.assert_called_once_with("1") mock_open_file.assert_called_once_with("/path/to/file", "rb") assert isinstance(results, CommandResults) assert results.readable_output == "Upload of Yara file succesfully." @patch("FireEyeETP.demisto.getFilePath") @patch("FireEyeETP.open", new_callable=mock_open, read_data=b"fake_yara_file_content") def test_upload_yara_file_command_failure(mock_open_file, mock_getFilePath): """ Given: - Policy UUID to get - Ruleset UUID to get - EntryID of context file to put When: - Running upload-yara-file command Then: - Ensure no yara file uploaded as expected """ from FireEyeETP import Client, upload_yara_file_command args = {"entryID": "1", "policy_uuid": "policy-12345", "ruleset_uuid": "ruleset-67890"} mock_getFilePath.return_value = {"path": "/path/to/file"} mock_response = MagicMock() mock_response.status_code = 400 mock_client = MagicMock(spec=Client) mock_client.upload_yara_file.return_value = mock_response results = upload_yara_file_command(mock_client, args) mock_getFilePath.assert_called_once_with("1") mock_open_file.assert_called_once_with("/path/to/file", "rb") assert isinstance(results, CommandResults) assert results.readable_output == "Upload of Yara file failed." def test_get_events_data_command_delivered(): """ Given: - Message ID to get When: - Running get-events-data command Then: - Ensure command runs successfully - Ensure results are returned """ from FireEyeETP import Client, get_events_data_command args = {"message_id": "12345"} mock_response = { "data": {"12345": [{"action_on_msg": "MTA_RCPT_DELIVERED_OUTBOUND", "display_msg": "Delivered <internetMessageId12345>"}]} } mock_client = MagicMock(spec=Client) mock_client.get_events_data.return_value = mock_response result = get_events_data_command(mock_client, args) assert isinstance(result, CommandResults) expected_output = { "Logs": mock_response["data"]["12345"], "Delivered_msg": "Delivered <internetMessageId12345>", "Delivered_status": "Delivered", "InternetMessageId": "internetMessageId12345", } assert result.outputs == expected_output assert result.outputs_prefix == "FireEyeETP.Events" expected_md = tableToMarkdown( "Events", expected_output, headers=["Logs", "Delivered_msg", "Delivered_status"], is_auto_json_transform=True ) assert result.readable_output == expected_md def test_get_events_data_command_failed(): """ Given: - Message ID to get When: - Running get-events-data command Then: - Ensure results are not returned """ from FireEyeETP import Client, get_events_data_command args = {"message_id": "12345"} mock_response = { "data": { "12345": [ { "action_on_msg": "MTA_RCPT_DELIVERY_PERM_FAILURE_OUTBOUND", "display_msg": "Failed to deliver <internetMessageId67890>", } ] } } mock_client = MagicMock(spec=Client) mock_client.get_events_data.return_value = mock_response result = get_events_data_command(mock_client, args) assert isinstance(result, CommandResults) expected_output = { "Logs": mock_response["data"]["12345"], "Delivered_msg": "Failed to deliver <internetMessageId67890>", "Delivered_status": "Failed", } assert result.outputs == expected_output assert result.outputs_prefix == "FireEyeETP.Events" expected_md = tableToMarkdown( "Events", expected_output, headers=["Logs", "Delivered_msg", "Delivered_status"], is_auto_json_transform=True ) assert result.readable_output == expected_md class MockResponse: def __init__(self, data): self.data = data def json(self): return self.data def test_quarantine_release_command(mocker): """ Given: - Message ID to get When: - Running quarantine-release command Then: - Ensure command runs successfully - Ensure results are returned - Ensure message send succesfully to quarantine """ from FireEyeETP import Client, quarantine_release_command response_data = {"data": {"type": "some_type", "operation": "some_operation", "successful_message_ids": "1,2,3"}} mock_response = MockResponse(response_data) args = {"message_id": "12345"} mock_client = MagicMock(spec=Client) mock_client.quarantine_release.return_value = mock_response result = quarantine_release_command(mock_client, args) assert isinstance(result, CommandResults) assert result.readable_output == ( "### Quarantine\n|type|operation|successful_message_ids|\n|---|---|---|\n| some_type | some_operation | 1,2,3 |\n" ) mock_client.quarantine_release.assert_called_once_with("12345") @pytest.mark.parametrize( "client_id, client_secret, api_key, scopes, expected_result, expected_exception", [ # Case 1: SUCCESS - Full OAuth2 configuration ("id", "secret", "", "scope", "oauth2", None), # Case 2: SUCCESS - API Key configuration ("", "", "key", "", "api_key", None), # Case 3: FAILURE - Ambiguous Over-Configuration (OAuth2 + API Key) ("id", "secret", "key", "scope", None, r"Both OAuth2 \(Client ID/Secret\) and API Key were provided\..*"), # Case 4: FAILURE - Incomplete OAuth2 (Missing Scopes) ("id", "secret", "", "", None, r".*'OAuth Scopes' parameter is missing\..*"), # Case 5 (FIXED): FAILURE - Incomplete OAuth2 (Missing Secret) # We expect the error message for MISSING SECRET. ("id", "", "", "scope", None, r"Client ID provided but Client Secret is missing\..*"), # Case 6 (FIXED): FAILURE - Incomplete OAuth2 (Missing ID) # We expect the error message for MISSING ID. ("", "secret", "", "scope", None, r"Client Secret provided but Client ID is missing\..*"), # Case 7: FAILURE - No credentials provided ("", "", "", "", None, r"No authentication credentials provided\."), ], ) def test_validate_authentication_params_parametrized( client_id, client_secret, api_key, scopes, expected_result, expected_exception, mocker ): """ Given: - A set of authentication parameters (client ID, client secret, API key, scopes). When: - Calling the validate_authentication_params function. Then: - Ensure the function returns the expected authentication method ('oauth2' or 'api_key'), OR - Ensure the function raises the expected ValueError for invalid or over-configured parameters. """ mocker.patch("FireEyeETP.CLIENT_ID", client_id) mocker.patch("FireEyeETP.CLIENT_SECRET", client_secret) mocker.patch("FireEyeETP.API_KEY", api_key) mocker.patch("FireEyeETP.SCOPES", scopes) from FireEyeETP import validate_authentication_params if expected_exception: with pytest.raises(ValueError, match=expected_exception): validate_authentication_params() else: assert validate_authentication_params() == expected_result def test_convert_to_demisto_severity(): from FireEyeETP import convert_to_demisto_severity from CommonServerPython import IncidentSeverity assert convert_to_demisto_severity("crit") == IncidentSeverity.CRITICAL assert convert_to_demisto_severity("majr") == IncidentSeverity.HIGH assert convert_to_demisto_severity("minr") == IncidentSeverity.LOW assert convert_to_demisto_severity("unkn") == IncidentSeverity.UNKNOWN def test_get_search_alert_summary_v2(): from FireEyeETP import get_search_alert_summary_v2 alert = { "id": "1", "sha256": "s", "md5": "m", "domain": "d", "original": "o", "report_id": "r", "alert_date": "date", "malware": [{"name": "mn", "stype": "ms"}, {"name": "mn2", "stype": "ms2"}], "email_status": "es", } res = get_search_alert_summary_v2(alert) assert res["Alert ID"] == "1" assert res["Malware name"] == ["mn", "mn2"] assert res["Malware stype"] == ["ms", "ms2"] def test_get_single_alert_summary_v2(): from FireEyeETP import get_single_alert_summary_v2 alert = { "id": "1", "domain": "d", "msg": "m", "traffic_type": "t", "verdict": "v", "report_id": "r", "alert_date": "ad", "product": "p", "alert": {"occurred": "o", "name": "n", "attack-time": "at", "severity": "s"}, } res = get_single_alert_summary_v2(alert) assert res["Alert ID"] == "1" assert res["Severity"] == "s" def test_get_alert_list_with_alert_id(mocker): """ Given: - An alert_id argument is provided When: - Running get_alert_list function Then: - Ensure get_single_alert_entry is called - Ensure the correct CommandResults is returned """ from FireEyeETP import get_alert_list, Client mock_client = MagicMock(spec=Client) alert_data = { "id": "alert123", "domain": "example.com", "msg": "test message", "traffic_type": "email", "verdict": "malicious", "report_id": "report1", "alert_date": "2023-01-01", "product": "ETP", "alert": { "occurred": "2023-01-01T10:00:00Z", "name": "Test Alert", "attack-time": "2023-01-01T09:00:00Z", "severity": "majr", }, } mocker.patch("FireEyeETP.demisto.args", return_value={"alert_id": "alert123"}) mock_client.get_alert_request_v2.return_value = alert_data result = get_alert_list(mock_client) assert isinstance(result, CommandResults) assert result.outputs_prefix == "FireEyeETP.Alerts" assert result.outputs_key_field == "id" assert result.outputs == alert_data @pytest.mark.parametrize( "token_url_value, expected_url", [ # Default commercial URL when no override is configured (TOKEN_URL stays as DEFAULT_TOKEN_URL) ( "https://auth.trellix.com/auth/realms/IAM/protocol/openid-connect/token", "https://auth.trellix.com/auth/realms/IAM/protocol/openid-connect/token", ), # GovCloud override — user provides base URL, code appends /iam/v1.0/token via urljoin ( "https://iam.us.trellix-gov.com/iam/v1.0/token", "https://iam.us.trellix-gov.com/iam/v1.0/token", ), ], ) def test_fetch_oauth_token_uses_configured_url(mocker, token_url_value, expected_url): """ Given: - A resolved TOKEN_URL (default commercial or GovCloud after urljoin). When: - Calling fetch_oauth_token. Then: - Ensure the OAuth token request is sent to the resolved URL. """ mocker.patch("FireEyeETP.TOKEN_URL", token_url_value) mocker.patch("FireEyeETP.CLIENT_ID", "test_id") mocker.patch("FireEyeETP.CLIENT_SECRET", "test_secret") mocker.patch("FireEyeETP.SCOPES", "etp.alrt.ro") mocker.patch("FireEyeETP.USE_SSL", True) mocker.patch("FireEyeETP.get_integration_context", return_value={}) mocker.patch("FireEyeETP.set_integration_context") mock_response = MagicMock() mock_response.json.return_value = {"access_token": "test_token", "expires_in": 600} mock_response.raise_for_status.return_value = None mock_post = mocker.patch("FireEyeETP.requests.post", return_value=mock_response) token = FireEyeETP.fetch_oauth_token() assert token == "test_token" mock_post.assert_called_once() actual_url = mock_post.call_args[0][0] assert actual_url == expected_url def test_get_alert_list_without_alert_id(mocker): """ Given: - No alert_id argument is provided (search parameters instead) When: - Running get_alert_list function Then: - Ensure get_alerts_entry is called - Ensure the correct CommandResults is returned with multiple alerts """ from FireEyeETP import Client, get_alert_list mock_client = MagicMock(spec=Client) search_response = { "data": [ { "id": "alert1", "sha256": "sha1", "md5": "md5_1", "domain": "example1.com", "original": "orig1", "report_id": "r1", "alert_date": "2023-01-01", "malware": [{"name": "malware1", "stype": "type1"}], "email_status": "delivered", }, { "id": "alert2", "sha256": "sha2", "md5": "md5_2", "domain": "example2.com", "original": "orig2", "report_id": "r2", "alert_date": "2023-01-02", "malware": [{"name": "malware2", "stype": "type2"}], "email_status": "quarantined", }, ] } mocker.patch("FireEyeETP.demisto.args", return_value={"limit": "10", "domain": "example.com"}) mock_client.get_alerts_request_v2.return_value = search_response result = get_alert_list(mock_client) assert isinstance(result, CommandResults) assert result.outputs_prefix == "FireEyeETP.Alerts" assert result.outputs_key_field == "id" assert len(result.outputs) == 2 assert result.outputs[0]["id"] == "alert1" assert result.outputs[1]["id"] == "alert2"