JizoM

This integration ensures interaction with the JizoM API.

Network Security · JizoM

Details

IDJizoM
ProviderCYBERDYNE inc
CategoryNetwork Security
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658

README

This integration ensures interaction with the JizoM API.
This integration was integrated and tested with version 12.3 of JizoM.

Configure JizoM in Cortex

Parameter Description Required
Username Reliability of the source providing the intelligence data. True
Password   True
Server URL. e.g., https://127.0.0.1:9001   True
Fetch incidents   False
Incident type   False
Trust any certificate (not secure)   False
Use system proxy settings   False
Incidents Fetch Interval   False
First fetch time (number, time unit, for example, 12 hours, 7 days, 3 months, 1 year)   False
Maximum number of alerts per fetch   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.

jizo-m-protocols-get


Get the list of alerts sorted by protocols.

Base Command

jizo-m-protocols-get

Input

Argument Name Description Required
ip_src Ipv4 or Ipv6 of the source. Optional
ip_dest Ipv4 or Ipv6 of the destination. Optional
datetime_from Get the alerts that were occurred from this date, for example, “3 days ago”, “2020-01-01-00:00:00”. The default value is 7 days ago. Optional
datetime_to Get the alerts that were occurred up to this date, for example, “3 days ago”, “2020-01-01-00:00:00”. The default is now. Optional
probe_name The name of the jizo probe. Optional
page A page number for pagination. Optional
limit The maximum number of protocols to display per alert. Optional

Context Output

Path Type Description
JizoM.Protocols.alerts_flows.count Number The number of displayed alerts.
JizoM.Protocols.alerts_flows.data String The details of alerts.
JizoM.Protocols.alerts_flows.total Number The total number of alerts.
JizoM.Protocols.alerts_files.count Number The number of displayed alerts.
JizoM.Protocols.alerts_files.data String The details of alerts.
JizoM.Protocols.alerts_files.total Number The total number of alerts.
JizoM.Protocols.alerts_usecase.count Number The number of displayed alerts.
JizoM.Protocols.alerts_usecase.data String The details of alerts.
JizoM.Protocols.alerts_usecase.total Number The total number of alerts.

jizo-m-peers-get


Get list of IP addresses connected to a specific one.

Base Command

jizo-m-peers-get

Input

Argument Name Description Required
ip_src Ipv4 or Ipv6 of the source. Optional
ip_dest Ipv4 or Ipv6 of the destination. Optional
datetime_from Get the alerts that were occurred from this date, for example, “3 days ago”, “2020-01-01-00:00:00”. The default value is 7 days ago. Optional
datetime_to Get the alerts that were occurred up to this date, for example, “3 days ago”, “2020-01-01-00:00:00”. The default is now. Optional
probe_name The name of the jizo probe. Optional
page A page number for pagination. Optional
limit The maximum number of samples to display per alert. Optional

Context Output

Path Type Description
JizoM.Peers.alerts_flows.count Number The number of displayed alerts.
JizoM.Peers.alerts_flows.data String The details of alerts.
JizoM.Peers.alerts_flows.total Number The total number of alerts.
JizoM.Peers.alerts_files.count Number The number of displayed alerts.
JizoM.Peers.alerts_files.data String The details of alerts.
JizoM.Peers.alerts_files.total Number The total number of alerts.
JizoM.Peers.alerts_usecase.count Number The number of displayed alerts.
JizoM.Peers.alerts_usecase.data String The details of alerts.
JizoM.Peers.alerts_usecase.total Number The total number of alerts.

jizo-m-query-records-get


Retrieve all information available on Jizo M, mainly alerts.

Base Command

jizo-m-query-records-get

Input

Argument Name Description Required
ip_src Ipv4 or Ipv6 of the source. Optional
ip_dest Ipv4 or Ipv6 of the destination. Optional
proto The protocol. Possible values are: TCP, UDP, IP, IPSEC, ICMP, ARP. Optional
app_proto The application protocol. Possible values are: HTTP, HTTPS, FTP, DNS, DHCP, DCERPC, SMB, SMTP, SNMP, SSL, SSH, SIP, RDP, RFB, NFS, MQTT, MSN, MODBUS, IMAP, TFTP, KRBS. Optional
port_src The source port. Optional
port_dest The destination port. Optional
flow_id The id of the flow. Optional
sid The id of the rule. Optional
probe_name The name of the jizo probe. Optional
port The alert port. Optional
datetime_from Get the alerts that were occurred from this date, for example, “3 days ago”, “2020-01-01-00:00:00”. The default value is 7 days ago. Optional
datetime_to Get the alerts that were occurred up to this date, for example, “3 days ago”, “2020-01-01-00:00:00”. The default is now. Optional
page A page number for pagination. Optional
limit The maximum number of samples to display per alert. Optional

Context Output

Path Type Description
JizoM.QueryRecords.alerts_flows.count Number The number of displayed alerts.
JizoM.QueryRecords.alerts_flows.data String The details of alerts.
JizoM.QueryRecords.alerts_flows.total Number The total number of alerts.
JizoM.QueryRecords.alerts_files.count Number The number of displayed alerts.
JizoM.QueryRecords.alerts_files.data String The details of alerts.
JizoM.QueryRecords.alerts_files.total Number The total number of alerts.
JizoM.QueryRecords.alerts_usecase.count Number The number of displayed alerts.
JizoM.QueryRecords.alerts_usecase.data String The details of alerts.
JizoM.QueryRecords.alerts_usecase.total Number The total number of alerts.

Configuration parameters

  • credentials — Username (required)
  • url — Server URL. e.g., https://127.0.0.1:9001 (required)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • incidentFetchInterval — Incidents Fetch Interval
  • first_fetch — First fetch time (<number> <time unit>, for example, 12 hours, 7 days, 3 months, 1 year)
  • max_fetch — Maximum number of alerts per fetch

Commands (3)

  • jizo-m-peers-get

    Get the list of IP addresses connected to a specific one.

  • jizo-m-protocols-get

    Get the list of alerts sorted by protocols.

  • jizo-m-query-records-get

    Retrieve all information available on Jizo M, mainly alerts.

import json
from datetime import datetime
from typing import Any

import demistomock as demisto

# Disable Secure Warnings
import urllib3
from CommonServerPython import *

urllib3.disable_warnings()

DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
JIZO_DATE_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
MAX_ALERTS_TO_FETCH = 10
ITEM_TEMP = (
    '"id":{id},"name":"Jizo Alert #{id}",'
    '"alert_type":"{alert_type}","severity":"{severity}",'
    '"category":"{category}","signature":"{signature}",'
    '"IP_source":"{IP_source}","IP_destination":"{IP_destination}","date":"{date}"'
)


""" CLIENT CLASS """


class Client(BaseClient):
    """
    Client class to interact with the service API

    """

    def __init__(
        self,
        base_url: str,
        auth: tuple,
        headers: dict = {},
        verify: bool = False,
        proxy: bool = False,
    ) -> None:
        self.base_url = base_url
        self.auth = auth
        self.headers = headers
        self.verify = verify
        self.proxy = proxy

    def test_module(self) -> bool:
        """Check if the API is active

        Returns:
            dict: response body of the ping endpoint
        """

        url = f"{self.base_url}/ping"
        # Define headers
        headers = {"Content-Type": "application/json"}

        # Sending POST request to the API endpoint with the specified headers and request body
        response = requests.get(
            url, headers=headers, verify=self.verify
        )  # Setting verify=False ignores SSL certificate verification. Be cautious about using it in a production environment.
        # Checking if the request was successful (status code 200)
        return response.status_code == 200

    def get_protocols(self, args: dict[str, Any]):
        """
        Get jizo protocols. You can filter by ip_src, ip_dest.
        You can filter also by timestamp or probe name
        """

        url = f"{self.base_url}/jizo_get_protocols"

        response = requests.get(url, params=args, headers=self.headers, verify=self.verify)
        if response.status_code == 200:
            return response.json()
        else:
            raise DemistoException(response.text, response.status_code, response.reason)

    def get_peers(self, args: dict[str, Any]):
        """
        Get jizo peers. You can filter by ip_src, ip_dest.
        You can filter also by timestamp or probe name or probe Ip

        """

        url = f"{self.base_url}/jizo_get_peers"

        response = requests.get(url, params=args, headers=self.headers, verify=self.verify)
        if response.status_code == 200:
            return response.json()
        else:
            raise DemistoException(response.text, response.status_code, response.reason)

    def get_query_records(self, args: dict[str, Any]):
        """
        Get jizo query records. You can filter by ip_src, proto, port_src, FlowId, Sid.
        You can filter also by timestamp or probe name

        """
        url = f"{self.base_url}/jizo_query_records"

        response = requests.get(url, params=args, headers=self.headers, verify=self.verify)
        if response.status_code == 200:
            return response.json()
        else:
            raise DemistoException(response.text, response.status_code, response.reason)

    def get_alert_list(self, limit: int, start_time: str, last_id: int = 0, first_fetched_ids: list = []):
        """Call jizo_query_records endpoint and select specific fields
        of response to return in alerts summary

        Args:
            limit (int): The number of items to generate.
            start_time (str): first fetch time of incidents.
            last_id(int): last incidents id
            first_fetched_ids (list): The ids of the first fetched alerts

        Returns:
            list[dict]: data of formatted items
        """

        context_data = self.get_query_records(args={"datetime_from": start_time})
        alerts: list[dict] = []
        # Get alerts details
        alert_data = context_data["alerts_flows"]["data"]

        if last_id == 0 and not bool(first_fetched_ids):  # means no previous alerts were fetched
            # Save first fetched alert id to check later
            # for other coming alerts
            first_fetched_ids.append(alert_data[0]["idx"])

        if bool(first_fetched_ids) and first_fetched_ids[-1] == -1 and alert_data[0]["idx"] not in first_fetched_ids:
            # Means the last element in alert_data list was reached in previous fetch
            # and, new alerts were coming
            last_id = 0
            first_fetched_ids.append(alert_data[0]["idx"])

        elif bool(first_fetched_ids) and first_fetched_ids[-1] == -1:
            # In case no new alerts have come, exit with empty list
            # of alerts to fetch
            return alerts, first_fetched_ids

        # Get index of next alert to fetch
        if last_id == 0:
            next_index_to_fetch = 0
        else:
            # search for last fetched alert index
            next_index_to_fetch = next((index for (index, d) in enumerate(alert_data) if d["idx"] == last_id), 0)
            next_index_to_fetch += 1

        if bool(alert_data):
            for i in range(limit):
                if next_index_to_fetch + i == len(alert_data) or (
                    alert_data[next_index_to_fetch + i]["idx"] in first_fetched_ids
                    and alert_data[next_index_to_fetch + i]["idx"] != first_fetched_ids[-1]
                ):
                    # Means the final element in alert_data list was fetched
                    # Or, In this fetch ,new coming alerts were fetched, but we are reaching
                    # indexes of alerts that were already fetched in previous ones
                    first_fetched_ids.append(-1)
                    break

                severity = alert_data[next_index_to_fetch + i].get("severity", "4")
                category = alert_data[next_index_to_fetch + i].get("alert_category", "")
                # Fill in the alert item
                item = ITEM_TEMP.format(
                    id=alert_data[next_index_to_fetch + i]["idx"],
                    alert_type="alert flow",
                    severity=severity,
                    category=category,
                    signature=alert_data[next_index_to_fetch + i].get("signature", ""),
                    IP_source=alert_data[next_index_to_fetch + i].get("ip_src", ""),
                    IP_destination=alert_data[next_index_to_fetch + i].get("ip_dest", ""),
                    date=formatting_date(alert_data[next_index_to_fetch]["date"]["date"]),
                )
                dict_item = json.loads("{" + item + "}")
                alerts.append(dict_item)

        return alerts, first_fetched_ids


""" HELPER FUNCTIONS """


def formatting_date(date: str) -> str:
    """
    Converts date retrieved from Jizo to Cortex XSOAR date format
    """

    formatted = datetime.strptime(date, JIZO_DATE_FORMAT)

    return datetime.strftime(formatted, DATE_FORMAT)


def convert_date(date: str) -> str:
    """
    Converts date of format n days ago to datetime
    """

    formatted = datetime.now() - timedelta(days=int(date.split(" ")[0]))

    return formatted.strftime(JIZO_DATE_FORMAT)


def convert_to_demisto_severity(severity: str) -> int:
    """
    Maps Jizo severity to Cortex XSOAR severity.
    In Jizo severities are from 4 to 1 (Alert of severity 1 is critical)
    In Cortex XSOAR it is the opposite logic

    Args:
        severity (str): severity as returned from the JizoM API.

    Returns:
        int: Cortex XSOAR Severity (1 to 4)
    """

    return {
        "4": IncidentSeverity.LOW,
        "3": IncidentSeverity.MEDIUM,
        "2": IncidentSeverity.HIGH,
        "1": IncidentSeverity.CRITICAL,
    }[severity]


""" COMMAND FUNCTIONS """


def test_module(client: Client) -> str:
    if client.test_module():
        return "ok"
    else:
        return "Request error, please check your API"


def get_token(client: Client):
    try:
        url = f"{client.base_url}/login"

        # Include username and password as JSON in the request body
        data = {
            "username": client.auth[0],
            "password": client.auth[1],
        }

        # Define headers
        headers = {"Content-Type": "application/json"}

        # Sending POST request to the API endpoint with the specified headers and request body
        response = requests.post(
            url, headers=headers, json=data, verify=False
        )  # Setting verify=False ignores SSL certificate verification. Be cautious about using it in a production environment.
        # Checking if the request was successful (status code 200)
        if response.status_code == 200:
            return response.json()
        else:
            return_error(f"Error: {response.status_code} - Authentication failed, please try again with appropriate credentials ")

    except Exception as e:
        return_error(f"An error occurred: {e}")


def get_protocols_command(client: Client, args: dict[str, Any]) -> List[CommandResults]:
    """
    Returns response of jizo_get_protocols endpoint

    Args:
        client (Client): JizoM client to use.

    Returns:
        CommandResults: A  list of ``CommandResults`` object that will be then passed to ``return_results``
    """

    # Call the Client function and get the raw response
    result = client.get_protocols(args)

    command_results = []

    headers = {
        "alerts_flows": ["Protocol", "Probe name", "Flow id", "IP source", "IP destination"],
        "alerts_files": ["Protocol", "Probe name", "Flow id", "IP source", "IP destination"],
        "alerts_usecase": ["Protocol", "Probe name", "Flow id", "IP source", "IP destination"],
    }

    for alert_type in result:
        alert_data = result[alert_type]["data"]
        human_readable = []
        for protocol in alert_data:
            for data in alert_data[protocol]:
                d = {
                    "Protocol": protocol,
                    "Probe name": data.get("probe_name", "None"),
                    "Flow id": data.get("flow_id", "None"),
                    "IP source": data.get("src_ip", "None"),
                    "IP destination": data.get("dest_ip", "None"),
                }

                human_readable.append(d)

        readable_output = tableToMarkdown(
            name=alert_type.replace("_", " "), t=human_readable, removeNull=True, headers=headers[alert_type]
        )
        command_results.append(
            CommandResults(
                readable_output=readable_output,
                outputs_prefix=f"JizoM.Protocols.{alert_type}",
                outputs_key_field="flow_id",
                outputs=result[alert_type],
            )
        )

    return command_results


def get_peers_command(client: Client, args: dict[str, Any]) -> List[CommandResults]:
    # Call the Client function and get the raw response
    result = client.get_peers(args)

    command_results = []

    headers = {
        "alerts_flows": ["Probe name", "IP source", "IP destination", "Protocol", "Flow id"],
        "alerts_files": ["Probe name", "IP source", "IP destination", "Protocol", "Flow id"],
        "alerts_usecase": ["Probe name", "IP source", "IP destination", "Protocol", "Flow id"],
    }

    for alert_type in result:
        alert_data = result[alert_type]["data"]
        human_readable = []
        for probe in alert_data:
            for ip in alert_data[probe]:
                for data in alert_data[probe][ip]:
                    d = {
                        "Probe name": probe,
                        "IP source": ip,
                        "IP destination": data.get("dest_ip", "None"),
                        "Protocol": data.get("protocol", "None"),
                        "Flow id": data.get("flow_id", "None"),
                    }

                    human_readable.append(d)

        readable_output = tableToMarkdown(
            name=alert_type.replace("_", " "),
            t=human_readable,
            removeNull=True,
            headers=headers[alert_type],
        )
        command_results.append(
            CommandResults(
                readable_output=readable_output,
                outputs_prefix=f"JizoM.Peers.{alert_type}",
                outputs_key_field="flow_id",
                outputs=result[alert_type],
            )
        )

    return command_results


def get_query_records_command(client: Client, args: dict[str, Any]) -> List[CommandResults]:
    # Call the Client function and get the raw response
    result = client.get_query_records(args)

    command_results = []

    headers = {
        "alerts_flows": ["Probe name", "IP source", "IP destination", "Alert category", "Severity"],
        "alerts_files": ["Probe name", "Rule name", "Rule type", "File name", "Message"],
        "alerts_usecase": ["Probe name", "IP source", "IP destination"],
    }
    for alert_type in result:
        alert_data = result[alert_type]["data"]
        human_readable = []
        for value in alert_data:
            if "flows" in alert_type:
                d = {
                    "Probe name": value.get("ip_probe", "None"),
                    "IP source": value.get("ip_src", "None"),
                    "IP destination": value.get("ip_dest", "None"),
                    "Alert category": value.get("alert_category", "None"),
                    "Severity": convert_to_demisto_severity(str(value.get("severity", "4"))),
                }
            elif "files" in alert_type:
                d = {
                    "Probe name": value.get("probe_name", "None"),
                    "Rule name": value.get("rule_name", "None"),
                    "Rule type": value.get("type_rule", "None"),
                    "File name": value.get("filename", "None"),
                    "Message": value.get("message", "None"),
                }
            else:
                d = {
                    "Probe name": value.get("probe_name", "None"),
                    "IP source": value.get("ip_src", "None"),
                    "IP destination": value.get("ip_dest", "None"),
                }
            human_readable.append(d)

        readable_output = tableToMarkdown(
            name=alert_type.replace("_", " "), t=human_readable, removeNull=True, headers=headers[alert_type]
        )
        command_results.append(
            CommandResults(
                readable_output=readable_output,
                outputs_prefix=f"JizoM.QueryRecords.{alert_type}",
                outputs_key_field="idx",
                outputs=result[alert_type],
            )
        )

    return command_results


def fetch_incidents(
    client: Client, max_results: int, last_run: dict[str, Any], first_fetch_time: str
) -> tuple[dict[str, Any], List[dict]]:
    """
    Fetch incidents (alerts) from Jizo Manager API.

    Parameters:
    - client (Client): Client object to interact with the API.
    - max_results (int, optional): Maximum number of incidents to fetch.
    - last_run (Dict[str, Any]): Dictionary containing details about the last time incidents were fetched.
    - first_fetch_time (str): ISO formatted string indicating the first time from which to start fetching incidents.


    Returns:
    - Tuple[Dict[str, str], List[Dict]]: Tuple containing a dictionary with the `last_fetch` time and a list of fetched incidents.
    """

    # Get the last fetch time, if exists
    last_fetch = last_run.get("last_fetch", None)
    last_ids: list[int] = last_run.get("last_ids", []) or []
    first_fetched_ids: list[int] = last_run.get("first_fetched_ids", []) or []

    if last_fetch is None:
        last_fetch = first_fetch_time

    assert last_fetch

    incidents: list[dict[str, Any]] = []
    last_id = min(last_ids) if last_ids else 0
    demisto.debug(f"Running API query with {last_fetch=}")

    alerts, first_fetched_ids = client.get_alert_list(
        limit=max_results, start_time=first_fetch_time, last_id=last_id, first_fetched_ids=first_fetched_ids
    )

    last_fetched_time = alerts[-1]["date"] if alerts else last_fetch
    last_ids = []
    for alert in alerts:
        if alert["date"] == last_fetched_time:
            last_ids.append(alert["id"])

        incident = {
            "name": alert["name"],
            "occurred": alert["date"],
            "type": "Jizo Alert",  # Map to Jizo ALert which is specific XSOAR alert Type
            "severity": convert_to_demisto_severity(alert.get("severity", "low")),
            "Category": alert["category"],
            "Signature": alert["signature"],
            "rawJSON": json.dumps(alert),
        }

        incidents.append(incident)

    next_run = {"last_fetch": last_fetched_time, "last_ids": last_ids, "first_fetched_ids": first_fetched_ids}
    return next_run, incidents


""" MAIN FUNCTION """


def main() -> None:  # pragma: no cover
    """
    main function, parses params and runs command functions
    """

    params = demisto.params()
    args = demisto.args()
    command = demisto.command()

    # get the service API url
    base_url = params.get("url")

    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)

    first_fetch_time = arg_to_datetime(
        arg=params.get("first_fetch", "7 days"),
        arg_name="First fetch time",
        required=True,
    )
    demisto.debug(f" first fetch time {first_fetch_time}")
    headers = {
        "Content-Type": "application/json",
    }
    # get credentials
    username = demisto.params().get("credentials", {}).get("identifier")
    password = demisto.params().get("credentials", {}).get("password")
    demisto.debug(f"Command being called is {command}")
    # convert date to jizo date format
    if "datetime_from" in args and "days" in args["datetime_from"]:
        args["datetime_from"] = convert_date(args["datetime_from"])
    if "datetime_to" in args and "days" in args["datetime_to"]:
        args["datetime_to"] = convert_date(args["datetime_to"])

    try:
        client = Client(
            base_url=base_url,
            auth=(username, password),
            headers=headers,
            verify=verify_certificate,
            proxy=proxy,
        )

        # get token
        connect = get_token(client)
        token = connect["token"]
        # add token to headers
        client.headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {token}",
        }
        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            return_results(test_module(client))
        elif command == "fetch-incidents":
            # Convert the argument to an int using helper function or set to MAX_ALERTS_TO_FETCH
            max_results = arg_to_number(arg=params.get("max_fetch"), arg_name="max_fetch", required=False)
            if not max_results or max_results > MAX_ALERTS_TO_FETCH:
                max_results = MAX_ALERTS_TO_FETCH

            next_run, incidents = fetch_incidents(
                client=client,
                max_results=max_results,
                last_run=demisto.getLastRun(),  # getLastRun() gets the last run dict
                first_fetch_time=datetime.strftime(first_fetch_time, DATE_FORMAT),  # type: ignore
            )

            # saves next_run for the time fetch-incidents is invoked
            demisto.setLastRun(next_run)
            demisto.incidents(incidents)
        elif command == "jizo-m-protocols-get":
            return_results(get_protocols_command(client, args))

        elif command == "jizo-m-peers-get":
            return_results(get_peers_command(client, args))

        elif command == "jizo-m-query-records-get":
            return_results(get_query_records_command(client, args))

        else:
            raise NotImplementedError(f"Command {command} is not implemented")

    # Log exceptions and return errors
    except Exception as e:
        return_error(f"Failed to execute {command} command.\nError:\n{e!s}")


""" ENTRY POINT """

if __name__ in ("__main__", "__builtin__", "builtins"):
    main()