ZeroNetworksSegmentEventCollector

Integrates with Zero Networks Segment API to fetch and process audit and network events.

Network Security · Zero Networks Segment

Details

IDZeroNetworksSegmentEventCollector
ProviderZero Networks
CategoryNetwork Security
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesXSIAM

README

This is the Zero Networks event collector integration for Cortex XSIAM.

Configure Zero Networks Segment Event Collector in Cortex

Parameter Description Required
Server URL   True
API Key The API key to use for connection. True
Fetch network events   False
Network Activity Filters Use filters to reduce the amount of events. False
Maximum audit events to fetch Maximum number of audit events per fetch. The default value is 10000. False
Maximum network activities events to fetch Maximum number of network activities events per fetch. The default value is 2000. False
Trust any certificate (not secure)   False
Use system proxy settings   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.

zero-networks-segment-get-events


Gets events from Zero Networks Segment.

Base Command

zero-networks-segment-get-events

Input

Argument Name Description Required
should_push_events If true, the command will create events, otherwise it will only display them. Warning: Using this argument may lead to duplicate events. Possible values are: true, false. Default is false. Required
from_date Date from which to get events. Optional

Context Output

There is no context output for this command.

Command Example

!zero-networks-segment-get-events from_date="2024-08-29T12:00:15.000Z"

Human Readable Output

Audit Events

timestamp auditType destinationEntitiesList details enforcementSource isoTimestamp
1724928222479 1 {“id”: “fake_id”} {“rule”:”fake_rule”, “id”:”fake_id”} 1 2024-08-29T10:43:42.479Z

Network Activities Events

timestamp protocol state trafficType dst src
1724924207581 6 2 1 {“assetId”:”fake_dst”, “ip”:”1.2.3.4”} {“assetId”:”fake_src”, “ip”:”1.1.1.1”}

Configuration parameters

  • url — Server URL (required)
  • credentials — (required)
  • event_types_to_fetch — Event Types To Fetch
  • network_activity_filters — Network Activity Filters
  • max_fetch_audit — Maximum audit events to fetch
  • max_fetch_network — Maximum network activities events to fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • zero-networks-segment-get-events

    Gets events from Zero Networks Segment.

import hashlib
import json

import demistomock as demisto  # noqa: F401
import pytest
from CommonServerPython import *
from ZeroNetworksSegmentEventCollector import (
    AUDIT,
    NETWORK_ACTIVITIES,
    Client,
    create_id,
    fetch_all_events,
    fetch_events,
    get_events,
    get_max_results_and_limit,
    handle_log_types,
    initialize_start_timestamp,
    process_events,
    update_last_run,
)


class MockClient(Client):
    def __init__(self, server_url: str, proxy: bool, verify: bool, headers: dict):
        pass

    def search_events(self, limit, cursor, log_type, filters=None):
        if cursor <= 123456:
            return {"items": [{"id": 1, "timestamp": 123456}, {"id": 2, "timestamp": 123457}], "scrollCursor": 123457}
        elif cursor == 123457:
            return {"items": [{"id": 2, "timestamp": 123457}, {"id": 3, "timestamp": 12345678}], "scrollCursor": 12345678}
        elif cursor == 12345678:
            return {"items": [{"id": 3, "timestamp": 12345678}, {"id": 4, "timestamp": 12345678}], "scrollCursor": 12345678}
        else:
            return {"items": [], "scrollCursor": ""}


def util_load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.load(f)


@pytest.mark.parametrize(
    "inputs, expected_outputs",
    [
        (test_case["inputs"], test_case["expected_outputs"])
        for test_case in util_load_json("test_data/test_process_events_params.json")["test_cases"]
    ],
)
def test_process_events(mocker, inputs, expected_outputs):
    """
    Given:
        - Mocked `create_id` function.
        - Inputs including `input_events`, `input_previous_ids`, `input_last_event_time`, and any other relevant parameters.

    When:
        Calling the process_events function with the provided inputs.

    Then:
        - Verify that `new_events` matches the `expected_events`.
        - Ensure that `updated_ids` matches the `expected_ids`.
        - Confirm that `updated_last_event_time` matches the `expected_last_event_time`.
    """

    def mock_create_id_function(event, log_type):
        return event.get("id")

    mocker.patch("ZeroNetworksSegmentEventCollector.create_id", side_effect=mock_create_id_function)
    input_events, input_previous_ids, input_last_event_time, max_results, num_results = inputs
    expected_events, expected_ids, expected_last_event_time, expected_num_results = expected_outputs

    new_events, updated_ids, updated_last_event_time = process_events(
        input_events, input_previous_ids, input_last_event_time, max_results, num_results, "audit"
    )

    assert new_events == expected_events
    assert updated_ids == expected_ids
    assert updated_last_event_time == expected_last_event_time


def test_initialize_start_timestamp_with_existing_timestamp():
    """
    Given
            A dictionary with a nested structure and a specific key
    When
            Calling `initialize_start_timestamp` with the dictionary and the key
    Then
            The function should return the value associated with the specified key
            The result is verified to match the expected value
    """
    last_run = {"audit": {"last_fetch": 1234567890}}
    result = initialize_start_timestamp(last_run, "audit")
    assert result == 1234567890


@pytest.mark.parametrize(
    "params, log_type, expected",
    [
        (test_case["params"], test_case["log_type"], test_case["expected"])
        for test_case in util_load_json("test_data/test_get_max_results_params.json")["test_cases"]
    ],
)
def test_get_max_results_and_limit_with_param(params, log_type, expected):
    """
    Given:
        - A set of parameters (params) which include configuration options related to fetching results.
        - A log type (log_type) indicating the type of log data being processed.
        - An expected tuple (expected) representing the maximum number of results and a limit value.

    When:
        Calling the get_max_results_and_limit function with the provided parameters and log type.

    Then:
        Verify that the function returns a tuple matching the expected result, ensuring it correctly processes
        the parameters to determine the maximum results and limit.
    """
    result = get_max_results_and_limit(params, log_type)
    assert result == tuple(expected)


@pytest.mark.parametrize(
    "event_types_to_fetch, expected",
    [(["Audit", "Network Activities"], [AUDIT, NETWORK_ACTIVITIES]), (["Audit"], [AUDIT]), ([], [])],
)
def test_handle_log_types(event_types_to_fetch, expected):
    """
    Given:
        - A list or set of event types to fetch (event_types_to_fetch).
        - An expected result (expected) representing the processed event types.

    When:
        Calling the handle_log_types function with the provided event types to fetch.

    Then:
        Verify that the function returns the expected result, confirming that it correctly processes the event types
        as intended.
    """
    result = handle_log_types(event_types_to_fetch)
    assert result == expected


def test_handle_not_valid_log_types():
    """
    Given:
        - An `event_types` list containing invalid event types, such as `['fake_type']`.

    When:
        Calling the `handle_log_types` function with the provided `event_types` list.

    Then:
        - Verify that `handle_log_types` raises a `DemistoException`.
        - Ensure that the exception message includes "Event type title 'fake_type' is not valid."
    """
    event_types = ["fake_type"]
    with pytest.raises(DemistoException) as e:
        handle_log_types(event_types)
    assert "'fake_type' is not valid event type, please select from the following list:" in str(e)


@pytest.mark.parametrize(
    "last_run, log_type, last_event_time, previous_ids, expected",
    [
        (case["last_run"], case["log_type"], case["last_event_time"], case["previous_ids"], case["expected"])
        for case in util_load_json("test_data/test_update_last_run_params.json")
    ],
)
def test_update_last_run(last_run, log_type, last_event_time, previous_ids, expected):
    """
    Given:
        - Initial input values (`last_run`, `log_type`, `last_event_time`, `previous_ids`).
        - An expected result after processing.

    When:
        Calling the `update_last_run` function with the given inputs.

    Then:
        Verify that the function returns the expected result, ensuring it processes the inputs correctly.
    """
    last_run_result = update_last_run(last_run, log_type, last_event_time, previous_ids)
    assert last_run_result == expected


@pytest.mark.parametrize(
    "max_results, limit, last_run, expected_last_run, expected_collected_events, start_timestamp",
    [
        (
            case["max_results"],
            case["limit"],
            case["last_run"],
            case["expected_last_run"],
            case["expected_collected_events"],
            case["start_timestamp"],
        )
        for case in util_load_json("test_data/test_fetch_events_params.json")["test_cases"]
    ],
)
def test_fetch_events(mocker, max_results, limit, last_run, expected_last_run, expected_collected_events, start_timestamp):
    """
    Given:
        - A mock setup for dependencies (`mocker`, `mock_create_id_function`, `MockClient`).
        - Input parameters for the `fetch_events` function (`params`, `last_run`, `start_timestamp`).
        - Expected results after fetching events (`expected_last_run`, `expected_collected_events`).

    When:
        Calling the `fetch_events` function with the mocked client and provided inputs.

    Then:
        Verify that the function returns the expected `last_run` and `collected_events`, ensuring it processes the inputs
        correctly and integrates with the mocks as intended.
    """

    def mock_create_id_function(event, log_type):
        return event.get("id")

    mocker.patch("ZeroNetworksSegmentEventCollector.create_id", side_effect=mock_create_id_function)
    mocker.patch("ZeroNetworksSegmentEventCollector.initialize_start_timestamp", return_value=start_timestamp)

    mock_client = MockClient("", False, False, {})

    result_last_run, result_collected_events = fetch_events(
        mock_client, last_run, start_timestamp, "audit", [], max_results, limit
    )

    assert result_last_run == expected_last_run
    assert result_collected_events == expected_collected_events


@pytest.mark.parametrize(
    "args, last_run, expected_events, expected_hr",
    [
        # Basic case without from_date
        (
            {},
            {},
            [
                {"id": 1, "timestamp": 123456, "source_log_type": "audit"},
                {"id": 2, "timestamp": 123457, "source_log_type": "audit"},
            ],
            "mocked HR output",
        ),
        # Case with from_date
        (
            {"from_date": "2024-08-25T12:34:56.000Z"},
            {},
            [
                {"id": 1, "timestamp": 123456, "source_log_type": "audit"},
                {"id": 2, "timestamp": 123457, "source_log_type": "audit"},
            ],
            "mocked HR output",
        ),
        # Case with no events returned
        ({}, {}, [], "mocked HR output"),
    ],
)
def test_get_events(mocker, args, last_run, expected_events, expected_hr):
    """
    Given:
        - A mock setup for dependencies (`mocker`, `mock_fetch_events`, `mock_table_to_markdown`).
        - Input arguments for the `get_events` function (`args`, `last_run`, `params`, `log_types`).
        - Expected results for events and human-readable output (`expected_events`, `expected_hr`).

    When:
        Calling the `get_events` function with the mocked client and provided inputs.

    Then:
        Verify that the function returns the expected events and performs the correct integration with the mocks:
        - Ensure the returned events match `expected_events`.
        - Confirm that `tableToMarkdown` was called once with the correct parameters if events are present.
    """
    mock_fetch_events = mocker.patch("ZeroNetworksSegmentEventCollector.fetch_events")
    mock_fetch_events.return_value = (last_run, expected_events)

    mock_table_to_markdown = mocker.patch("ZeroNetworksSegmentEventCollector.tableToMarkdown")
    mock_table_to_markdown.return_value = expected_hr

    mock_client = MockClient("", False, False, {})

    result_events, _ = get_events(mock_client, args, last_run, params={}, log_types=["audit"])

    assert result_events == expected_events
    if result_events:
        mock_table_to_markdown.assert_called_once_with(name="Audit Events", t=expected_events)


def compute_hash(combined_string):
    hash_object = hashlib.sha256(combined_string.encode())
    return hash_object.hexdigest()


@pytest.mark.parametrize(
    "event, log_type, expected_id",
    [
        # Test case for AUDIT with all fields present
        (
            {"timestamp": "1234567890", "reportedObjectId": "123", "performedBy": {"id": "user"}},
            AUDIT,
            compute_hash("1234567890-123-user"),
        ),
        # Test case for AUDIT with missing performedBy name
        ({"timestamp": "1234567890", "reportedObjectId": "123", "performedBy": {}}, AUDIT, compute_hash("1234567890-123-")),
        # Test case for NETWORK_ACTIVITIES with all fields present
        (
            {"timestamp": "0987654321", "src": {"assetId": "src-asset"}, "dst": {"assetId": "dst-asset"}},
            NETWORK_ACTIVITIES,
            compute_hash("0987654321-src-asset-dst-asset"),
        ),
        # Test case for NETWORK_ACTIVITIES with missing src or dst assetId
        (
            {"timestamp": "0987654321", "src": {}, "dst": {"assetId": "dst-asset"}},
            NETWORK_ACTIVITIES,
            compute_hash("0987654321--dst-asset"),
        ),
        (
            {"timestamp": "0987654321", "src": {"assetId": "src-asset"}, "dst": {}},
            NETWORK_ACTIVITIES,
            compute_hash("0987654321-src-asset-"),
        ),
        # Test case with missing timestamp
        ({"reportedObjectId": "123", "performedBy": {"id": "user"}}, AUDIT, compute_hash("None-123-user")),
        # Test case with empty event
        ({}, AUDIT, compute_hash("None--")),
    ],
)
def test_create_id(event, log_type, expected_id):
    """
    Given:
        - An `event` and a `log_type` used to generate an ID.
        - An `expected_id` that represents the anticipated result.

    When:
        Calling the `create_id` function with the provided `event` and `log_type`.

    Then:
        Verify that the function returns the expected ID, ensuring it generates the ID correctly based on the inputs.
    """
    result = create_id(event, log_type)
    assert result == expected_id


def test_fetch_events_limit_logic(mocker):
    """
    Given:
        - Mocked functions such as `initialize_start_timestamp`, `get_max_results_and_limit`, and others.
        - Specific parameters and inputs required by `fetch_events`.

    When:
        Calling the `fetch_events` function with these parameters and a limit value.

    Then:
        - Verify that the limit values in the calls to `mock_search_events` increase as expected.
        - Ensure that the number of calls to `mock_search_events` matches the expected count.
    """
    mocker.patch("ZeroNetworksSegmentEventCollector.initialize_start_timestamp", return_value=1000)
    mocker.patch("ZeroNetworksSegmentEventCollector.handle_log_types", return_value=["audit"])
    mock_search_events = mocker.patch(
        "ZeroNetworksSegmentEventCollector.Client.search_events",
        return_value={"items": [{"id": 1, "timestamp": 1}], "scrollCursor": "1"},
    )
    mocker.patch("ZeroNetworksSegmentEventCollector.process_events", return_value=([], {}, 0))

    max_results, limit = (1, 1)
    last_run = {}
    client = Client("", False, False, {})

    fetch_events(client, last_run, 1000, "audit", [], max_results, limit)

    calls = list(mock_search_events.call_args_list)
    first_call_limit = calls[0][0][0]  # Limit in the first call
    assert first_call_limit == 1

    second_call_limit = calls[1][0][0]  # Limit in the second call
    assert second_call_limit == 2

    assert len(calls) == 2


@pytest.mark.parametrize(
    "last_run, log_types, mock_initialize_start_timestamp, mock_fetch_events_side_effect, expected_last_run, expected_all_events",
    [
        (
            case["last_run"],
            case["log_types"],
            case["mock_initialize_start_timestamp"],
            case["mock_fetch_events_side_effect"],
            case["expected_last_run"],
            case["expected_all_events"],
        )
        for case in util_load_json("test_data/test_fetch_all_events_params.json")["test_cases"]
    ],
)
def test_fetch_all_events(
    mocker,
    last_run,
    log_types,
    mock_initialize_start_timestamp,
    mock_fetch_events_side_effect,
    expected_last_run,
    expected_all_events,
):
    """
    Given:
        - A mock setup for dependencies (`mocker`, `mock_initialize_start_timestamp`, `mock_fetch_events_side_effect`).
        - Initial state (`last_run`) and a list of `log_types`.
        - Expected results for the last run and all events after fetching (`expected_last_run`, `expected_all_events`).

    When:
        Calling the `fetch_all_events` function with the mocked client, initial state, and log types.

    Then:
        Verify that the function returns the expected `last_run` and `all_events`, ensuring it correctly integrates with the mocks
        and processes the inputs as intended.
    """
    mocker.patch("ZeroNetworksSegmentEventCollector.initialize_start_timestamp", return_value=mock_initialize_start_timestamp)
    mocker.patch("ZeroNetworksSegmentEventCollector.fetch_events", side_effect=mock_fetch_events_side_effect)

    client = MockClient("", False, False, {})

    result_last_run, result_all_events = fetch_all_events(client, {}, last_run, log_types)

    assert result_last_run == expected_last_run
    assert result_all_events == expected_all_events