IronDefense
The IronDefense Integration for Cortex XSOAR allows users to interact with IronDefense alerts within Cortex XSOAR. The Integration provides the ability to rate alerts, update alert statuses, add comments to alerts, to report observed bad activity, get alerts, get events, and get IronDome information.
Network Security · IronNet
Details
| ID | IronDefense |
|---|---|
| Provider | IronNet Cybersecurity |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
IronDefense gives users the ability to rate alerts, update alert statuses, add comments to alerts, and to report observed bad activity.
Configure IronDefense on XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for IronDefense.
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- IronAPI Host/IP
- IronAPI Port
- Username
- Request Timeout (Sec)
- Click Test to validate the new instance.
Commands
You can execute these commands from the XSOAR 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.
- Rate an alert: irondefense-rate-alert
- Add a comment to an alert: irondefense-comment-alert
- Set the status of an alert: irondefense-set-alert-status
- Submit an observed bad endpoint to create Threat Intelligence Rules (TIR): irondefense-report-observed-bad-activity
1. Rate an alert
Rates an IronDefense alert.
Base Command
irondefense-rate-alert
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The ID of the IronDefense alert. | Required |
| severity | The severity rating of the alert. Can be: "Undecided", "Benign", "Suspicious", "Malicious". | Required |
| expectation | Determines whether the rating was expected. Can be: "Unknown", "Expected", "Unexpected". Use "Unknown" if the rating is undecided. | Required |
| comments | Explains the rating of the alert. | Required |
| share_comment_with_irondome | Whether to share the comment with IronDome. | Required |
Context Output
There are no context output for this command.
2. Add a comment to an alert
Adds a comment to an IronDefense alert.
Base Command
irondefense-comment-alert
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The ID of the IronDefense alert. | Required |
| comment | Explains the rating of the alert. | Required |
| share_comment_with_irondome | Whether to share the comment with IronDome. | Required |
Context Output
There are no context output for this command.
3. Set the status of an alert
Sets the status of an IronDefense alert.
Base Command
irondefense-set-alert-status
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The ID of the IronDefense alert. | Required |
| status | The alert status to set. Can be: "Awaiting Review", "Under Review", "Closed". | Required |
| comments | Explains the status of the alert. | Required |
| share_comment_with_irondome | Whether to share the comment with IronDome. | Required |
Context Output
There are no context output for this command.
4. Submit an observed bad endpoint to create Threat Intelligence Rules (TIR)
Submits an observed bad endpoint to IronDefense to create Threat Intelligence Rules (TIR).
Base Command
irondefense-report-observed-bad-activity
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The name of the Threat Intelligence Rule (TIR) to be created. | Required |
| description | A description of the observed bad endpoint. | Required |
| ip | The IP address of the observed bad endpoint. | Optional |
| domain | The domain name of the observed bad endpoint. | Optional |
| activity_start_time | The start time of the observed bad activity in RFC 3339 format. | Required |
| activity_end_time | The end time of the observed bad activity in RFC 3339 format. | Required |
Context Output
There are no context output for this command.
irondefense-get-event
Retrieves an IronDefense event.
Base Command
irondefense-get-event
Input
| Argument Name | Description | Required |
|---|---|---|
| event_id | The ID of the IronDefense event. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IronDefense.Event.id | String | The event ID. |
| IronDefense.Event.alert_id | String | The associated alert ID. |
| IronDefense.Event.category | String | The event category. |
| IronDefense.Event.sub_category | String | The event sub category. |
| IronDefense.Event.severity | Number | The event severity (0-1000). |
| IronDefense.Event.confidence | Number | The event confidence (real number between 0-1). |
| IronDefense.Event.created | Date | Time the event was created. |
| IronDefense.Event.updated | Date | Time the event was last updated. |
| IronDefense.Event.start_time | Date | The start time of this event’s activity. |
| IronDefense.Event.end_time | Date | The end time of this event’s activity. |
| IronDefense.Event.iron_dome_shared_time | Date | The time when the event was sent to IronDome - not present if not shared with irondome. |
| IronDefense.Event.is_whitelisted | Boolean | True if the event activity was whitelisted, false otherwise. |
| IronDefense.Event.is_blacklisted | Boolean | True if the event activity was blacklisted, false otherwise. |
| IronDefense.Event.src_ip | String | The source IP associated with this event. |
| IronDefense.Event.dst_ip | String | The destination IP associated with this event. |
| IronDefense.Event.dst_port | Number | The destination port associated with this event. |
| IronDefense.Event.ppp_domains | String | Any domains associated with this event. |
| IronDefense.Event.primary_app_protocol | String | The primary application protocol associated with this event. |
| IronDefense.Event.secondary_app_protocol | String | The secondary application protocol associated with this event. |
| IronDefense.Event.bytes_in | Number | The byte count of incoming traffic for this event. |
| IronDefense.Event.bytes_out | Number | The byte count of outgoing traffic for this event. |
| IronDefense.Event.total_bytes | Number | The byte count of the total traffic (in either direction) for this event. |
| IronDefense.Event.url | String | The related URL for this event, if applicable. |
| IronDefense.Event.raw_data_formats | String | The list of distinct raw data formats for this event. |
| IronDefense.Event.src_entity_attribute | String | The source entity attribute related to this event, if one could be determined. |
| IronDefense.Event.src_entity_attribute_type | String | The source entity attribute type related to this event, if one could be determined. |
| IronDefense.Event.dst_entity_attribute | String | The destination entity attribute related to this event, if one could be determined. |
| IronDefense.Event.dst_entity_attribute_type | String | The destination entity attribute type related to this event, if one could be determined. |
| IronDefense.Event.vue_url | String | The url for displaying the event within IronVUE. |
| IronDefense.Event.Context.name | String | The name of the context table. |
| IronDefense.Event.Context.columns | String | Column values for the context table. |
irondefense-get-alerts
Pulls Alerts from IronDefense.
Base Command
irondefense-get-alerts
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The list of Alert IDs to filter by. | Optional |
| category | The list of Alert categories to filter by. Possible values are: C2, Action, Access, Recon, Other. | Optional |
| sub_category | The list of Alert sub categories to filter by. | Optional |
| status | The list of Alert status to filter by. Possible values are: Awaiting Review, Under Review, Closed. | Optional |
| analyst_severity | The list of Alert analyst severity to filter by. Possible values are: Undecided, Benign, Suspicious, Malicious. | Optional |
| analyst_expectation | The list of Alert analyst expectation to filter by. Possible values are: Unknown, Expected, Unexpected. | Optional |
| min_severity | The minimum Alert severity to filter by. (0-1000). | Optional |
| max_severity | The maximum Alert severity to filter by. (0-1000). | Optional |
| min_created | The minimum Alert created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| max_created | The maximum Alert created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| min_updated | The minimum Alert updated date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| max_updated | The maximum Alert updated date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| min_first_event_created | The minimum Alert first event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| max_first_event_created | The maximum Alert first event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| min_last_event_created | The minimum Alert last event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| max_last_event_created | The maximum Alert last event created date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| min_first_event_start_time | The minimum Alert first event start date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| max_first_event_start_time | The maximum Alert first event start date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| min_last_event_end_time | The minimum Alert last event end date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| max_last_event_end_time | The maximum Alert last event end date to filter by in RFC 3339 format (E.g. 2017-10-13T07:20:50.52Z). | Optional |
| analytic_version | The list of Alert analytic versions to filter by. | Optional |
| limit | The limit on the number of Alerts to be returned. | Optional |
| offset | The number of results to skip - used for paging the results. | Optional |
| sort | The list of Alert fields to sort by. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IronDefense.Alert.id | string | The ID of the alert. |
| IronDefense.Alert.category | string | The category of the alert. |
| IronDefense.Alert.sub_category | string | The subcategory of the alert. |
| IronDefense.Alert.severity | Number | The severity score of the alert. (0-1000) |
| IronDefense.Alert.status | String | The status of the alert. |
| IronDefense.Alert.analyst_severity | String | The analyst severity of the alert. |
| IronDefense.Alert.analyst_expectation | String | The analyst expectation of the alert. |
| IronDefense.Alert.created | Date | Time the alert was created in IronDefense. |
| IronDefense.Alert.updated | Date | Time the alert was last updated in IronDefense. |
| IronDefense.Alert.event_count | Number | The number of non-whitelisted events associated with this alert. |
| IronDefense.Alert.FirstEventCreated | String | The earliest created date of any associated event on this alert. |
| IronDefense.Alert.last_event_created | String | The last created date of any associated event on this alert. |
| IronDefense.Alert.raw_data_format | String | List of distinct raw data formats for this event. |
| IronDefense.Alert.aggregation_criteria | String | Criteria used to build alert, specific to the event context fields. |
| IronDefense.Alert.vue_url | String | The url for displaying the alert within IronVUE. |
| IronDefense.Query.GetAlerts.limit | Number | The maximum number of results that were requested to be returned. |
| IronDefense.Query.GetAlerts.offset | Number | The number of results that were skipped - used for paging the results. |
| IronDefense.Query.GetAlerts.total | Number | The total number of results possible from the query. |
irondefense-get-alert-irondome-information
Retrieves IronDome information for an IronDefense alert.
Base Command
irondefense-get-alert-irondome-information
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The ID of the IronDefense alert. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| IronDome.Correlations.alert_id | String | The alert ID the correlations are associated with. |
| IronDome.Correlations.dome_tag | String | The IronDome tag. |
| IronDome.Correlations.correlation.correlations.ip | String | The IP correlated on (if an IP correlation). |
| IronDome.Correlations.correlation.correlations.domain | String | The Domain correlated on (if a Domain correlation). |
| IronDome.Correlations.correlation.correlations.behavior | Boolean | True if the correlation was behavior-based. |
| IronDome.Correlations.correlation.correlations.enterprise_correlations | Number | The number of enterprise correlations. |
| IronDome.Correlations.correlation.correlations.community_correlations | Number | The number of community correlations. |
| IronDome.CorrelationParticipation.alert_id | String | The alert ID the correlation participants are associated with. |
| IronDome.CorrelationParticipation.correlation_participation.dome_tag | String | The IronDome tag. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.malicious_count | Number | Count of malicious ratings. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.suspicious_count | Number | Count of suspicious ratings. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.benign_count | Number | Count of benign ratings. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.whitelisted_count | Number | Count of whitelisted ratings. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.comments_count | Number | Count of comments made. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.activity_count | Number | Count of activity. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.resource_owner | Boolean | True if the caller is the resource owner. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.first_seen | Date | The time the activity was first seen. |
| IronDome.CorrelationParticipation.correlation_participation.behavior.last_seen | Date | The time the activity was last seen. |
| IronDome.CorrelationParticipation.correlation_participation.domain.malicious_count | Number | Count of malicious ratings. |
| IronDome.CorrelationParticipation.correlation_participation.domain.suspicious_count | Number | Count of suspicious ratings. |
| IronDome.CorrelationParticipation.correlation_participation.domain.benign_count | Number | Count of benign ratings. |
| IronDome.CorrelationParticipation.correlation_participation.domain.whitelisted_count | Number | Count of whitelisted ratings. |
| IronDome.CorrelationParticipation.correlation_participation.domain.comments_count | Number | Count of comments made. |
| IronDome.CorrelationParticipation.correlation_participation.domain.activity_count | Number | Count of activity. |
| IronDome.CorrelationParticipation.correlation_participation.domain.resource_owner | Boolean | True if the caller is the resource owner. |
| IronDome.CorrelationParticipation.correlation_participation.domain.first_seen | Date | The time the activity was first seen. |
| IronDome.CorrelationParticipation.correlation_participation.domain.last_seen | Date | The time the activity was last seen. |
| IronDome.CorrelationParticipation.correlation_participation.ip.malicious_count | Number | Count of malicious ratings. |
| IronDome.CorrelationParticipation.correlation_participation.ip.suspicious_count | Number | Count of suspicious ratings. |
| IronDome.CorrelationParticipation.correlation_participation.ip.benign_count | Number | Count of benign ratings. |
| IronDome.CorrelationParticipation.correlation_participation.ip.whitelisted_count | Number | Count of whitelisted ratings. |
| IronDome.CorrelationParticipation.correlation_participation.ip.comments_count | Number | Count of comments made. |
| IronDome.CorrelationParticipation.correlation_participation.ip.activity_count | Number | Count of activity. |
| IronDome.CorrelationParticipation.correlation_participation.ip.resource_owner | Boolean | True if the caller is the resource owner. |
| IronDome.CorrelationParticipation.correlation_participation.ip.first_seen | Date | The time the activity was first seen. |
| IronDome.CorrelationParticipation.correlation_participation.ip.last_seen | Date | The time the activity was last seen. |
| IronDome.CommunityComments.alert_id | String | The alert ID associated with the community comments. |
| IronDome.CommunityComments.community_comments.created | Date | The time that the comment was created. |
| IronDome.CommunityComments.community_comments.comment | String | The comment text. |
| IronDome.CommunityComments.community_comments.dome_tags | String | The IronDome tags related to the comment. |
| IronDome.CommunityComments.community_comments.enterprise | Boolean | True if enterprise. |
| IronDome.CommunityComments.community_comments.self | Boolean | True if the comment was made by the caller. |
| IronDome.CognitiveSystemScore.alert_id | String | The alert ID associated with the cognitive system score. |
| IronDome.CognitiveSystemScore.cognitive_system_score | Number | The cognitive system score of the alert. |
| IronDome.Notification.alert_id | String | The alert ID associated with the IronDome notification. |
| IronDome.Notification.dome_notification.id | String | The unique ID of the notification. |
| IronDome.Notification.dome_notification.category | String | The category of Dome notification. |
| IronDome.Notification.dome_notification.created | Date | The category of Dome notification. |
| IronDome.Notification.dome_notification.dome_tags | String | The IronDome tags related to this notification. |
| IronDome.Notification.dome_notification.alert_ids | String | The IDs of the alerts related to this notification. |
| IronDome.Notification.dome_notification.mismatch_details.enterprise_severities.analyst_severity | String | The mismatched severity across correlated alerts within the enterprise. |
| IronDome.Notification.dome_notification.mismatch_details.enterprise_severities.count | Number | The count of mismatched severity with this analyst rating. |
| IronDome.Notification.dome_notification.mismatch_details.community_severities.analyst_severity | String | The mismatched severity across correlated alerts within the community. |
| IronDome.Notification.dome_notification.mismatch_details.community_severities.count | Number | The count of mismatched severity with this analyst rating. |
| IronDome.Notification.dome_notification.severity_details.analyst_severity | String | The analyst severity of the alert. |
| IronDome.Notification.dome_notification.comment_details.comment | String | The comment text in the notification. |
| IronDome.Notification.dome_notification.severity_suspicious_details.domains | String | The list of domains correlated on a suspicious alert. |
| IronDome.Notification.dome_notification.severity_suspicious_details.ips | String | The list of IPs correlated on a suspicious alert. |
| IronDome.Notification.dome_notification.severity_suspicious_details.comments | String | The comments about this suspicious alert correlation. |
| IronDome.Notification.dome_notification.severity_malicious_details.domains | String | The list of domains correlated on a malicious alert. |
| IronDome.Notification.dome_notification.severity_malicious_details.ips | String | The list of IPs correlated on a malicious alert. |
| IronDome.Notification.dome_notification.severity_malicious_details.comments | String | The comments about this malicious alert correlation. |
| IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.ip | String | The IP of a Threat Intelligence rule that was automatically created based on an IronDome correlation. |
| IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.domain | String | The domain of a Threat Intelligence rule that was automatically created based on an IronDome correlation. |
| IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.rule_id | String | The rule ID of a Threat Intelligence rule that was automatically created based on an IronDome correlation. |
| IronDome.Notification.dome_notification.severity_malicious_details.generated_threat_intel_rules.vue_url | String | The IronVUE URL of a Threat Intelligence rule that was automatically created based on an IronDome correlation. |
| IronDome.Notification.dome_notification.high_cognitive_system_score_details.enterprise_alert_score | Number | The alert score for the enterprise. |
| IronDome.Notification.dome_notification.high_cognitive_system_score_details.cognitive_system_score | Number | The cognitive system score. |
irondefense-get-events-from-alert
Retrieves IronDefense Events for a given Alert ID.
Base Command
irondefense-get-events-from-alert
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The ID of the IronDefense alert to retrieve events for. | Required |
| limit | The limit on the number of Events to be returned. | Optional |
| offset | The number of results to skip - used for paging the results. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| IronDefense.Event.id | String | The event ID. |
| IronDefense.Event.alert_id | String | The associated alert ID. |
| IronDefense.Event.category | String | The event category. |
| IronDefense.Event.sub_category | String | The event sub category. |
| IronDefense.Event.severity | Number | The event severity (0-1000). |
| IronDefense.Event.confidence | Number | The event confidence (real number between 0-1). |
| IronDefense.Event.created | Date | Time the event was created. |
| IronDefense.Event.updated | Date | Time the event was last updated. |
| IronDefense.Event.start_time | Date | The start time of this event’s activity. |
| IronDefense.Event.end_time | Date | The end time of this event’s activity. |
| IronDefense.Event.iron_dome_shared_time | Date | The time when the event was sent to IronDome - not present if not shared with irondome. |
| IronDefense.Event.is_whitelisted | Boolean | True if the event activity was whitelisted, false otherwise. |
| IronDefense.Event.is_blacklisted | Boolean | True if the event activity was blacklisted, false otherwise. |
| IronDefense.Event.src_ip | String | The source IP associated with this event. |
| IronDefense.Event.dst_ip | String | The destination IP associated with this event. |
| IronDefense.Event.dst_port | Number | The destination port associated with this event. |
| IronDefense.Event.app_domains | String | Any domains associated with this event. |
| IronDefense.Event.primary_app_protocol | String | The primary application protocol associated with this event. |
| IronDefense.Event.secondary_app_protocol | String | The secondary application protocol associated with this event. |
| IronDefense.Event.bytes_in | Number | The byte count of incoming traffic for this event. |
| IronDefense.Event.bytes_out | Number | The byte count of outgoing traffic for this event. |
| IronDefense.Event.total_bytes | Number | The byte count of the total traffic (in either direction) for this event. |
| IronDefense.Event.url | String | The related URL for this event, if applicable. |
| IronDefense.Event.raw_data_formats | String | The list of distinct raw data formats for this event. |
| IronDefense.Event.src_entity_attribute | String | The source entity attribute related to this event, if one could be determined. |
| IronDefense.Event.src_entity_attribute_type | String | The source entity attribute type related to this event, if one could be determined. |
| IronDefense.Event.dst_entity_attribute | String | The destination entity attribute related to this event, if one could be determined. |
| IronDefense.Event.dst_entity_attribute_type | String | The destination entity attribute type related to this event, if one could be determined. |
| IronDefense.Event.vue_url | String | The url for displaying the event within IronVUE. |
| IronDefense.Query.GetEvents.limit | Number | The maximum number of results that were requested to be returned. |
| IronDefense.Query.GetEvents.offset | Number | The number of results that were skipped - used for paging the results. |
| IronDefense.Query.GetEvents.total | Number | The total number of results possible from the query. |
Configuration parameters
ironAPIHost— IronAPI Host/IP (required)ironAPIPort— IronAPI Port (required)credentials— Username (required)requestTimeout— Request Timeout (Sec)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalenableAlertNotifications— Enable Alert Notification Ingestion (Refresh Not Supported)alertLimit— Alert Notification Limit Per Request. (Max 500)alertCategories— Alert Notification Categories to ExcludealertSubCategories— Alert Notification SubCategories to Exclude, See https://ironnet.atlassian.net/wiki/x/hADvGg for values (Enter in comma separated list format)alertSeverityLower— Lower Bound of Severity for Alert Notification (0-1000)alertSeverityUpper— Upper Bound of Severity for Alert Notification (0-1000)alertActions— Alert Notification Actions to Include (Alert Created enabled by default)enableEventNotifications— Enable Event Notification Ingestion (Refresh Not Supported)eventLimit— Event Notification Limit Per Request. (Max 500)eventCategories— Event Notification Categories to ExcludeeventSubCategories— Event Notification SubCategories to Exclude, See https://ironnet.atlassian.net/wiki/x/hADvGg for values (Enter in comma separated list format)eventSeverityLower— Lower Bound of Severity for Event Notification (0-1000)eventSeverityUpper— Upper Bound of Severity for Event Notification (0-1000)eventActions— Event Notification Actions to Include (Event Created enabled by default)enableDomeNotifications— Enable Dome Notification Ingestion (Refresh Not Supported)domeLimit— Dome Notification Limit Per Request. (Max 500)domeCategories— Dome Notification Types to Exclude
Commands (8)
-
irondefense-comment-alertAdds a comment to an IronDefense alert.
-
irondefense-get-alert-irondome-informationRetrieves IronDome information for an IronDefense alert.
-
irondefense-get-alertsPulls Alerts from IronDefense.
-
irondefense-get-eventRetrieves an IronDefense event.
-
irondefense-get-events-from-alertRetrieves IronDefense Events for a given Alert ID.
-
irondefense-rate-alertRates an IronDefense alert.
-
irondefense-report-observed-bad-activitySubmits an observed bad endpoint to IronDefense to create Threat. Intelligence Rules (TIR).
-
irondefense-set-alert-statusSets the status of an IronDefense alert.
import json import unittest import demistomock as demisto import IronDefense as irondefense_module from IronDefense import IronDefense, LOG_PREFIX from unittest import TestCase, mock from unittest.mock import Mock, call import requests from http.client import HTTPException requests.packages.urllib3.disable_warnings() class IronDefenseTest(TestCase): COOKIE_KEY = "some_cookie_key" # Create a mock logger so nothing gets written to stdout demisto_logger_patcher = mock.patch("IronDefense.XsoarLogger", autospec=True) MockXsoarLogger = demisto_logger_patcher.start() MockXsoarLogger.debug = lambda msg: None MockXsoarLogger.error = lambda msg: None session_patcher = mock.patch("requests.Session", autospec=True) MockSession = session_patcher.start() def setUp(self): self.host = "dev.rva.ironnet.io" self.url_prefix = "/IronApi" self.port = 6942 self.credentials = {"identifier": "someusername", "password": "somepassword"} logger = self.MockXsoarLogger(demisto, LOG_PREFIX) self.mock_session = self.MockSession() self.mock_session.headers = {} # initialize the IronDefense object self.class_under_test = IronDefense(demisto, self.mock_session, self.host, self.port, self.credentials, logger) def tearDown(self): demisto.setIntegrationContext({}) def test_get_jwt(self): test_jwt = "jwt token" test_context = {"JWT": test_jwt} assert test_jwt == self.class_under_test._get_jwt(test_context), "Unexpected result" assert None is self.class_under_test._get_jwt(None), "None context should return None" assert None is self.class_under_test._get_jwt({"bogus": "context"}), "Missing jwt in context should return None" def test_configure_session_auth(self): self.mock_session.headers = {} # test no jwt token self.class_under_test._configure_session_auth({}) assert self.mock_session.headers.get("authentication", None) is None # test loading jwt token test_jwt = "jwt token" test_context = {"JWT": test_jwt} self.class_under_test._configure_session_auth(test_context) assert "Bearer " + test_jwt == self.mock_session.headers.get("Authorization") @mock.patch("requests.Response", autospec=True) def test_http_request(self, MockResponse): MockResponse.return_value.headers = {} method = "GET" uri = "/something" headers: dict[str, str] = {} data = "{}" params: dict[str, str] = {} files = None mock_jwt_value = "jwt token" expected_uri = f"https://{self.host}:{self.port}{self.url_prefix}{uri}" """Test successful response""" mock_response = MockResponse() mock_response.status_code = 200 self.mock_session.request.return_value = mock_response test_response = self.class_under_test._http_request(method, uri) self.mock_session.request.assert_called_with( method, expected_uri, headers=headers, data=data, params=params, files=files, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) assert test_response == mock_response """Test incorrect creds""" mock_response = MockResponse() mock_response.status_code = 401 self.mock_session.request.return_value = mock_response test_response = self.class_under_test._http_request(method, uri) self.mock_session.request.assert_called_with( method, expected_uri, headers=headers, data=data, params=params, files=files, timeout=self.class_under_test.request_timeout, auth=(self.credentials["identifier"], self.credentials["password"]), verify=False, ) assert test_response == mock_response """Test expired jwt""" self.mock_session.request.reset_mock() # create a class to return a mock 401 response after the initial call then a 200 response after class RequestEffect: def __init__(self): pass calls = 0 def side_effect(self, *args, **kwargs): self.calls += 1 side_effect_response = MockResponse() if self.calls > 1: side_effect_response.status_code = 200 side_effect_response.headers["auth-token"] = mock_jwt_value else: side_effect_response.status_code = 401 return side_effect_response self.mock_session.request.side_effect = RequestEffect().side_effect test_response = self.class_under_test._http_request(method, uri) self.mock_session.request.assert_has_calls( [ mock.call( method, expected_uri, headers=headers, data=data, params=params, files=files, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ), mock.call( method, expected_uri, headers=headers, data=data, params=params, files=files, timeout=self.class_under_test.request_timeout, auth=(self.credentials["identifier"], self.credentials["password"]), verify=False, ), ] ) # check to see if the jwt was stored assert demisto.getIntegrationContext() == {"JWT": mock_jwt_value} assert test_response.status_code == 200, "Unexpected status code" assert self.mock_session.request.call_count == 2, "_http_request should have made 2 calls" """Test 5xx response""" self.mock_session.request.reset_mock() self.mock_session.request.side_effect = None mock_response = MockResponse() mock_response.status_code = 500 mock_response.json.return_value = {"msg": "server error!"} self.mock_session.request.return_value = mock_response test_response = self.class_under_test._http_request(method, uri) self.mock_session.request.assert_called_with( method, expected_uri, headers=headers, data=data, params=params, files=files, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) assert test_response == mock_response @mock.patch("requests.Response", autospec=True) def test_test_module(self, MockResponse): MockResponse.return_value.headers = {} expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/Login") # test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response result = self.class_under_test.test_module() self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data="{}", params={}, files=None, timeout=self.class_under_test.request_timeout, auth=(self.credentials["identifier"], self.credentials["password"]), verify=False, ) assert result == "ok", 'Result should be "ok"' # test failed response error_json = {"msg": "Some error message"} error_msg = json.dumps(error_json) mock_response = MockResponse() mock_response.status_code = 500 mock_response.text = error_msg mock_response.json.return_value = error_json mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response result = self.class_under_test.test_module() self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data="{}", params={}, files=None, timeout=self.class_under_test.request_timeout, auth=(self.credentials["identifier"], self.credentials["password"]), verify=False, ) assert result != "ok", "Result should have an error message" @mock.patch("requests.Response", autospec=True) def test_update_analyst_ratings(self, MockResponse): MockResponse.return_value.headers = {} alert_id = "test_alert_id" severity = "Malicious" expectation = "Unexpected" comments = "test comments" share_irondome = True expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/RateAlert") expected_body = json.dumps( { "alert_id": alert_id, "analyst_severity": "SEVERITY_MALICIOUS", "analyst_expectation": "EXP_UNEXPECTED", "comment": comments, "share_comment_with_irondome": share_irondome, } ) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.update_analyst_ratings( alert_id, severity=severity, expectation=expectation, comments=comments, share_irondome=share_irondome ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises( HTTPException, self.class_under_test.update_analyst_ratings, alert_id, severity=severity, expectation=expectation, comments=comments, share_irondome=share_irondome, ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) @mock.patch("requests.Response", autospec=True) def test_add_comment_to_alert(self, MockResponse): MockResponse.return_value.headers = {} alert_id = "test_alert_id" comment = "test comment" share_irondome = True expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/CommentOnAlert") expected_body = json.dumps({"alert_id": alert_id, "comment": comment, "share_comment_with_irondome": share_irondome}) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.add_comment_to_alert(alert_id, comment=comment, share_irondome=share_irondome) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises( HTTPException, self.class_under_test.add_comment_to_alert, alert_id, comment=comment, share_irondome=share_irondome ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) @mock.patch("requests.Response", autospec=True) def test_set_alert_status(self, MockResponse): MockResponse.return_value.headers = {} alert_id = "test_alert_id" status = "Closed" comments = "test comments" share_irondome = True expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/SetAlertStatus") expected_body = json.dumps( {"alert_id": alert_id, "status": "STATUS_CLOSED", "comment": comments, "share_comment_with_irondome": share_irondome} ) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.set_alert_status(alert_id, status=status, comments=comments, share_irondome=share_irondome) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises( HTTPException, self.class_under_test.set_alert_status, alert_id, status=status, comments=comments, share_irondome=share_irondome, ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) @mock.patch("requests.Response", autospec=True) def test_report_observed_bad_activity(self, MockResponse): MockResponse.return_value.headers = {} name = "test_name" description = "asdf" ip = "1.1.1.1" domain = "bad.com" activity_start_time = "2019-01-01T00:00:00Z" activity_end_time = "2019-02-01T00:00:00Z" expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/ReportObservedBadActivity") expected_body = json.dumps( { "name": name, "description": description, "ip": ip, "domain": domain, "activity_start_time": activity_start_time, "activity_end_time": activity_end_time, } ) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.ok = True mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.report_observed_bad_activity( name, description=description, ip=ip, domain=domain, activity_start_time=activity_start_time, activity_end_time=activity_end_time, ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.ok = False mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises( HTTPException, self.class_under_test.report_observed_bad_activity, name, description=description, ip=ip, domain=domain, activity_start_time=activity_start_time, activity_end_time=activity_end_time, ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) @mock.patch("requests.Response", autospec=True) def test_get_event(self, MockResponse): MockResponse.return_value.headers = {} event_id = "test_event_id" expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/GetEvent") expected_body = json.dumps( { "event_id": event_id, } ) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.get_event(event_id) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises(HTTPException, self.class_under_test.get_event, event_id) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) @mock.patch("requests.Response", autospec=True) def test_get_events(self, MockResponse): MockResponse.return_value.headers = {} alert_id = "test_alert_id" expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/GetEvents") expected_body = json.dumps({"alert_id": "test_alert_id", "constraint": {"limit": 10, "offset": 1}}) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.get_events(alert_id=alert_id, limit=10, offset=1) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises(HTTPException, self.class_under_test.get_events, alert_id, limit=10, offset=1) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) @mock.patch("requests.Response", autospec=True) def test_get_alerts(self, MockResponse): MockResponse.return_value.headers = {} alert_id = ["test_alert_id"] category = ["TEST_CATEGORY"] sub_category = ["TEST_CATEGORY"] status = ["TEST_STATUS"] analyst_severity = ["TEST_ANALYST_SEVERITY"] analyst_expectation = ["TEST_ANALYST_EXPECTATION"] severity = {"lower_bound": 0, "upper_bound": 2} created = {"start": "2019-01-01T00:00:00Z", "end": "2020-01-01T00:00:00Z"} updated = {"start": "2019-02-01T00:00:00Z", "end": "2020-02-01T00:00:00Z"} first_event_created = {"start": "2019-02-01T00:00:00Z", "end": "2020-02-01T00:00:00Z"} last_event_created = {"start": "2019-02-01T00:00:00Z", "end": "2020-02-01T00:00:00Z"} first_event_start_time = {"start": "2019-02-01T00:00:00Z", "end": "2020-02-01T00:00:00Z"} last_event_end_time = {"start": "2019-02-01T00:00:00Z", "end": "2020-02-01T00:00:00Z"} analytic_version = ["1"] constraint = {"limit": 10, "offset": 0} sort = [{"field": "ASF_UPDATED", "direction": "SD_ASCENDING"}] expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/GetAlerts") expected_body = json.dumps( { "alert_id": alert_id, "category": category, "sub_category": sub_category, "status": ["STATUS_" + status[0]], "analyst_severity": ["SEVERITY_" + analyst_severity[0]], "analyst_expectation": ["EXP_" + analyst_expectation[0]], "analytic_version": analytic_version, "sort": sort, "severity": severity, "created": created, "updated": updated, "first_event_created": first_event_created, "last_event_created": last_event_created, "first_event_start_time": first_event_start_time, "last_event_end_time": last_event_end_time, "constraint": constraint, } ) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.ok = True mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.get_alerts( alert_id=alert_id[0], category=category[0], sub_category=sub_category[0], status=status[0], analyst_severity=analyst_severity[0], analyst_expectation=analyst_expectation[0], min_severity=severity["lower_bound"], max_severity=severity["upper_bound"], min_created=created["start"], max_created=created["end"], min_updated=updated["start"], max_updated=updated["end"], min_first_event_created=first_event_created["start"], max_first_event_created=first_event_created["end"], min_last_event_created=last_event_created["start"], max_last_event_created=last_event_created["end"], min_first_event_start_time=first_event_start_time["start"], max_first_event_start_time=first_event_start_time["end"], min_last_event_end_time=last_event_end_time["start"], max_last_event_end_time=last_event_end_time["end"], analytic_version=analytic_version[0], limit=constraint["limit"], offset=constraint["offset"], sort=sort, ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.ok = False mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises( HTTPException, self.class_under_test.get_alerts, alert_id=alert_id[0], category=category[0], sub_category=sub_category[0], status=status[0], analyst_severity=analyst_severity[0], analyst_expectation=analyst_expectation[0], min_severity=severity["lower_bound"], max_severity=severity["upper_bound"], min_created=created["start"], max_created=created["end"], min_updated=updated["start"], max_updated=updated["end"], min_first_event_created=first_event_created["start"], max_first_event_created=first_event_created["end"], min_last_event_created=last_event_created["start"], max_last_event_created=last_event_created["end"], min_first_event_start_time=first_event_start_time["start"], max_first_event_start_time=first_event_start_time["end"], min_last_event_end_time=last_event_end_time["start"], max_last_event_end_time=last_event_end_time["end"], analytic_version=analytic_version[0], limit=constraint["limit"], offset=constraint["offset"], sort=sort, ) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Mock Json Data for fetch_alert_incidents tests def mock_fetch_alert_incidents_data(self): data = { "alert_notifications": [ { "alert_action": "ANA_ALERT_CREATED", "alert": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "alert_action": "ANA_ALERT_CREATED", "alert": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 400, }, }, { "alert_action": "ANA_A1", "alert": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "alert_action": "ANA_A2", "alert": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "alert_action": "ANA_A3", "alert": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "alert_action": "ANA_ALERT_CREATED", "alert": { "category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600, }, }, { "alert_action": "ANA_ALERT_CREATED", "alert": { "category": "XC1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600, }, }, { "alert_action": "ANA_ALERT_CREATED", "alert": { "category": "XC2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600, }, }, { "alert_action": "ANA_ALERT_CREATED", "alert": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC1", "severity": 600, }, }, { "alert_action": "ANA_ALERT_CREATED", "alert": { "category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC2", "severity": 600, }, }, ] } return data # Test successful/unsuccessful responses for fetch_alert_incidents() @mock.patch("requests.Response", autospec=True) def test_fetch_alert_incidents(self, MockResponse): MockResponse.return_value.headers = {} # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.json.return_value = self.mock_fetch_alert_incidents_data() mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response alert_limit = 50 expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/GetAlertNotifications") expected_body = json.dumps( { "limit": alert_limit, } ) # Run test self.class_under_test.fetch_alert_incidents(alert_limit=alert_limit) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response # Run test self.assertRaises(Exception, self.class_under_test.fetch_alert_incidents, alert_limit) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test filtering from inputs on Alert Notifications @mock.patch("requests.Response", autospec=True) def test_fetch_alert_incidents_filtering(self, MockResponse): MockResponse.return_value.headers = {} mock_response = MockResponse() mock_response.status_code = 200 mock_response.json.return_value = self.mock_fetch_alert_incidents_data() mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response # Input params for filtering excluded_categories = ["xc1", "xc2"] excluded_subcats = "xsc1,xsc2" severity_threshold = 500 included_alert_actions = ["Alert Created", "A1", "A2"] alert_limit = 50 # Define expectations expected_resp_data = [ '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "alert"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "alert"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "alert"}', '{"category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "alert"}', ] # Run test init_result = self.class_under_test.fetch_alert_incidents( alert_categories=excluded_categories, alert_subcategories=excluded_subcats, alert_severity_lower=severity_threshold, alert_severity_upper=None, alert_limit=alert_limit, alert_actions=included_alert_actions, ) result = [init_result[0]["rawJSON"], init_result[1]["rawJSON"], init_result[2]["rawJSON"], init_result[3]["rawJSON"]] assert expected_resp_data == result # Test default filtering on Alert Notifications @mock.patch("requests.Response", autospec=True) def test_fetch_alert_incidents_action_default(self, MockResponse): MockResponse.return_value.headers = {} mock_response = MockResponse() mock_response.status_code = 200 mock_response.json.return_value = self.mock_fetch_alert_incidents_data() mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response # Input params for filtering excluded_categories = None excluded_subcats = None severity_threshold = None included_alert_actions = None alert_limit = 50 # Define Expectations expected_resp_data = [ '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "alert"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 400,' + ' "type": "alert"}', '{"category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "alert"}', '{"category": "XC1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "alert"}', '{"category": "XC2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "alert"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC1", "severity": 600,' + ' "type": "alert"}', '{"category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC2", "severity": 600,' + ' "type": "alert"}', ] # Run test init_result = self.class_under_test.fetch_alert_incidents( alert_categories=excluded_categories, alert_subcategories=excluded_subcats, alert_severity_lower=severity_threshold, alert_severity_upper=None, alert_limit=alert_limit, alert_actions=included_alert_actions, ) result = [ init_result[0]["rawJSON"], init_result[1]["rawJSON"], init_result[2]["rawJSON"], init_result[3]["rawJSON"], init_result[4]["rawJSON"], init_result[5]["rawJSON"], init_result[6]["rawJSON"], ] assert expected_resp_data == result # Mock Json Data for fetch_event_incidents() def mock_fetch_event_incidents_data(self): data = { "event_notifications": [ { "event_action": "ENA_EVENT_CREATED", "event": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "event_action": "ENA_EVENT_CREATED", "event": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 400, }, }, { "event_action": "ENA_A1", "event": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "event_action": "ENA_A2", "event": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "event_action": "ENA_A3", "event": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700, }, }, { "event_action": "ENA_EVENT_CREATED", "event": { "category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600, }, }, { "event_action": "ENA_EVENT_CREATED", "event": { "category": "XC1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600, }, }, { "event_action": "ENA_EVENT_CREATED", "event": { "category": "XC2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600, }, }, { "event_action": "ENA_EVENT_CREATED", "event": { "category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC1", "severity": 600, }, }, { "event_action": "ENA_EVENT_CREATED", "event": { "category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC2", "severity": 600, }, }, ] } return data # Test successful/unsuccessful responses for fetch_event_incidents() @mock.patch("requests.Response", autospec=True) def test_fetch_event_incidents(self, MockResponse): MockResponse.return_value.headers = {} # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.json.return_value = self.mock_fetch_event_incidents_data() mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response event_limit = 50 expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/GetEventNotifications") expected_body = json.dumps( { "limit": event_limit, } ) # Run test self.class_under_test.fetch_event_incidents(event_limit=event_limit) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response # Run test self.assertRaises(Exception, self.class_under_test.fetch_event_incidents, event_limit) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test filtering from inputs on Event Notifications @mock.patch("requests.Response", autospec=True) def test_fetch_event_incidents_filtering(self, MockResponse): MockResponse.return_value.headers = {} mock_response = MockResponse() mock_response.status_code = 200 mock_response.json.return_value = self.mock_fetch_event_incidents_data() mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response # Input params for filtering excluded_categories = ["xc1", "xc2"] excluded_subcats = "xsc1,xsc2" severity_threshold = 500 included_event_actions = ["Event Created", "A1", "A2"] event_limit = 50 # Define expectations expected_resp_data = [ '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "event"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "event"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "event"}', '{"category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "event"}', ] # Run test init_result = self.class_under_test.fetch_event_incidents( event_categories=excluded_categories, event_subcategories=excluded_subcats, event_severity_lower=severity_threshold, event_severity_upper=None, event_limit=event_limit, event_actions=included_event_actions, ) result = [init_result[0]["rawJSON"], init_result[1]["rawJSON"], init_result[2]["rawJSON"], init_result[3]["rawJSON"]] assert expected_resp_data == result # Test default filtering on Event Notifications @mock.patch("requests.Response", autospec=True) def test_fetch_event_incidents_action_default(self, MockResponse): MockResponse.return_value.headers = {} mock_response = MockResponse() mock_response.status_code = 200 mock_response.json.return_value = self.mock_fetch_event_incidents_data() mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response # Input params for filtering excluded_categories = None excluded_subcats = None severity_threshold = None included_event_actions = None event_limit = 50 # Define Expectations expected_resp_data = [ '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700,' + ' "type": "event"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 400,' + ' "type": "event"}', '{"category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "event"}', '{"category": "XC1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "event"}', '{"category": "XC2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600,' + ' "type": "event"}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC1", "severity": 600,' + ' "type": "event"}', '{"category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "XSC2", "severity": 600,' + ' "type": "event"}', ] # Run test init_result = self.class_under_test.fetch_event_incidents( event_categories=excluded_categories, event_subcategories=excluded_subcats, event_severity_lower=severity_threshold, event_severity_upper=None, event_limit=event_limit, event_actions=included_event_actions, ) result = [ init_result[0]["rawJSON"], init_result[1]["rawJSON"], init_result[2]["rawJSON"], init_result[3]["rawJSON"], init_result[4]["rawJSON"], init_result[5]["rawJSON"], init_result[6]["rawJSON"], ] assert expected_resp_data == result @mock.patch("requests.Response", autospec=True) def test_get_alert_irondome_information(self, MockResponse): MockResponse.return_value.headers = {} alert_id = "test_alert_id" expected_uri = "https://{}:{}{}{}".format(self.host, self.port, self.url_prefix, "/GetAlertIronDomeInformation") expected_body = json.dumps( { "alert_id": alert_id, } ) # Test successful response mock_response = MockResponse() mock_response.status_code = 200 mock_response.headers["auth-token"] = "some jwt token" self.mock_session.request.return_value = mock_response self.class_under_test.get_alert_irondome_information(alert_id) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) # Test failed response mock_response = MockResponse() mock_response.status_code = 403 mock_response.headers["auth-token"] = "some jwt token" mock_response.json.return_value = {"msg": "Some error"} self.mock_session.request.return_value = mock_response self.assertRaises(HTTPException, self.class_under_test.get_alert_irondome_information, alert_id) self.mock_session.request.assert_called_with( "POST", expected_uri, headers={}, data=expected_body, params={}, files=None, timeout=self.class_under_test.request_timeout, auth=None, verify=False, ) @mock.patch("requests.Response", autospec=True) def test_get_error_msg_from_response(self, MockResponse): expected_error_msg = "Some error message" mock_response = MockResponse() error_json = {"msg": expected_error_msg} mock_response.json.return_value = error_json error_msg = self.class_under_test._get_error_msg_from_response(mock_response) assert expected_error_msg == error_msg, "Error message was not properly extracted" mock_response.json.return_value = {} mock_response.text = expected_error_msg error_msg = self.class_under_test._get_error_msg_from_response(mock_response) assert expected_error_msg == error_msg, "Error message was not properly extracted" @mock.patch("IronDefense.demisto") def test_test_module_command(self, mock_demisto): irondefense_module.IRON_DEFENSE = Mock() expected_result = "result" irondefense_module.IRON_DEFENSE.test_module.return_value = expected_result irondefense_module.test_module_command() irondefense_module.IRON_DEFENSE.test_module.assert_called_once() mock_demisto.results.assert_called_once_with(expected_result) @mock.patch("IronDefense.demisto") def test_update_analyst_ratings_command(self, mock_demisto): irondefense_module.IRON_DEFENSE = Mock() expected_result = "result" irondefense_module.IRON_DEFENSE.update_analyst_ratings.return_value = expected_result expected_alert_id = "aaa-bbb-ccc" expected_comment = "comment" expected_severity = "some severity" expected_expectation = "some expectation" def getArg_side_effect(arg): if arg == "alert_id": return expected_alert_id if arg == "comments": return expected_comment if arg == "share_comment_with_irondome": return "true" if arg == "severity": return expected_severity if arg == "expectation": return expected_expectation return None mock_demisto.getArg.side_effect = getArg_side_effect irondefense_module.update_analyst_ratings_command() irondefense_module.IRON_DEFENSE.update_analyst_ratings.assert_called_once_with( expected_alert_id, severity=expected_severity, expectation=expected_expectation, comments=expected_comment, share_irondome=True, ) mock_demisto.results.assert_called_once_with(expected_result) @mock.patch("IronDefense.demisto") def test_add_comment_to_alert_command(self, mock_demisto): irondefense_module.IRON_DEFENSE = Mock() expected_result = "result" irondefense_module.IRON_DEFENSE.add_comment_to_alert.return_value = expected_result expected_alert_id = "aaa-bbb-ccc" expected_comment = "comment" def getArg_side_effect(arg): if arg == "alert_id": return expected_alert_id if arg == "comment": return expected_comment if arg == "share_comment_with_irondome": return "true" return None mock_demisto.getArg.side_effect = getArg_side_effect irondefense_module.add_comment_to_alert_command() irondefense_module.IRON_DEFENSE.add_comment_to_alert.assert_called_once_with( expected_alert_id, comment=expected_comment, share_irondome=True ) mock_demisto.results.assert_called_once_with(expected_result) @mock.patch("IronDefense.demisto") def test_set_alert_status_command(self, mock_demisto): irondefense_module.IRON_DEFENSE = Mock() expected_result = "result" irondefense_module.IRON_DEFENSE.set_alert_status.return_value = expected_result expected_alert_id = "aaa-bbb-ccc" expected_comment = "comment" expected_status = "some status" def getArg_side_effect(arg): if arg == "alert_id": return expected_alert_id if arg == "comments": return expected_comment if arg == "share_comment_with_irondome": return "true" if arg == "status": return expected_status return None mock_demisto.getArg.side_effect = getArg_side_effect irondefense_module.set_alert_status_command() irondefense_module.IRON_DEFENSE.set_alert_status.assert_called_once_with( expected_alert_id, comments=expected_comment, share_irondome=True, status=expected_status ) mock_demisto.results.assert_called_once_with(expected_result) @mock.patch("IronDefense.demisto") def test_report_observed_bad_activity_command(self, mock_demisto): irondefense_module.IRON_DEFENSE = Mock() expected_result = "result" irondefense_module.IRON_DEFENSE.report_observed_bad_activity.return_value = expected_result expected_name = "some name" expected_description = "some description" expected_ip = "1.1.1.1" expected_domain = "bad.com" expected_activity_start_time = "pickles" expected_activity_end_time = "more pickles" def getArg_side_effect(arg): if arg == "name": return expected_name if arg == "description": return expected_description if arg == "ip": return expected_ip if arg == "domain": return expected_domain if arg == "activity_start_time": return expected_activity_start_time if arg == "activity_end_time": return expected_activity_end_time return None mock_demisto.getArg.side_effect = getArg_side_effect irondefense_module.report_observed_bad_activity_command() irondefense_module.IRON_DEFENSE.report_observed_bad_activity.assert_called_once_with( expected_name, description=expected_description, ip=expected_ip, domain=expected_domain, activity_start_time=expected_activity_start_time, activity_end_time=expected_activity_end_time, ) mock_demisto.results.assert_called_once_with(expected_result) @mock.patch("IronDefense.demisto") def test_fetch_incidents_command(self, mock_demisto): irondefense_module.IRON_DEFENSE = Mock() irondefense_module.LOGGER = self.MockXsoarLogger(demisto, LOG_PREFIX) expected_result = [ '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700}', '{"category": "C1", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC1", "severity": 700}', '{"category": "C2", "updated": "2020-04-09T04:29:10.471378Z", "sub_category": "SC2", "severity": 600}', ] irondefense_module.IRON_DEFENSE.fetch_alert_incidents.return_value = expected_result # Notification related params irondefense_module.PARAMS = { "domeCategories": None, "domeLimit": 500, "enableDomeNotifications": False, "alertCategories": None, "alertSubCategories": None, "alertSeverityLower": None, "alertSeverityUpper": None, "alertLimit": 500, "alertActions": None, "enableAlertNotifications": True, "eventCategories": None, "eventSubCategories": None, "eventSeverityLower": None, "eventSeverityUpper": None, "eventLimit": 500, "eventActions": None, "enableEventNotifications": False, } expected_alert_categories = irondefense_module.PARAMS.get("alertCategories", None) expected_alert_subcategories = irondefense_module.PARAMS.get("alertSubCategories", None) expected_alert_severity_lower = irondefense_module.PARAMS.get("alertSeverityLower", None) expected_alert_severity_upper = irondefense_module.PARAMS.get("alertSeverityUpper", None) expected_alert_limit = int(irondefense_module.PARAMS.get("alertLimit", 500)) expected_alert_actions = irondefense_module.PARAMS.get("alertActions", None) irondefense_module.fetch_incidents_command() irondefense_module.IRON_DEFENSE.fetch_alert_incidents.assert_called_once_with( expected_alert_categories, expected_alert_subcategories, expected_alert_severity_lower, expected_alert_severity_upper, expected_alert_limit, expected_alert_actions, ) mock_demisto.incidents.assert_called_once_with(expected_result) @mock.patch("IronDefense.demisto") def test_get_event_command(self, mock_demisto): # Expectations expected_event = { "id": "abc", "category": "cat", "sub_category": "subcat", } with open("./test-data/event-context-table.json") as event_context_table_file: json_data = event_context_table_file.read() enterprise_ips_table = json.loads(json_data) with open("./test-data/event-context-key-value-table.json") as event_context_table_file: json_data = event_context_table_file.read() summary_table = json.loads(json_data) expected_context = [summary_table, enterprise_ips_table] expected_enterprise_ips = [ { "ip": "1.1.1.1", "classification": "CLASSIFICATION_ENTERPRISE", }, { "ip": "2.2.2.2", "classification": "CLASSIFICATION_ENTERPRISE_2", }, ] expected_summary_table = { "session_size": "1069918", "threshold_entity_time": "2020-04-11T09:00:00.000Z", "producer_to_consumer_ratio": "1.977250634801539", "threshold_size": "1020401", } expected_return_outputs_calls = [ call( readable_output="### IronDefense Event: cat - subcat\n" "link\n" "|category|id|sub_category|\n" "|---|---|---|\n" "| cat | abc | subcat |\n", outputs={ "IronDefense.Event(val.id == obj.id)": expected_event, }, raw_response=expected_event, ), call( readable_output="### Event Context: enterprise_ips\n" "|ip|classification|\n" "|---|---|\n" "| 1.1.1.1 | CLASSIFICATION_ENTERPRISE |\n" "| 2.2.2.2 | CLASSIFICATION_ENTERPRISE_2 |\n", outputs={ "IronDefense.Event.Context(val.name == obj.name)": enterprise_ips_table, }, raw_response=enterprise_ips_table, ), call( readable_output="### Event Context: summary\n" "|producer_to_consumer_ratio|session_size|threshold_entity_time|threshold_size|\n" "|---|---|---|---|\n" "| 1.977250634801539 | 1069918 | 2020-04-11T09:00:00.000Z | 1020401 |\n", outputs={ "IronDefense.Event.Context(val.name == obj.name)": summary_table, }, raw_response=summary_table, ), ] expected_event_id = "aaa-bbb-ccc" # Set up mocks irondefense_module.IRON_DEFENSE = Mock() get_event_result = { "event": expected_event, "context": expected_context, } irondefense_module.IRON_DEFENSE.get_event.return_value = get_event_result irondefense_module.IRON_DEFENSE.create_markdown_link.return_value = "link" def getArg_side_effect(arg): if arg == "event_id": return expected_event_id return None def event_context_table_contains_multi_columns_side_effect(table): return table.get("name") == "enterprise_ips" mock_demisto.getArg.side_effect = getArg_side_effect irondefense_module.IRON_DEFENSE.event_context_table_contains_multi_columns.side_effect = ( event_context_table_contains_multi_columns_side_effect ) irondefense_module.IRON_DEFENSE.event_context_table_to_dict_list.return_value = expected_enterprise_ips irondefense_module.IRON_DEFENSE.event_context_table_to_dict.return_value = expected_summary_table irondefense_module.return_outputs = Mock() # Execute irondefense_module.get_event_command() # Assert irondefense_module.IRON_DEFENSE.get_event.assert_called_once_with(expected_event_id) irondefense_module.return_outputs.assert_has_calls(expected_return_outputs_calls, any_order=True) @mock.patch("IronDefense.return_outputs") @mock.patch("IronDefense.demisto") def test_get_events_command(self, demisto_mock, return_outputs_mock): # Expectations expected_alert_id = "abc" expected_limit = 42 expected_offset = 4242 expected_total = 5000 expected_event_1 = {"id": "123", "category": "cat1", "sub_category": "subcat1"} expected_event_2 = {"id": "456", "category": "cat2", "sub_category": "subcat2"} expected_calls = [ call( readable_output="### IronDefense Event 4243/5000\n" "link\n" "|category|id|sub_category|\n" "|---|---|---|\n" "| cat1 | 123 | subcat1 |\n", outputs={ "IronDefense.Event(val.id == obj.id)": expected_event_1, }, raw_response=expected_event_1, ), call( readable_output="### IronDefense Event 4244/5000\n" "link\n" "|category|id|sub_category|\n" "|---|---|---|\n" "| cat2 | 456 | subcat2 |\n", outputs={ "IronDefense.Event(val.id == obj.id)": expected_event_2, }, raw_response=expected_event_2, ), ] # Setup mocks def getArg_side_effect(arg_name): if arg_name == "alert_id": return expected_alert_id elif arg_name == "limit": return expected_limit elif arg_name == "offset": return expected_offset else: return None demisto_mock.getArg.side_effect = getArg_side_effect irondefense_module.IRON_DEFENSE = Mock() irondefense_module.IRON_DEFENSE.get_events.return_value = { "events": [expected_event_1, expected_event_2], "constraint": {"total": expected_total, "offset": expected_offset, "limit": expected_limit}, } irondefense_module.IRON_DEFENSE.create_markdown_link.return_value = "link" # Execute test irondefense_module.get_events_command() # Assert results irondefense_module.IRON_DEFENSE.get_events.assert_called_with( alert_id=expected_alert_id, limit=expected_limit, offset=expected_offset ) return_outputs_mock.assert_has_calls(expected_calls, any_order=True) @mock.patch("IronDefense.return_outputs") @mock.patch("IronDefense.demisto") def test_get_alerts_command(self, demisto_mock, return_outputs_mock): # Expectations expected_alert_id = "a" expected_category = "b" expected_sub_category = "c" expected_status = "d" expected_analyst_severity = "x" expected_analyst_expectation = "e" expected_min_severity = "f" expected_max_severity = "g" expected_min_created = "h" expected_max_created = "i" expected_min_updated = "j" expected_max_updated = "k" expected_min_first_event_created = "l" expected_max_first_event_created = "m" expected_min_last_event_created = "n" expected_max_last_event_created = "o" expected_min_first_event_start_time = "p" expected_max_first_event_start_time = "q" expected_min_last_event_end_time = "r" expected_max_last_event_end_time = "s" expected_analytic_version = "t" expected_limit = "u" expected_offset = "v" expected_sort = "w" expected_alert_1 = {"id": "123", "category": "cat1", "sub_category": "subcat1"} expected_alert_2 = {"id": "456", "category": "cat2", "sub_category": "subcat2"} expected_constraint = { "limit": 4242, "offset": 10, "total": 42, } expected_calls = [ call( readable_output="### IronDefense Alert 11/42: cat1 - subcat1\n" "link\n" "|category|id|sub_category|\n" "|---|---|---|\n" "| cat1 | 123 | subcat1 |\n", outputs={ "IronDefense.Alert(val.id == obj.id)": expected_alert_1, }, raw_response=expected_alert_1, ), call( readable_output="### IronDefense Alert 12/42: cat2 - subcat2\n" "link\n" "|category|id|sub_category|\n" "|---|---|---|\n" "| cat2 | 456 | subcat2 |\n", outputs={ "IronDefense.Alert(val.id == obj.id)": expected_alert_2, }, raw_response=expected_alert_2, ), call( readable_output="### Query Constraints\n|limit|offset|total|\n|---|---|---|\n| 4242 | 10 | 42 |\n", outputs={ "IronDefense.Query.GetAlerts": expected_constraint, }, raw_response=expected_constraint, ), ] # Setup mocks def getArg_side_effect(arg_name): arg_dict = { "alert_id": expected_alert_id, "category": expected_category, "sub_category": expected_sub_category, "status": expected_status, "analyst_severity": expected_analyst_severity, "analyst_expectation": expected_analyst_expectation, "min_severity": expected_min_severity, "max_severity": expected_max_severity, "min_created": expected_min_created, "max_created": expected_max_created, "min_updated": expected_min_updated, "max_updated": expected_max_updated, "min_first_event_created": expected_min_first_event_created, "max_first_event_created": expected_max_first_event_created, "min_last_event_created": expected_min_last_event_created, "max_last_event_created": expected_max_last_event_created, "min_first_event_start_time": expected_min_first_event_start_time, "max_first_event_start_time": expected_max_first_event_start_time, "min_last_event_end_time": expected_min_last_event_end_time, "max_last_event_end_time": expected_max_last_event_end_time, "analytic_version": expected_analytic_version, "limit": expected_limit, "offset": expected_offset, "sort": expected_sort, } return arg_dict[arg_name] demisto_mock.getArg.side_effect = getArg_side_effect irondefense_module.IRON_DEFENSE = Mock() irondefense_module.IRON_DEFENSE.get_alerts.return_value = { "alerts": [expected_alert_1, expected_alert_2], "constraint": expected_constraint, } irondefense_module.IRON_DEFENSE.create_markdown_link.return_value = "link" # Execute test irondefense_module.get_alerts_command() # Assert results irondefense_module.IRON_DEFENSE.get_alerts.assert_called_with( alert_id=expected_alert_id, category=expected_category, sub_category=expected_sub_category, status=expected_status, analyst_severity=expected_analyst_severity, analyst_expectation=expected_analyst_expectation, min_severity=expected_min_severity, max_severity=expected_max_severity, min_created=expected_min_created, max_created=expected_max_created, min_updated=expected_min_updated, max_updated=expected_max_updated, min_first_event_created=expected_min_first_event_created, max_first_event_created=expected_max_first_event_created, min_last_event_created=expected_min_last_event_created, max_last_event_created=expected_max_last_event_created, min_first_event_start_time=(expected_min_first_event_start_time), max_first_event_start_time=(expected_max_first_event_start_time), min_last_event_end_time=expected_min_last_event_end_time, max_last_event_end_time=expected_max_last_event_end_time, analytic_version=expected_analytic_version, limit=expected_limit, offset=expected_offset, sort=expected_sort, ) return_outputs_mock.assert_has_calls(expected_calls, any_order=True) @mock.patch("IronDefense.return_outputs") @mock.patch("IronDefense.demisto") def test_get_alert_irondome_information_command(self, demisto_mock, return_outputs_mock): # load test data with open("./test-data/get_alert_irondome_information_resp.json") as test_data: json_data = test_data.read() mock_resp = json.loads(json_data) # Expectations expected_alert_id = "abc" expected_correlations_output = { "IronDome.Correlations(val.alert_id = obj.alert.id)": { "alert_id": expected_alert_id, "correlation": mock_resp.get("correlations")[0], } } expected_correlations_raw = mock_resp.get("correlations")[0] expected_ip_correlations_readable_output = ( '### IronDome IP Correlations in "redskins"\n' "|ip|enterprise_correlations|community_correlations|\n" "|---|---|---|\n" "| 1.1.1.1 | 1 | 2 |\n" ) expected_domain_correlations_readable_output = ( '### IronDome Domain Correlations in "redskins"\n' "|domain|enterprise_correlations|community_correlations|\n" "|---|---|---|\n" "| bad.com | 3 | 4 |\n" ) expected_behavior_correlations_readable_output = ( '### IronDome Behavior Correlations in "redskins"\n' "|behavior|enterprise_correlations|community_correlations|\n" "|---|---|---|\n" "| true | 5 | 6 |\n" ) expected_correlation_participation_output = { "IronDome.CorrelationParticipation(val.alert_id = obj.alert.id)": { "alert_id": expected_alert_id, "correlation_participation": mock_resp.get("correlation_participation")[0], } } expected_correlation_participation_readable_output = ( "### IronDome Correlation Participation in " '"redskins"\n|malicious' "_count|suspicious_count|benign_count|whitelisted_count" "|comments_count|activity_count|resource_owner|" "first_seen|last_seen|\n|---|---|---|---|---|---|---|---" "|---|\n| 7 | 8 | 9 | 10 | 11 | 12 | false | " "2020-01-08T10:40:00.000Z | 2020-02-07T19:22:56.000Z |\n" ) expected_correlation_participation_raw = mock_resp.get("correlation_participation")[0] expected_community_comments_output = { "IronDome.CommunityComments(val.alert_id = obj.alert.id)": { "alert_id": expected_alert_id, "community_comments": mock_resp.get("community_comments"), } } expected_community_comments_readable_output = ( "### IronDome Community " "Comments\n|created|comment|dome_tags|enterprise|" "self|\n|---|---|---|---|---|\n| 2020-04-15T18:57:16.000Z | " "BrandonTest2 - Share irondome | demo,<br>brandon_test,<br>" "BrandonNEWTEST,<br>Energy,<br>redskins | true | true |\n" ) expected_community_comments_raw = mock_resp.get("community_comments") expected_cognitive_system_score_output = { "IronDome.CognitiveSystemScore(val.alert_id = obj.alert.id)": { "alert_id": expected_alert_id, "cognitive_system_score": mock_resp.get("cognitive_system_score"), } } expected_cognitive_system_score_readable_output = f'### Cognitive System Score: {mock_resp.get("cognitive_system_score")}' expected_cognitive_system_score_raw = mock_resp.get("cognitive_system_score") expected_dome_notifications_output = { "IronDome.Notification(val.alert_id = obj.alert.id)": { "alert_id": expected_alert_id, "dome_notification": mock_resp.get("dome_notifications")[0], } } expected_dome_notifications_readable_output = ( "### IronDome Notification: " "DNC_JOINED_HIGH_RISK\n|alert_ids|category|created|" "dome_tags|id|\n|---|---|---|---|---|\n| " "04f94226-60f7-4f1b-9569-15d7ddc01b7a | DNC_JOINED_HIGH_RISK " "| 2020-04-15T09:27:20.000Z | redskins | 576225 |\n" ) expected_dome_notifications_raw = mock_resp.get("dome_notifications")[0] expected_results = "link" expected_calls = [ call( readable_output=expected_ip_correlations_readable_output, outputs=expected_correlations_output, raw_response=expected_correlations_raw, ), call( readable_output=expected_domain_correlations_readable_output, outputs=expected_correlations_output, raw_response=expected_correlations_raw, ), call( readable_output=expected_behavior_correlations_readable_output, outputs=expected_correlations_output, raw_response=expected_correlations_raw, ), call( readable_output=expected_correlation_participation_readable_output, outputs=expected_correlation_participation_output, raw_response=expected_correlation_participation_raw, ), call( readable_output=expected_community_comments_readable_output, outputs=expected_community_comments_output, raw_response=expected_community_comments_raw, ), call( readable_output=expected_cognitive_system_score_readable_output, outputs=expected_cognitive_system_score_output, raw_response=expected_cognitive_system_score_raw, ), call( readable_output=expected_dome_notifications_readable_output, outputs=expected_dome_notifications_output, raw_response=expected_dome_notifications_raw, ), call(readable_output=expected_results, outputs={}), ] # Set up mocks def getArg_side_effect(arg_name): arg_dict = { "alert_id": expected_alert_id, } return arg_dict[arg_name] demisto_mock.getArg.side_effect = getArg_side_effect irondefense_module.IRON_DEFENSE = Mock() irondefense_module.IRON_DEFENSE.get_alert_irondome_information.return_value = mock_resp irondefense_module.IRON_DEFENSE.create_dome_markdown_link.return_value = "link" # Execute test irondefense_module.get_alert_irondome_information_command() # Assert results irondefense_module.IRON_DEFENSE.get_alert_irondome_information.assert_called_with(expected_alert_id) return_outputs_mock.assert_has_calls(expected_calls, any_order=True) irondefense_module.IRON_DEFENSE.create_dome_markdown_link.assert_called_with( "Open IronDome information in IronVue", expected_alert_id ) @mock.patch("IronDefense.return_outputs") @mock.patch("IronDefense.demisto") def test_get_alert_irondome_information_command_empty_resp(self, demisto_mock, return_outputs_mock): # Expectations expected_alert_id = "abc" expected_results = f"No correlations found for alert ID: {expected_alert_id}" # Setup mocks def getArg_side_effect(arg_name): arg_dict = { "alert_id": expected_alert_id, } return arg_dict[arg_name] demisto_mock.getArg.side_effect = getArg_side_effect irondefense_module.IRON_DEFENSE = Mock() irondefense_module.IRON_DEFENSE.get_alert_irondome_information.return_value = { "correlations": [], "correlation_participation": [], "community_comments": [], "dome_notifications": [], "cognitive_system_score": 0, } # Execute test irondefense_module.get_alert_irondome_information_command() # Assert results irondefense_module.IRON_DEFENSE.get_alert_irondome_information.assert_called_with(expected_alert_id) demisto_mock.results.assert_called_with(expected_results) return_outputs_mock.assert_not_called() def test_event_context_table_to_dict_list(self): # Expectations expected_dict_table_list = [ { "ip": "1.1.1.1", "classification": "CLASSIFICATION_ENTERPRISE", }, { "ip": "2.2.2.2", "classification": "CLASSIFICATION_ENTERPRISE_2", }, ] # Execute test with open("./test-data/event-context-table.json") as event_context_table_file: json_data = event_context_table_file.read() event_context_table = json.loads(json_data) actual_dict_table_list = self.class_under_test.event_context_table_to_dict_list(event_context_table) # Assert results assert expected_dict_table_list == actual_dict_table_list def test_event_context_to_dict(self): # Expectations expected_dict_table = { "session_size": "1069918", "threshold_entity_time": "2020-04-11T09:00:00.000Z", "producer_to_consumer_ratio": "1.977250634801539", "threshold_size": "1020401", } # Execute test with open("./test-data/event-context-key-value-table.json") as event_context_table_file: json_data = event_context_table_file.read() event_context_table = json.loads(json_data) actual_dict_table = self.class_under_test.event_context_table_to_dict(event_context_table) # Assert results assert expected_dict_table == actual_dict_table def test_event_context_table_contains_multi_columns(self): # Execute test with open("./test-data/event-context-table.json") as event_context_table_file: json_data = event_context_table_file.read() event_context_table = json.loads(json_data) result = self.class_under_test.event_context_table_contains_multi_columns(event_context_table) assert result with open("./test-data/event-context-key-value-table.json") as event_context_table_file: json_data = event_context_table_file.read() event_context_table = json.loads(json_data) result = self.class_under_test.event_context_table_contains_multi_columns(event_context_table) assert not result def test_create_markdown_link(self): # Expectations link_text = "asdf" url = "https://asdf.com" expected_markdown_link = "[asdf](https://asdf.com)" # Execute test actual_markdown_link = self.class_under_test.create_markdown_link(link_text, url) # Assert assert expected_markdown_link == actual_markdown_link def test_create_dome_markdown_link(self): # Expectations link_text = "asdf" alert_id = "abc" expected_markdown_link = f"[asdf](https://{self.host}/alerts/irondome?filter=alertId%3D%3D{alert_id})" # Execute test actual_markdown_link = self.class_under_test.create_dome_markdown_link(link_text, alert_id) # Assert assert expected_markdown_link == actual_markdown_link if __name__ == "__main__": unittest.main()