From 8fc2588f64cfa41c3ad3ee586b447923947c7840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20de=20Reques=C3=A9ns?= <134218114+lderequesensS@users.noreply.github.com> Date: Mon, 9 Sep 2024 03:18:11 -0300 Subject: [PATCH 1/2] Zerofox Alerts: Feature - Add modify notes (#12) * Add modify_notes custom action * done developer checklist * added required data path for modify notes action * change app version and remove 3.6.0.md file * Updated filename as README.md in manual_readme_content.md file --------- Co-authored-by: Ishan Shah <86037628+ishans-crest@users.noreply.github.com> Co-authored-by: gdelavadiya-crest --- .pre-commit-config.yaml | 2 +- LICENSE | 2 +- README.md | 25 ++++++++- manual_readme_content.md | 2 +- release_notes/unreleased.md | 1 + zerofox.json | 88 ++++++++++++++++++++++++++++++- zerofox_connector.py | 102 ++++++++++++++++++++++++++++++++++-- zerofox_consts.py | 2 +- 8 files changed, 215 insertions(+), 9 deletions(-) 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/LICENSE b/LICENSE index c3eb7da..510ff7a 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file + limitations under the License. diff --git a/README.md b/README.md index e5ed96e..3f377e9 100644 --- a/README.md +++ b/README.md @@ -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,26 @@ action_result.data.\*.alert.timestamp | string | | action_result.summary | string | | action_result.message | string | | summary.total_objects | numeric | | -summary.total_objects_successful | numeric | | \ No newline at end of file +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 | | 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/unreleased.md b/release_notes/unreleased.md index fbcb2fd..a8d884a 100644 --- a/release_notes/unreleased.md +++ b/release_notes/unreleased.md @@ -1 +1,2 @@ **Unreleased** +* 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" From 2d26c132bd3b3e7b3f6fc96cdb6a6f9ebb6fab74 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Sep 2024 23:19:53 -0700 Subject: [PATCH 2/2] Release notes for version 3.6.0 --- LICENSE | 2 +- README.md | 30 +++++++++++++++++------------- release_notes/3.6.0.md | 1 + release_notes/unreleased.md | 1 - 4 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 release_notes/3.6.0.md diff --git a/LICENSE b/LICENSE index 510ff7a..c3eb7da 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 3f377e9..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,7 +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 +[modify notes](#action-modify-notes) - Append or replace notes on ZeroFox alert ## action: 'test connectivity' Validate the asset configuration for connectivity using supplied configuration @@ -178,26 +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 | | +summary.total_objects_successful | numeric | | ## action: 'modify notes' Append or replace notes on ZeroFox alert -Type: **generic** +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 | +**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.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/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/release_notes/unreleased.md b/release_notes/unreleased.md index a8d884a..fbcb2fd 100644 --- a/release_notes/unreleased.md +++ b/release_notes/unreleased.md @@ -1,2 +1 @@ **Unreleased** -* Added new action 'modify notes' \ No newline at end of file