Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nozomi fix lost incidents #37351

72 changes: 43 additions & 29 deletions Packs/NozomiNetworks/Integrations/NozomiNetworks/NozomiNetworks.py
Original file line number Diff line number Diff line change
@@ -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,
YairGlick marked this conversation as resolved.
Show resolved Hide resolved
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 '''
Expand All @@ -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))
}
Expand Down Expand Up @@ -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


Expand All @@ -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']

Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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'):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: '-'
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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 == {
Expand All @@ -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"}'
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -135,6 +135,7 @@
"src_roles",
"dst_roles",
"time",
"record_created_at",
"ack",
"id_src",
"id_dst",
Expand Down
9 changes: 9 additions & 0 deletions Packs/NozomiNetworks/ReleaseNotes/1_0_12.md
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion Packs/NozomiNetworks/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"support": "partner",
"author": "Nozomi Networks",
"url": "https://www.nozominetworks.com",
"currentVersion": "1.0.11",
"currentVersion": "1.0.12",
"email": "[email protected]",
"categories": [
"Network Security"
Expand Down
Loading