diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d56cada..c1a4e18 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: org-hook - id: package-app-dependencies - repo: https://github.com/Yelp/detect-secrets - rev: v1.4.0 + rev: v1.5.0 hooks: - id: detect-secrets args: ['--no-verify'] diff --git a/README.md b/README.md index e5ed96e..e95a503 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # ZeroFox Publisher: ZeroFox -Connector Version: 3.5.1 +Connector Version: 3.6.0 Product Vendor: ZeroFox Product Name: ZeroFox Product Version Supported (regex): ".\*" @@ -10,7 +10,7 @@ Minimum Product Version: 6.1.1 ZeroFox Alerts for Splunk SOAR -[comment]: # File: manual_readme_content.md +[comment]: # File: README.md [comment]: # [comment]: # Copyright (c) ZeroFox, 2024 [comment]: # @@ -43,6 +43,7 @@ VARIABLE | REQUIRED | TYPE | DESCRIPTION [tag alert](#action-tag-alert) - Add or remove a tag to a ZeroFox alert [threat submission](#action-threat-submission) - Add a manual threat to ZeroFox [lookup alert](#action-lookup-alert) - Retrieve a single alert and it's details, identified by its unique integer identifier +[modify notes](#action-modify-notes) - Append or replace notes on ZeroFox alert ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -177,4 +178,30 @@ action_result.data.\*.alert.timestamp | string | | action_result.summary | string | | action_result.message | string | | summary.total_objects | numeric | | +summary.total_objects_successful | numeric | | + +## action: 'modify notes' +Append or replace notes on ZeroFox alert + +Type: **generic** +Read only: **False** + +#### Action Parameters +PARAMETER | REQUIRED | DESCRIPTION | TYPE | CONTAINS +--------- | -------- | ----------- | ---- | -------- +**alert_id** | required | ZeroFox Alert ID | numeric | +**modify_action** | required | Modify action: append or replace | string | +**notes** | required | Alert's notes | string | + +#### Action Output +DATA PATH | TYPE | CONTAINS | EXAMPLE VALUES +--------- | ---- | -------- | -------------- +action_result.status | string | | success failed +action_result.parameter.alert_id | numeric | | +action_result.parameter.notes | string | | +action_result.parameter.modify_action | string | | +action_result.data | string | | +action_result.summary | string | | +action_result.message | string | | +summary.total_objects | numeric | | summary.total_objects_successful | numeric | | \ No newline at end of file diff --git a/manual_readme_content.md b/manual_readme_content.md index 1036032..188d65a 100644 --- a/manual_readme_content.md +++ b/manual_readme_content.md @@ -1,4 +1,4 @@ -[comment]: # File: manual_readme_content.md +[comment]: # File: README.md [comment]: # [comment]: # Copyright (c) ZeroFox, 2024 [comment]: # diff --git a/release_notes/3.6.0.md b/release_notes/3.6.0.md new file mode 100644 index 0000000..1b3209d --- /dev/null +++ b/release_notes/3.6.0.md @@ -0,0 +1 @@ +* Added new action 'modify notes' \ No newline at end of file diff --git a/zerofox.json b/zerofox.json index b441ace..09dbf70 100644 --- a/zerofox.json +++ b/zerofox.json @@ -19,7 +19,7 @@ } ], "license": "Copyright (c) ZeroFox, 2024", - "app_version": "3.5.1", + "app_version": "3.6.0", "utctime_updated": "2023-07-26T17:04:22.513369Z", "package_name": "phantom_zerofox", "main_module": "zerofox_connector.py", @@ -491,6 +491,92 @@ "height": 5 }, "versions": "EQ(*)" + }, + { + "action": "modify notes", + "identifier": "modify_notes", + "description": "Append or replace notes on ZeroFox alert", + "type": "generic", + "read_only": false, + "parameters": { + "alert_id": { + "description": "ZeroFox Alert ID", + "data_type": "numeric", + "required": true, + "order": 0 + }, + "modify_action": { + "data_type": "string", + "order": 1, + "description": "Modify action: append or replace", + "value_list": [ + "append", + "replace" + ], + "default": "append", + "required": true + }, + "notes": { + "data_type": "string", + "order": 2, + "description": "Alert's notes", + "required": true + } + }, + "output": [ + { + "data_path": "action_result.status", + "data_type": "string", + "column_order": 3, + "column_name": "Status", + "example_values": [ + "success", + "failed" + ] + }, + { + "data_path": "action_result.parameter.alert_id", + "data_type": "numeric", + "column_name": "Alert ID", + "column_order": 0 + }, + { + "data_path": "action_result.parameter.notes", + "data_type": "string", + "column_name": "Alert Notes", + "column_order": 1 + }, + { + "data_path": "action_result.parameter.modify_action", + "data_type": "string", + "column_name": "Modify Action", + "column_order": 2 + }, + { + "data_path": "action_result.data", + "data_type": "string" + }, + { + "data_path": "action_result.summary", + "data_type": "string" + }, + { + "data_path": "action_result.message", + "data_type": "string" + }, + { + "data_path": "summary.total_objects", + "data_type": "numeric" + }, + { + "data_path": "summary.total_objects_successful", + "data_type": "numeric" + } + ], + "render": { + "type": "table" + }, + "versions": "EQ(*)" } ] } diff --git a/zerofox_connector.py b/zerofox_connector.py index fc6cefb..a85ead3 100644 --- a/zerofox_connector.py +++ b/zerofox_connector.py @@ -33,6 +33,10 @@ def __new__(cls, val1, val2=None): class AlertMapper: + def __init__(self, _container_label, app_id): + self._container_label = _container_label + self.app_id = app_id + def _phantom_severity_transform(self, severity): """ Map ZeroFOX severity to Phantom severity. @@ -198,7 +202,7 @@ def prepare_alert_container(self, alert): container["tags"] = alert["tags"] date_time_obj = datetime.strptime(alert["timestamp"], "%Y-%m-%dT%H:%M:%S+00:00") container["start_time"] = date_time_obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ") - container["ingest_app_id"] = self.get_app_id() + container["ingest_app_id"] = self.app_id return container @@ -230,7 +234,6 @@ def __init__(self): # Do note that the app json defines the asset config, so please # modify this as you deem fit. self._base_url = ZEROFOX_API_URL - self.mapper = AlertMapper() def _get_app_headers(self): return { @@ -832,6 +835,88 @@ def _threat_submit(self, param): return action_result.set_status(phantom.APP_SUCCESS) + def _modify_notes(self, param): + self.debug_print(f"Param: {param}") + + # Add an action result object to self (BaseConnector) to represent the action for this param + action_result = self.add_action_result(ActionResult(dict(param))) + + alert_id = param.get("alert_id") + + endpoint = f"/1.0/alerts/{alert_id}/" + headers = self._get_app_headers() + + ret_val, response = self._make_rest_call( + endpoint, action_result, method="get", headers=headers + ) + + if phantom.is_fail(ret_val): + action_result.set_status( + phantom.APP_ERROR, + f"Error fetching alert with id: {alert_id}", + ) + self.debug_print( + f"Interim action_result dictionary after adding FAILURE status: {action_result.get_dict()}" + ) + summary = action_result.update_summary({}) + summary["status"] = "failed" + return action_result.set_status(phantom.APP_ERROR) + + alert = response.get("alert", {}) + + if not alert: + self.debug_print(f"Failed to obtain data of alert id: {alert_id}") + summary = action_result.update_summary({}) + summary["status"] = "failed" + return action_result.set_status(phantom.APP_ERROR) + + action = param.get("modify_action", "append") + previous_notes = alert.get("notes", "") + notes = param.get("notes", "") + new_notes = "" + if action == "replace": + new_notes = notes + elif action == "append": + new_notes = notes if not previous_notes else f"{previous_notes}\n{notes}" + else: + self.debug_print(f"Modify notes failed because it found action: {action}") + summary = action_result.update_summary({}) + summary["status"] = "failed" + return action_result.set_status(phantom.APP_ERROR) + + ret_val, response = self._make_rest_call( + endpoint, + action_result, + method="post", + json={"notes": new_notes}, + headers=headers, + ) + + if phantom.is_fail(ret_val): + action_result.set_status( + phantom.APP_ERROR, + f"Error changing notes on alert for {alert_id}, with notes {notes}", + ) + self.debug_print( + f"Interim action_result dictionary after adding FAILURE status: {action_result.get_dict()}" + ) + summary = action_result.update_summary({}) + summary["status"] = "failed" + return action_result.set_status(phantom.APP_ERROR) + + # Add the response into the data section + action_result.add_data(response) + + # Add a dictionary that is made up of the most important values from data into the summary + summary = action_result.update_summary({}) + summary["num_alerts"] = 1 + summary["status"] = "success" + + self.save_progress("Notes Modified Succesfully") + self.debug_print(f"{self._banner} response: {response}") + + return action_result.set_status(phantom.APP_SUCCESS) + def _take_alert_action(self, param): # Implement the handler here # use self.save_progress(...) to send progress messages back to the platform @@ -921,6 +1006,9 @@ def handle_action(self, param): elif action_id == "threat_submit": ret_val = self._threat_submit(param) + elif action_id == "modify_notes": + ret_val = self._modify_notes(param) + elif action_id == "on_poll": ret_val = self._on_poll(param) @@ -954,6 +1042,7 @@ def initialize(self): self.zf_client = ZeroFoxClient( token=config.get("zerofox_api_token"), username=config.get("username") ) + self.mapper = AlertMapper(self._container_label, self.get_app_id()) return phantom.APP_SUCCESS @@ -975,7 +1064,14 @@ def finalize(self): argparser.add_argument("input_test_json", help="Input Test JSON file") argparser.add_argument("-u", "--username", help="username", required=False) argparser.add_argument("-p", "--password", help="password", required=False) - argparser.add_argument('-v', '--verify', action='store_true', help='verify', required=False, default=False) + argparser.add_argument( + "-v", + "--verify", + action="store_true", + help="verify", + required=False, + default=False, + ) args = argparser.parse_args() session_id = None diff --git a/zerofox_consts.py b/zerofox_consts.py index 65c5ac8..4f8dddc 100644 --- a/zerofox_consts.py +++ b/zerofox_consts.py @@ -15,4 +15,4 @@ # Define your constants here -ZEROFOX_API_URL = 'https://api.zerofox.com' +ZEROFOX_API_URL = "https://api.zerofox.com"