Nozomi Networks
The Nozomi Networks platform, available as a hardware, virtual appliance, or via the Vantage Cloud product, provides comprehensive monitoring for OT, IoT, and IT networks. It combines asset discovery, network visualization, vulnerability assessment, risk monitoring, and advanced threat detection in a unified solution. The integration is designed to gather alerts and asset information from Nozomi, whether deployed on-premises or in the cloud via Vantage, ensuring seamless visibility and security across environments.
Network Security · Nozomi Networks
Details
| ID | Nozomi Networks |
|---|---|
| Provider | Mitsubishi Electric |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
The Nozomi Networks integration enables the collection of alert and asset information from Nozomi’s suite of products, including Guardian, CMC, and the cloud-based Vantage platform. These solutions provide comprehensive OT/IoT/IT network monitoring through asset discovery, network visualization, vulnerability assessment, risk monitoring, and threat detection — all within a unified platform.
This integration is used to gather alert and asset information from Nozomi.
Configure Nozomi Networks in Cortex
| Parameter | Description | Required |
|---|---|---|
| endpoint | Endpoint url | True |
| username | Username | True |
| password | Password | True |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | False |
| isFetch | Fetch incidents | False |
| fetchTime | Get incidents from last | False |
| riskFrom | Get incidents from risk level | False |
| fecthAlsoIncidents | Fetch also nozomi incidents | False |
| incidentPerRun | Specifies the maximum number of incidents to retrieve | False |
Commands
You can execute these commands from the 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.
nozomi-find-assets
This command permits you to get some assets from Nozomi, you can use the query filter to to refine your search. With the limits you can decide the max number of assets you can retrieve from Nozomi, the limit can’t be bigger than 1000.
Base Command
nozomi-find-assets
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | You can add a filter to get exactly the assets you want. For example ‘where ip match 10.0.1.10’, ‘where vendor == Selta Telematica S.p.a’ | Optional |
| limit | Maximun number of assets get from Nozomi, can’t be bigger than 1000 | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Nozomi.Asset.id | String | uniq id of an asset |
| Nozomi.Asset.level | Number | network layer |
| Nozomi.Asset.capture_device | String | source from which the asset was captured |
| Nozomi.Asset.ip | Unknown | array of asset ip |
| Nozomi.Asset.mac_address | Unknown | array of asset mac address |
| Nozomi.asset.mac_vendor | Unknown | array of mac vendor |
| os | String | operating system |
| vendor | String | asset vendor |
| Nozomi.Asset.firmware_version | String | firmaware version |
| serial_number | String | serial number |
| product_name | String | product name |
| type | String | asset type as ‘OT_device’ |
| protocols | Unknown | array of asset protocols |
Command Example
!nozomi-find-assets limit=3 filter="| where level == 4"
Context Example
{
"Nozomi": {
"Asset": [
{
"name": "10.197.23.146",
"level": "1",
"id": "a3707ec4-7c85-437e-9d46-dbabd39b4dc2",
"appliance_hosts": [
"nozomi-dev"
],
"capture_device": "/vagrant/ids-testapi/fixtures/iec104_mestre_mini.pcap",
"ip": [
"10.197.23.146"
],
"mac_address": [
"00:02:3e:99:fe:1b"
],
"mac_address_level": {
"00:02:3e:99:fe:1b": "unconfirmed"
},
"vlan_id": [],
"mac_vendor": [
"Selta Telematica S.p.a"
],
"os": "",
"roles": [
"slave"
],
"vendor": "",
"_asset_kb_id": "",
"vendor:info": {
"source": "passive"
},
"firmware_version": "",
"firmware_version:info": {
"source": "passive"
},
"os_or_firmware": "",
"serial_number": "",
"serial_number:info": {
"source": "passive"
},
"product_name": "",
"product_name:info": {
"source": "passive"
},
"type": "OT_device",
"type:info": {
"source": "passive"
},
"protocols": [
"iec104"
],
"nodes": [
"10.197.23.146"
],
"zones": [
"RemoteRTU"
],
"custom_fields": {}
}
]
}
}
Human Readable Output
Nozomi Networks - No assets found
nozomi-close-incidents-as-security
Close incidents as security
Base Command
nozomi-close-incidents-as-security
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | List of IDs to close as security | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Nozomi.CloseStatus | String | Status of the request |
| Ids | Unknown | Ids closed |
| CloseAction | String | As the incidents are closed |
Command Example
!nozomi-close-incidents-as-security ids=['fa441619-39d4-46c1-a2fb-fc3b285c0b64']
Context Example
{
"Nozomi": {
"CloseAction": "closed_as_security",
"CloseStatus": "SUCCESS",
"Ids": [
"fa441619-39d4-46c1-a2fb-fc3b285c0b64"
]
}
}
Human Readable Output
Command changes the status of alerts passed as “closed_as_security” in Nozomi Networks platform.
nozomi-close-incidents-as-change
Close incidents as change
Base Command
nozomi-close-incidents-as-change
Input
| Argument Name | Description | Required |
|---|---|---|
| ids | List of IDs to close as change. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Nozomi.CloseStatus | String | Status of the request |
| Ids | Unknown | Ids closed |
| CloseAction | String | As the incidents are closed |
Command Example
!nozomi-close-incidents-as-change ids=['fa441619-39d4-46c1-a2fb-fc3b285c0b64']
Context Example
{
"Nozomi": {
"CloseAction": "closed_as_change",
"CloseStatus": "SUCCESS",
"Ids": [
"fa441619-39d4-46c1-a2fb-fc3b285c0b64"
]
}
}
Human Readable Output
Command changes the status of alerts passed as “closed_as_change” in Nozomi Networks platform.
nozomi-query
Can execute a nozomi query to get all the information you want.
A query can be something like that: “alerts | select id name status ack | where status == open”
Take a look to n2os manual to know how to do a query.
Base Command
nozomi-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | A valid query | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Nozomi.Query.Result | Unknown | An array of items |
| Nozomi.Error | String | In case the query is not correct the errors shows you the reason. |
Command Example
!nozomi-query query="links | where from match 192.168.10.2 | where protocol match ssh"
Context Example
{
"Nozomi": {
"Result": []
}
}
Human Readable Output
Nozomi Networks - Results for Query
No entries.
nozomi-find-ip-by-mac
Find a node ip from a mac address
Base Command
nozomi-find-ip-by-mac
Input
| Argument Name | Description | Required |
|---|---|---|
| mac | a mac address | Required |
| only_nodes_confirmed | This argument permit you to return only the nodes IPs from a mac address of nodes having the status to ‘confirmed’. Default value is True. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Nozomi.IpByMac.ips | Unknown | Array of ips found for the mac address passed, empty if not found. |
| Nozomi.Error | String | Usually an ip not found error |
Command Example
!nozomi-find-ip-by-mac mac='00:0c:29:22:50:26' only_nodes_confirmed='True'
Context Example
{
"Nozomi": {
"Error": "Ip not found"
}
}
Human Readable Output
Nozomi Networks - No IP results were found for mac address: ‘00:0c:29:22:50:26’
Configuration parameters
endpoint— Endpoint url (required)credentials— Username (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsincidentType— Incident typefetchTime— Get incidents from lastriskFrom— Get incidents from risk levelfecthAlsoIncidents— Fetch also Nozomi incidentsincidentFetchInterval— Incidents Fetch IntervalincidentPerRun— Incidents per run
Commands (5)
-
nozomi-close-incidents-as-changeClose incidents as change.
-
nozomi-close-incidents-as-securityClose incidents as security.
-
nozomi-find-assetsThis command permits you to get some assets from Nozomi, you can use the query filter to to refine your search. With the limits you can decide the max number of assets you can retrieve from Nozomi, the limit can't be bigger than 100.
-
nozomi-find-ip-by-macFind a node ip from a mac address.
-
nozomi-queryCan execute a nozomi query to get all the information you want. A query can be something like that: "alerts | select id name status ack | where status == open" Take a look to n2os manual to know how to do a query.
from CommonServerPython import * """ IMPORTS """ import urllib3 import json import requests urllib3.disable_warnings() class Client: def __init__(self, base_url=None, verify=None, auth_credentials=None, use_basic_auth=None, bearer_token=None, proxy=None): self.base_url = base_url or demisto.params().get("endpoint") self.verify = verify if verify is not None else not demisto.params().get("insecure", True) self.proxy = proxy or demisto.params().get("proxy", False) self.auth_credentials = auth_credentials or ( demisto.params().get("credentials", {}).get("identifier", ""), demisto.params().get("credentials", {}).get("password", ""), ) self.bearer_token = bearer_token or None self.use_basic_auth = use_basic_auth or False def sign_in(self): payload = {"key_name": self.auth_credentials[0], "key_token": self.auth_credentials[1]} try: url = f"{self.base_url}/api/open/sign_in" proxies = self.build_proxies() response = requests.post(url, json=payload, verify=self.verify, proxies=proxies) if response.status_code != 200: raise Exception(f"Authentication failed with status code {response.status_code}: {response.text}") self.bearer_token = response.headers.get("Authorization") self.use_basic_auth = False except Exception as e: demisto.info(f"Sign-in failed: {str(e)}. Falling back to basic authentication.") self.use_basic_auth = True def build_proxies(self): if self.proxy: return handle_proxy() else: skip_proxy() return {} def build_headers(self): if self.use_basic_auth: return {"accept": "application/json"} return {"accept": "application/json", "Authorization": f"{self.bearer_token}"} def _make_request(self, method, path, **kwargs): url = self.base_url + path if not self.bearer_token and not self.use_basic_auth: self.sign_in() if self.use_basic_auth: kwargs["auth"] = self.auth_credentials response = requests.request( method=method, url=url, headers=self.build_headers(), verify=self.verify, proxies=self.build_proxies(), **kwargs ) status_code = response.status_code if status_code in (401, 403): raise Exception("Authentication failure or resource forbidden.") if status_code not in (200, 201, 202, 204): demisto.info(f"Unexpected status code: {status_code}, path {path} Returning empty JSON.") return {"result": None, "error": f"Unexpected status code: {status_code}"} return response.json() def http_get_request(self, path): return self._make_request("GET", path) def http_post_request(self, path, data): return self._make_request("POST", path, json=data) """ GLOBAL_VARIABLES """ INTEGRATION_NAME = "Nozomi Networks" QUERY_PATH = "/api/open/query/do?query=" QUERY_ALERTS_PATH = "/api/open/query/do?query=alerts" QUERY_ASSETS_PATH = "/api/open/query/do?query=assets | sort id" JOB_STATUS_MAX_RETRY = 5 DEFAULT_HEAD_ASSETS = 50 DEFAULT_COUNT_ALERTS = 100 MAX_PAGE_NUMBER_REACHABLE = 100 DEFAULT_HEAD_QUERY = 500 MAX_ASSETS_FINDABLE_BY_A_COMMAND = 100 DEFAULT_ASSETS_FINDABLE_BY_A_COMMAND = 50 """HELPER FUNCTIONS""" def get_client(): return Client() def parse_incident(i): return { "name": f"{i['name']}_{i['id']}", "occurred": datetime.fromtimestamp(i["record_created_at"] / 1000, timezone.utc).isoformat(), # noqa: UP017 "severity": parse_severity(i), "rawJSON": json.dumps(clean_null_terms(i)), } def clean_null_terms(d): clean = {} for key, value in d.items(): if isinstance(value, dict): nested = clean_null_terms(value) if len(nested.keys()) > 0: clean[key] = nested elif value is not None: clean[key] = value return clean def parse_severity(item): result = int(float(item["risk"]) / 2) if result < 1: return 1 return result - 1 if result > 4 else result def ids_from_incidents(incidents_array): return [incident["id"] for incident in incidents_array] def better_than_time_filter(st): t = "" if st: t = f" | where record_created_at > {st}" return t def equal_time_filter(st): t = "" if st: t = f" | where record_created_at == {st}" return t def better_than_id_filter(id): res = "" if id: res = f" | where id > {id}" return res def start_time(last_run, fetch_time_from="7 days"): fetch_time_default, _ = parse_date_range(fetch_time_from, date_format="%Y-%m-%dT%H:%M:%SZ", to_timestamp=True) if has_last_run(last_run): time_from_last_run = f'{last_run.get("last_fetch", fetch_time_default)}' result = f"{fetch_time_default}" if time_from_last_run == "0" else f"{time_from_last_run}" else: result = f"{fetch_time_default}" return result def has_last_run(lr): return lr is not None and "last_fetch" in lr def _fetch_incidents(time_filter, st, page, risk, also_n2os_incidents, client): query = ( f"{QUERY_ALERTS_PATH} | sort record_created_at asc" f"{time_filter(st)}" f"{risk_filter(risk)}" f"{also_n2os_incidents_filter(also_n2os_incidents)}" ) full_path = f"{query}&page={page}&count={min(int(incident_per_run()), 1000)}" return client.http_get_request(full_path)["result"] def incidents_better_than_time(st, page, risk, also_n2os_incidents, client): return _fetch_incidents(better_than_time_filter, st, page, risk, also_n2os_incidents, client) def incidents_equal_to_time(st, page, risk, also_n2os_incidents, client): return _fetch_incidents(equal_time_filter, st, page, risk, also_n2os_incidents, client) def also_n2os_incidents_filter(also_n2os_incidents): if also_n2os_incidents: return "" else: return " | where is_incident == false" def risk_filter(risk): return f" | where risk >= {int(risk)}" if risk else "" def incidents(st, last_run, risk, also_n2os_incidents, client): def get_incident_name(i): return i["name"] current_page = last_run.get("page", 1) ibtt = incidents_better_than_time(st, current_page, risk, also_n2os_incidents, client) if current_page == MAX_PAGE_NUMBER_REACHABLE: iett = incidents_equal_to_time(last_fetched_time(ibtt, last_run), current_page, risk, also_n2os_incidents, client) combined = ibtt + iett if ibtt else iett ibtt = list({incident["id"]: incident for incident in combined}.values()) lft = last_fetched_time(ibtt, last_run) if ibtt is None: return [], lft parsed_incidents = [parse_incident(i) for i in ibtt] parsed_incidents.sort(key=get_incident_name) return parsed_incidents, lft def last_fetched_time(inc, last_run): if inc and len(inc) > 0 and "record_created_at" in inc[-1]: return inc[-1]["record_created_at"] return last_run.get("last_fetch", 0) def last_asset_id(response): return response[-1]["id"] if len(response) > 0 else "" def ack_unack_alerts(ids, status, client): data = [] for id in ids: data.append({"id": id, "ack": status}) if data: client.http_post_request("/api/open/alerts/ack", {"data": data}) def ack_alerts(ids, client): return ack_unack_alerts(ids, True, client) def nozomi_alerts_ids_from_demisto_incidents(demisto_incidents): return ids_from_incidents([json.loads(incident["rawJSON"]) for incident in demisto_incidents]) def close_alerts(args, close_action, client): readable_close_action = "closed_as_security" if close_action == "delete_rules" else "closed_as_change" extracted_ids = argToList(args.get("ids")) human_readable = ( f"Command changes the status of the following alerts: {extracted_ids} " f'passed as "{readable_close_action}" in Nozomi Networks platform.' ) client.http_post_request("/api/open/alerts/close", {"ids": extracted_ids, "close_action": close_action}) return {"readable_output": human_readable, "outputs_prefix": None, "outputs_key_field": None, "outputs": None} def filter_from_args(args): if args and args.get("filter", "") != "": filter = args.get("filter", "") if "| where" in filter: return filter else: return f" | where {filter}" else: return "" def assets_limit_from_args(args): if args: limit = int(args.get("limit", DEFAULT_ASSETS_FINDABLE_BY_A_COMMAND)) if limit > MAX_ASSETS_FINDABLE_BY_A_COMMAND: return MAX_ASSETS_FINDABLE_BY_A_COMMAND else: return limit else: return DEFAULT_ASSETS_FINDABLE_BY_A_COMMAND def nodes_confirmed_filter(only_nodes_confirmed): if only_nodes_confirmed and only_nodes_confirmed == "True": return " | where mac_address:info.likelihood_level == confirmed" else: return "" def humanize_api_error(error): if "401" in error: return "Authentication error, check your username and password" else: return error """ MAIN_FUNCTION """ def fetch_incidents(client, st=None, last_run=None, risk=None, fetch_also_n2os_incidents=None, test_mode=False): st = st or start_time(demisto.getLastRun(), demisto.params().get("fetchTime", "7 days").strip()) last_run = last_run or demisto.getLastRun() risk = risk or demisto.params().get("riskFrom", None) fetch_also_n2os_incidents = fetch_also_n2os_incidents or demisto.params().get("fecthAlsoIncidents", False) demisto_incidents, last_fetch = incidents(st, last_run, risk, fetch_also_n2os_incidents, client) if not test_mode: next_page = build_next_page(last_run.get("page", 1), len(demisto_incidents)) demisto.setLastRun({"last_fetch": last_fetch_to_set(last_fetch, next_page, st), "page": next_page}) demisto.incidents(demisto_incidents) ack_alerts(nozomi_alerts_ids_from_demisto_incidents(demisto_incidents), client) return demisto_incidents, last_fetch def last_fetch_to_set(last_fetch, next_page, st): return last_fetch if next_page == 1 else st def build_next_page(current_page, incidents_count): if current_page >= MAX_PAGE_NUMBER_REACHABLE or incidents_count < incident_per_run(): if incidents_count == 0: # if demisto_incidents for this page is empty will be queried until returns at least one item next_page = current_page else: next_page = 1 else: next_page = current_page + 1 return next_page def incident_per_run(): return int(demisto.params().get("incidentPerRun", DEFAULT_COUNT_ALERTS)) def is_alive(client): error = None try: client.http_get_request(f"{QUERY_ALERTS_PATH} | count") except Exception as e: error = e.args[0] return humanize_api_error(error) if error else "ok" def close_incidents_as_change(args, client): return close_alerts(args, "learn_rules", client) def close_incidents_as_security(args, client): return close_alerts(args, "delete_rules", client) def query(args, client): title = f"{INTEGRATION_NAME} - Results for Query" response = client.http_get_request(f'{QUERY_PATH}{args.get("query", "")} | head {DEFAULT_HEAD_QUERY}') if "error" in response and response["error"]: return {"outputs_key_field": None, "outputs": None, "outputs_prefix": None, "readable_output": response["error"]} result = response["result"] human_readable = tableToMarkdown(t=result, name=title, removeNull=True) return { "outputs": result, "outputs_prefix": "Nozomi.Query.Result", "outputs_key_field": "", "readable_output": human_readable, } def find_assets(args, client, head=DEFAULT_HEAD_ASSETS): title = f"{INTEGRATION_NAME} - Results for Find Assets" limit = assets_limit_from_args(args) result = [] # type: List[dict] last_id = None are_there_assets_to_request = True if head > limit: head = limit while limit > len(result) and are_there_assets_to_request: raw_response = client.http_get_request( f"{QUERY_ASSETS_PATH}{filter_from_args(args)}{better_than_id_filter(last_id)} | head {head}" ) if raw_response["result"] is None: continue last_id = last_asset_id(raw_response["result"]) are_there_assets_to_request = head == len(raw_response["result"]) result = result + raw_response["result"] if not result: return { "outputs": [], "outputs_prefix": "Nozomi.Asset", "outputs_key_field": "id", "readable_output": f"{INTEGRATION_NAME} - No assets found", } human_readable = tableToMarkdown( t=result, name=title, removeNull=True, headers=["id", "name", "ip", "mac_address", "level firmware_version", "os vendor", "type"], ) return {"outputs": result, "outputs_prefix": "Nozomi.Asset", "outputs_key_field": "id", "readable_output": human_readable} def find_ip_by_mac(args, client): mac = args.get("mac", "") only_nodes_confirmed = args.get("only_nodes_confirmed", True) result_error = False result = {} # type: Dict response = client.http_get_request( f"{QUERY_PATH}nodes | select ip mac_address | where mac_address == {mac}{nodes_confirmed_filter(only_nodes_confirmed)}" ) if len(response["result"]) == 0: human_readable = f"{INTEGRATION_NAME} - No IP results were found for mac address: {mac}" result_error = True prefix = None else: ips = [node["ip"] for node in response["result"]] human_readable = f"{INTEGRATION_NAME} - Results for the Ip from Mac Search is {ips}" result = {"ips": ips, "mac": mac} prefix = "Nozomi.IpByMac" return { "outputs": None if result_error else result, "outputs_prefix": prefix, "outputs_key_field": None, "readable_output": human_readable, } """ EXECUTION """ def main(): try: client = get_client() if demisto.command() == "fetch-incidents": fetch_incidents(client) elif demisto.command() == "test-module": if demisto.params().get("isFetch"): fetch_incidents(client, test_mode=True) demisto.results("ok") else: demisto.results(is_alive(client)) elif demisto.command() == "nozomi-close-incidents-as-change": return_results(CommandResults(**close_incidents_as_change(demisto.args(), client))) elif demisto.command() == "nozomi-close-incidents-as-security": return_results(CommandResults(**close_incidents_as_security(demisto.args(), client))) elif demisto.command() == "nozomi-find-assets": return_results(CommandResults(**find_assets(demisto.args(), client))) elif demisto.command() == "nozomi-query": return_results(CommandResults(**query(demisto.args(), client))) elif demisto.command() == "nozomi-find-ip-by-mac": return_results(CommandResults(**find_ip_by_mac(demisto.args(), client))) except Exception as e: error_message = f"Error of type {type(e).__name__} occurred: {str(e)}" demisto.error(error_message) return_error(error_message) if __name__ in ("__main__", "__builtin__", "builtins"): main()