diff --git a/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.py b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.py index e420b547fa4c..600600031e83 100644 --- a/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.py +++ b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.py @@ -1,30 +1,49 @@ -from datetime import timezone - from CommonServerPython import * ''' IMPORTS ''' import urllib3 import json +import requests urllib3.disable_warnings() -class Client(BaseClient): - def http_request(self, method, path, data=None): - response = self._http_request( - method, - url_suffix=path, - resp_type="json", - json_data=data +class Client: + def __init__(self, base_url=None, headers=None, verify=None, proxies=None, auth=None): + self.base_url = base_url or demisto.params().get('endpoint') + self.headers = headers or {'accept': "application/json"} + self.verify = verify if verify is not None else not demisto.params().get('insecure', True) + self.proxies = proxies if proxies is not None else demisto.params().get('proxy', False) + self.auth = auth or ( + demisto.params().get("credentials", {}).get('identifier', ''), + demisto.params().get("credentials", {}).get('password', '') + ) + + def _make_request(self, method, path, **kwargs): + url = self.base_url + path + demisto.info("Nozomi url " + url) + response = requests.request( + method=method, + url=url, + headers=self.headers, + verify=self.verify, + proxies=self.proxies, + auth=self.auth, + **kwargs ) - return response + + if response.status_code not in (200, 201, 202, 204): + demisto.info(f"Nozomi Unexpected status code: {response.status_code}. Returning empty JSON.") + return {"result": {}, "error": f"Unexpected status code: {response.status_code}"} + + return response.json() def http_get_request(self, path): - return self.http_request('GET', path) + return self._make_request('GET', path) def http_post_request(self, path, data): - return self.http_request('POST', path, data) + return self._make_request('POST', path, json=data) ''' GLOBAL_VARIABLES ''' @@ -44,20 +63,13 @@ def http_post_request(self, path, data): def get_client(): - return Client( - base_url=demisto.params().get('endpoint'), - verify=not demisto.params().get('insecure', True), - ok_codes=(200, 201, 202, 204), - headers={'accept': "application/json"}, - auth=(demisto.params().get("credentials", {}).get('identifier', ''), - demisto.params().get("credentials", {}).get('password', '')), - proxy=demisto.params().get('proxy', False)) + return Client() def parse_incident(i): return { 'name': f"{i['name']}_{i['id']}", - 'occurred': datetime.fromtimestamp(i['time'] / 1000, timezone.utc).isoformat(), + 'occurred': datetime.fromtimestamp(i['record_created_at'] / 1000, timezone.utc).isoformat(), # noqa: UP017 'severity': parse_severity(i), 'rawJSON': json.dumps(clean_null_terms(i)) } @@ -89,14 +101,14 @@ def ids_from_incidents(incidents_array): def better_than_time_filter(st): t = '' if st: - t = f' | where time > {st}' + t = f' | where record_created_at > {st}' return t def equal_than_time_filter(st): t = '' if st: - t = f' | where time == {st}' + t = f' | where record_created_at == {st}' return t @@ -123,14 +135,14 @@ def has_last_run(lr): def incidents_better_than_time(st, head, risk, also_n2os_incidents, client): return client.http_get_request( - f'{QUERY_ALERTS_PATH} | sort time asc | sort id asc{better_than_time_filter(st)}' + f'{QUERY_ALERTS_PATH} | sort record_created_at asc | sort id asc{better_than_time_filter(st)}' f'{risk_filter(risk)}{also_n2os_incidents_filter(also_n2os_incidents)} | head {head}' )['result'] def incidents_equal_than_time(st, risk, also_n2os_incidents, client): return client.http_get_request( - f'{QUERY_ALERTS_PATH} | sort time asc | sort id asc{equal_than_time_filter(st)}' + f'{QUERY_ALERTS_PATH} | sort record_created_at asc | sort id asc{equal_than_time_filter(st)}' f'{risk_filter(risk)}{also_n2os_incidents_filter(also_n2os_incidents)}' )['result'] @@ -180,7 +192,7 @@ def get_incident_name(i): def last_fetched_time(inc, last_run): - return inc[-1]['time'] if len(inc) > 0 else last_run.get("last_fetch", 0) + return inc[-1]['record_created_at'] if len(inc) > 0 else last_run.get("last_fetch", 0) def last_fetched_id(inc, last_run): @@ -196,7 +208,8 @@ def ack_unack_alerts(ids, status, client): for id in ids: data.append({'id': id, 'ack': status}) response = client.http_post_request('/api/open/alerts/ack', {'data': data}) - return response["result"]["id"] + + return response.get("result", {}).get("id", None) def ack_alerts(ids, client): @@ -437,8 +450,9 @@ def main(): elif demisto.command() == 'nozomi-find-ip-by-mac': return_results(CommandResults(**find_ip_by_mac(demisto.args(), client))) except Exception as e: - demisto.error(f'nozomi: got an error {e}') - return_error(str(e)) + error_message = f"Nozomi Error of type {type(e).__name__} occurred: {str(e)}" + demisto.error(error_message) + return_error(error_message) if __name__ in ('__main__', '__builtin__', 'builtins'): diff --git a/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.yml b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.yml index 5b4f712e34a4..534d68a6e9ee 100644 --- a/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.yml +++ b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.yml @@ -162,7 +162,7 @@ script: - contextPath: Nozomi.IpByMac.mac description: Mac found the ips. type: String - dockerimage: demisto/python3:3.10.14.92207 + dockerimage: demisto/python3:3.11.10.116949 isfetch: true runonce: false script: '-' diff --git a/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks_description.md b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks_description.md new file mode 100644 index 000000000000..95926cd7eeb0 --- /dev/null +++ b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks_description.md @@ -0,0 +1,16 @@ +# Nozomi Networks Integration + +This integration utilizes the **Nozomi Networks Universal Open API**. + +## Prerequisites + +To retrieve the `key_name` and `key_token`, please refer to the instructions provided in the **Nozomi Networks Products Technical Documentation**. + +## Features + +The integration provides the following capabilities: + +1. **Fetch Incidents**: Retrieve incidents directly from Nozomi Networks. +2. **Execute Commands**: Perform actions to gather and update data from Nozomi Networks. + +For detailed setup and usage instructions, consult the technical documentation provided by Nozomi Networks. diff --git a/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks_test.py b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks_test.py index 4e29e78ecc7e..c4b0267fb315 100644 --- a/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks_test.py +++ b/Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks_test.py @@ -1,5 +1,13 @@ import pytest from NozomiNetworks import * +import demistomock as demisto # Assuming you're using demistomock + +# Mock the `callingContext` before invoking your client or code that needs it +demisto.callingContext = { + 'context': { + 'IntegrationBrand': 'NozomiNetworks' + } +} NOZOMIGUARDIAN_URL = 'https://test.com' @@ -30,25 +38,56 @@ def test_start_time_if_last_run_0_return_default(): assert len(time) == 13 -@pytest.mark.parametrize('obj, expected', [(None, ''), ('', ''), ("1540390400000", " | where time > 1540390400000"), ]) +@pytest.mark.parametrize( + 'obj, expected', + [ + (None, ''), + ('', ''), + ("1540390400000", " | where record_created_at > 1540390400000"), + ] +) def test_time_filter(obj, expected): assert better_than_time_filter(obj) == expected -@pytest.mark.parametrize('obj, expected', [(None, ''), ('', ''), ("1540390400000", " | where time == 1540390400000"), ]) +@pytest.mark.parametrize( + 'obj, expected', + [ + (None, ''), + ('', ''), + ("1540390400000", " | where record_created_at == 1540390400000"), + ] +) def test_equal_time_filter_with_ts(obj, expected): assert equal_than_time_filter(obj) == expected def test_parse_incidents(): - i = parse_incident( - {'id': '1af93f46-65c1-4f52-a46a-2a181597fb0c', 'type_id': 'NET:RST-FROM-SLAVE', - 'name': 'Link RST sent by Slave', - 'description': 'The slave 10.197.23.139 sent a RST of the connection to the master.', 'severity': 10, - 'mac_src': '00:02:3e:99:c9:5d', 'mac_dst': '00:c0:c9:30:04:f1', 'ip_src': '10.197.23.182', - 'ip_dst': '10.197.23.139', 'risk': '4.5', 'protocol': 'iec104', 'src_roles': 'master', 'dst_roles': 'slave', - 'time': 1392048082242, 'ack': False, 'port_src': 1097, 'port_dst': 2404, 'status': 'open', 'threat_name': '', - 'type_name': 'Link RST sent by Slave', 'zone_src': 'RemoteRTU', 'zone_dst': 'RemoteRTU'}) + i = parse_incident({ + 'id': '1af93f46-65c1-4f52-a46a-2a181597fb0c', + 'type_id': 'NET:RST-FROM-SLAVE', + 'name': 'Link RST sent by Slave', + 'description': 'The slave 10.197.23.139 sent a RST of the connection to the master.', + 'severity': 10, + 'mac_src': '00:02:3e:99:c9:5d', + 'mac_dst': '00:c0:c9:30:04:f1', + 'ip_src': '10.197.23.182', + 'ip_dst': '10.197.23.139', + 'risk': '4.5', + 'protocol': 'iec104', + 'src_roles': 'master', + 'dst_roles': 'slave', + 'record_created_at': 1392048082242, + 'ack': False, + 'port_src': 1097, + 'port_dst': 2404, + 'status': 'open', + 'threat_name': '', + 'type_name': 'Link RST sent by Slave', + 'zone_src': 'RemoteRTU', + 'zone_dst': 'RemoteRTU' + }) + del i['occurred'] assert i == { @@ -59,7 +98,7 @@ def test_parse_incidents(): '"description": "The slave 10.197.23.139 sent a RST of the connection to the master.", ' '"severity": 10, "mac_src": "00:02:3e:99:c9:5d", "mac_dst": "00:c0:c9:30:04:f1", ' '"ip_src": "10.197.23.182", "ip_dst": "10.197.23.139", "risk": "4.5", "protocol": "iec104", ' - '"src_roles": "master", "dst_roles": "slave", "time": 1392048082242, "ack": false, ' + '"src_roles": "master", "dst_roles": "slave", "record_created_at": 1392048082242, "ack": false, ' '"port_src": 1097, "port_dst": 2404, "status": "open", "threat_name": "", ' '"type_name": "Link RST sent by Slave", "zone_src": "RemoteRTU", "zone_dst": "RemoteRTU"}' } @@ -98,8 +137,8 @@ def test_incidents_filtered(requests_mock): True, __get_client( [{'json': __load_test_data('./test_data/incidents_better_than_time.json'), - 'path': '/api/open/query/do?query=alerts | sort time asc | sort id asc ' - '| where time > 1392048082000 | where risk >= 4 | head 20'}], + 'path': '/api/open/query/do?query=alerts | sort record_created_at asc | sort id asc ' + '| where record_created_at > 1392048082000 | where risk >= 4 | head 20'}], requests_mock)) assert lid is not None diff --git a/Packs/NozomiNetworks/Integrations/NozomiNetworks/test_data/incidents_better_than_time.json b/Packs/NozomiNetworks/Integrations/NozomiNetworks/test_data/incidents_better_than_time.json index 94a5ed7fbb74..116c9d4004aa 100644 --- a/Packs/NozomiNetworks/Integrations/NozomiNetworks/test_data/incidents_better_than_time.json +++ b/Packs/NozomiNetworks/Integrations/NozomiNetworks/test_data/incidents_better_than_time.json @@ -14,7 +14,7 @@ "protocol": "", "src_roles": "other", "dst_roles": null, - "time": 1392048082242, + "record_created_at": 1392048082242, "ack": false, "id_src": "10.197.23.139", "id_dst": null, @@ -74,7 +74,7 @@ "protocol": "iec104", "src_roles": "master", "dst_roles": "slave", - "time": 1392048082242, + "record_created_at": 1392048082242, "ack": false, "id_src": "10.197.23.182", "id_dst": "10.197.23.139", @@ -135,6 +135,7 @@ "src_roles", "dst_roles", "time", + "record_created_at", "ack", "id_src", "id_dst", diff --git a/Packs/NozomiNetworks/ReleaseNotes/1_0_12.md b/Packs/NozomiNetworks/ReleaseNotes/1_0_12.md new file mode 100644 index 000000000000..96573e96e001 --- /dev/null +++ b/Packs/NozomiNetworks/ReleaseNotes/1_0_12.md @@ -0,0 +1,9 @@ + +#### Integrations + +##### Nozomi Networks + +- Documentation and metadata improvements. +- Pagination using `record_created_at` to avoid concurrency. +- Some fixes to make the service more robust to errors. +- update docker image to 3.11.10.116949 \ No newline at end of file diff --git a/Packs/NozomiNetworks/pack_metadata.json b/Packs/NozomiNetworks/pack_metadata.json index cbcf029907d2..e06719c651cf 100644 --- a/Packs/NozomiNetworks/pack_metadata.json +++ b/Packs/NozomiNetworks/pack_metadata.json @@ -4,7 +4,7 @@ "support": "partner", "author": "Nozomi Networks", "url": "https://www.nozominetworks.com", - "currentVersion": "1.0.11", + "currentVersion": "1.0.12", "email": "support@nozominetworks.com", "categories": [ "Network Security"