NetskopeEventCollector Deprecated

Deprecated. Use Netskope Event Collector v2 instead.

Analytics & SIEM · Netskope

Details

IDNetskopeEventCollector
ProviderNetskope
CategoryAnalytics & SIEM
From Version6.8.0
Docker Imagedemisto/python3:3.12.11.4508456
Supported ModulesAgentix XSIAM

README

This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.

Configure Netskope Event Collector in Cortex

Parameter Description Required
Server URL   True
API token   True
Trust any certificate (not secure)   False
Use system proxy settings   False
Max events per fetch The maximum amount of events to retrieve per each event type. For more information about event types see the help section. False

Fetch Events Limitation

The collector can handle up to 35K events per minute on average.

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.

netskope-get-events


Returns events extracted from SaaS traffic and or logs.

Base Command

netskope-get-events

Input

Argument Name Description Required
limit The maximum number of alerts to return (default: 10, maximum value - 10000). Optional
should_push_events Set this argument to True in order to create events, otherwise the command will only display them. Optional

Context Output

There is no context output for this command.

Command example

!netskope-get-events limit=1

Context Example

{
    "Netskope": {
        "Event": [
            {
                "_category_id": "8",
                "_correlation_id": "c66ef426-b403-4be5-8052-05d2c81ed321",
                "_ef_received_at": 1658102836562,
                "_event_id": "bd1074e2-fcbc-4c02-98f1-357aeb57f6c8",
                "_forwarded_by": "service-event-forwarder",
                "_gef_src_dp": "NL-AAA",
                "_id": "23a372c433381a6a11798123",
                "_insertion_epoch_timestamp": 1658102843,
                "_raw_event_inserted_at": 1658102836720,
                "_service_identifier": "service-test",
                "access_method": "API Connector",
                "acked": "false",
                "action": "anomaly_detection",
                "activity": "Login Successful",
                "alert": "yes",
                "alert_id": "62d4a3c35b8bdd69ad5e1234",
                "alert_name": "Alert Name",
                "alert_type": "test",
                "anomalyData": {
                    "_t": "CategoricalModeling",
                    "binCount": 6,
                    "convergenceFactor": 0.9863013699,
                    "featureValue": "1.1.1.1",
                    "histo": [
                        {
                            "bin": "2.2.2.2",
                            "count": 205
                        },
                        {
                            "bin": "3.3.3.3",
                            "count": 30
                        },
                        {
                            "bin": "4.4.4.4",
                            "count": 1
                        }
                    ],
                    "modelId": "test",
                    "observationCount": 0,
                    "percentileThresholdCount": 6,
                    "probability": 0,
                    "sampleCount": 438,
                    "scope": "User"
                },
                "anomaly_type": "test-type",
                "app": "Microsoft Office 365 Sharepoint Online",
                "appcategory": "Collaboration",
                "category": "Collaboration",
                "cci": 91,
                "ccl": "excellent",
                "count": 1,
                "createdTime": "2022-07-18 00:05:23.321000",
                "event_type": "alert",
                "instance_id": "test-instance",
                "organization_unit": "test",
                "other_categories": [],
                "score": 75,
                "severity": "Low",
                "site": "Microsoft Office 365 Sharepoint Sites",
                "src_country": "PH",
                "src_geoip_src": 2,
                "src_latitude": 456.789,
                "src_location": "Test",
                "src_longitude": 123.456,
                "src_region": "Province of Somewhere",
                "src_zipcode": "1234",
                "srcip": "6.6.6.6",
                "timestamp": "2022-07-17T23:48:52.000Z",
                "traffic_type": "CloudApp",
                "type": "nspolicy",
                "ur_normalized": "test@test.com",
                "user": "test@test.com",
                "userkey": "test@test.com",
                "windowId": 1658016000000
            },
            {
                "_category_id": "8",
                "_correlation_id": "57e53633-3eb9-4055-9e84-07de4c367347",
                "_ef_received_at": 1656449549192,
                "_event_id": "7dc94895-fe14-456d-b9c8-0a7f0dac5064",
                "_forwarded_by": "service-event-forwarder",
                "_gef_src_dp": "ABCD",
                "_id": "9f806593aa4385e4fc14865c",
                "_insertion_epoch_timestamp": 1656449557,
                "_raw_event_inserted_at": 1656449549850,
                "_service_identifier": "service-introspection",
                "_session_begin": 1,
                "access_method": "API Connector",
                "activity": "Login Successful",
                "alert": "no",
                "app": "Microsoft Office 365 Sharepoint Online",
                "app_activity": "UserLoggedIn",
                "app_session_id": 6162799428773683,
                "appcategory": "Collaboration",
                "browser": "unknown",
                "category": "Collaboration",
                "cci": 91,
                "ccl": "excellent",
                "count": 1,
                "device": "Other",
                "dst_latitude": "",
                "dst_longitude": "",
                "event_type": "application",
                "from_user": "test@test.com",
                "instance_id": "some-instance",
                "netskope_activity": "False",
                "object": "test@test.com",
                "object_id": "test@test.com",
                "object_type": "User",
                "organization_unit": "test",
                "os": "unknown",
                "other_categories": [],
                "site": "Microsoft Office 365 Sharepoint Sites",
                "src_country": "PH",
                "src_geoip_src": 2,
                "src_latitude": 456,
                "src_location": "test",
                "src_longitude": 123,
                "src_region": "Province of Test",
                "src_zipcode": "1234",
                "srcip": "2.2.2.2",
                "timestamp": "2022-06-28T16:59:15.000Z",
                "traffic_type": "CloudApp",
                "type": "nspolicy",
                "ur_normalized": "test@test.com",
                "user": "test@test.com",
                "userip": "2.2.2.2",
                "userkey": "test@test.com"
            },
            {
                "_id": "efac69202c964c91fd59bcb9",
                "_insertion_epoch_timestamp": 1658331170,
                "audit_log_event": "Client Disable Request Submitted",
                "ccl": "unknown",
                "count": 1,
                "event_type": "audit",
                "organization_unit": "test",
                "severity_level": 1,
                "supporting_data": {
                    "data_type": "hostname",
                    "data_values": "HAMRGBCNX147"
                },
                "timestamp": "2022-07-20T15:27:50.000Z",
                "type": "admin_audit_logs",
                "ur_normalized": "test@test.com",
                "user": "test@test.com"
            },
            {
                "_correlation_id": "5f3e3987-115c-4fed-9c5e-f69e184069af",
                "_ef_received_at": 1657742097188,
                "_event_id": "bd3de3e3-378e-4e01-ba8d-a5d72565bde7",
                "_forwarded_by": "msg-relayer",
                "_gef_src_dp": "IN-AAA1",
                "_id": "e03cf756afc2a707666fcbc0",
                "_insertion_epoch_timestamp": 1657742104,
                "_raw_event_inserted_at": 1657742097698,
                "_service_identifier": "service-npa",
                "_tenant_id": "test-tenant",
                "access_method": "Client",
                "action": "allow",
                "app": "[CS SEG's]",
                "appcategory": "n/a",
                "category": "",
                "cci": 0,
                "ccl": "unknown",
                "client_bytes": 1593,
                "client_packets": 13,
                "count": 1,
                "device": "Windows",
                "dsthost": "8.8.8.8",
                "dstip": "",
                "dstport": 443,
                "end_time": "2022-07-13T19:53:02+00:00",
                "event_type": "network",
                "hostname": "L-101861180",
                "ip_protocol": "TCP",
                "netskope_pop": "IN-AAA1",
                "network_session_id": "12345678",
                "num_sessions": 1,
                "numbytes": 2387,
                "organization_unit": "test",
                "os": "Windows",
                "os_version": "10.0 (2009)",
                "policy": "Netskope Private Apps Allowed",
                "protocol": "Http",
                "protocol_port": "TCP:443",
                "publisher_cn": "abcd1234",
                "publisher_name": "test",
                "server_bytes": 794,
                "server_packets": 11,
                "session_duration": 23461,
                "site": "1.1.1.1",
                "srcip": "",
                "srcport": 447,
                "start_time": "2022-07-13T19:52:51+00:00",
                "timestamp": "2022-07-13T19:54:57.000Z",
                "total_packets": 24,
                "traffic_type": "PrivateApp",
                "tunnel_id": "1150",
                "tunnel_type": "NPA",
                "tunnel_up_time": 23461,
                "type": "network",
                "ur_normalized": "test@test.com",
                "user": "test@test.com",
                "userip": "",
                "userkey": "test@test.com"
            }
        ]
    }
}

Human Readable Output

Events List

Id Timestamp Type Access Method App Traffic Type
23a372c433381a6a11798123 2022-07-17T23:48:52.000Z nspolicy API Connector Microsoft Office 365 Sharepoint Online CloudApp
9f806593aa4385e4fc14865c 2022-06-28T16:59:15.000Z nspolicy API Connector Microsoft Office 365 Sharepoint Online CloudApp
efac69202c964c91fd59bcb9 2022-07-20T15:27:50.000Z admin_audit_logs      
e03cf756afc2a707666fcbc0 2022-07-13T19:54:57.000Z network Client [CS SEG’s] PrivateApp

Configuration parameters

  • url — Server URL (required)
  • credentials — (required)
  • max_fetch — Max events per fetch
  • eventFetchInterval — Events Fetch Interval
  • event_types_to_fetch — Event Types To Fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • netskope-get-events Deprecated

    Returns events extracted from SaaS traffic and or logs.

import json
import re
import time
from unittest.mock import MagicMock

import dateparser
import demistomock as demisto
import pytest
from NetskopeEventCollector import ALL_SUPPORTED_EVENT_TYPES, RATE_LIMIT_REMAINING, RATE_LIMIT_RESET, Client


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


MOCK_ENTRY = util_load_json("test_data/mock_events_entry.json")
EVENTS_RAW = util_load_json("test_data/events_raw.json")
EVENTS_PAGE_RAW = util_load_json("test_data/multiple_events_raw.json")
BASE_URL = "https://netskope.example.com"
FIRST_LAST_RUN = {
    "alert": {"operation": 1680182467},
    "application": {"operation": 1680182467},
    "audit": {"operation": 1680182467},
    "network": {"operation": 1680182467},
    "page": {"operation": 1680182467},
}


def test_test_module(mocker):
    """
    Given:
        - raw_response of an event (as it returns from the api)
    When:
        - Running the test_module command
    Then:
        - Verify that 'ok' is returned.
    """
    from NetskopeEventCollector import test_module

    client = Client(BASE_URL, "dummy_token", False, False, event_types_to_fetch=ALL_SUPPORTED_EVENT_TYPES)
    mocker.patch.object(client, "perform_data_export", return_value=EVENTS_RAW)
    results = test_module(client, last_run=FIRST_LAST_RUN, max_fetch=1)
    assert results == "ok"


def test_populate_prepare_events():
    """
    Given:
        - Event from the API of type audit
    When:
        - Running the command
    Then:
        - Make sure the _time, evnet_id, and source_log_event fields are populated properly.
    """
    from NetskopeEventCollector import prepare_events

    event = EVENTS_RAW.get("result")[0]
    prepare_events([event], event_type="audit")
    assert event.get("_time") == "2022-01-18T19:58:07.000Z"
    assert event.get("source_log_event") == "audit"
    assert event.get("event_id") == "f0e9b2cadd17402b59b3938b"


def test_get_all_events(requests_mock):
    """
    Given:
        - netskope-get-events call
    When:
        - Running the get_all_events command
    Then:
        - Make sure the number of events returns as expected
        - Make sure that the _time and event_id fields are populated as expected
        - Make sure the new_last_run is set.
    """

    def json_callback(request, _):
        endpoint = request.path.split("/")[-1]
        return EVENTS_PAGE_RAW[endpoint]

    from NetskopeEventCollector import get_all_events

    client = Client(
        BASE_URL, "netskope_token", validate_certificate=False, proxy=False, event_types_to_fetch=ALL_SUPPORTED_EVENT_TYPES
    )
    url_matcher = re.compile("https://netskope[.]example[.]com/events/dataexport/events")
    requests_mock.get(url_matcher, json=json_callback)
    events = []
    new_last_run = get_all_events(client, FIRST_LAST_RUN, all_event_types=events)
    assert len(events) == 26
    assert events[0].get("event_id") == "1"
    assert events[0].get("_time") == "2023-05-22T10:30:16.000Z"
    assert all(new_last_run[event_type]["operation"] == "next" for event_type in ALL_SUPPORTED_EVENT_TYPES)


def test_get_events_command(mocker):
    """
    Given:
        - netskope-get-events call
    When:
        - Running the get_events_command
    Then:
        - Make sure the number of events returns as expected
        - Make sure that human_readable returned as expected
        - Make sure the outputs are set correctly.
    """
    from NetskopeEventCollector import get_events_command

    client = Client(BASE_URL, "dummy_token", False, False, event_types_to_fetch=ALL_SUPPORTED_EVENT_TYPES)
    mocker.patch("NetskopeEventCollector.get_all_events", return_value={})
    mocker.patch.object(time, "sleep")
    results, events = get_events_command(client, args={}, last_run=FIRST_LAST_RUN, events=MOCK_ENTRY)
    assert "Events List" in results.readable_output
    assert len(events) == 9
    assert results.outputs_prefix == "Netskope.Event"
    assert results.outputs == MOCK_ENTRY


@pytest.mark.parametrize(
    "headers, endpoint, expected_sleep",
    [
        ({RATE_LIMIT_REMAINING: 1}, "test_endpoint", None),
        ({}, "test_endpoint", None),
        ({RATE_LIMIT_REMAINING: 0, RATE_LIMIT_RESET: 2}, "test_endpoint", 2),
        ({RATE_LIMIT_REMAINING: 0}, "test_endpoint", 1),
    ],
)
def test_honor_rate_limiting(mocker, headers, endpoint, expected_sleep):
    """
    Given:
        Case a: Netskope response headers with RATE_LIMIT_REMAINING = 1
        Case b: Netskope with response headers
        Case c: Netskope with response headers RATE_LIMIT_REMAINING = 1 and RATE_LIMIT_RESET = 2
        Case c: Netskope with response headers RATE_LIMIT_REMAINING = 0

    When:
        Checking if sleeping is required

    Then:
        Case a: validate that there is no sleeping
        Case b: validate that there is no sleeping
        Case c: validate that we sleep for 2 secs (which is the reset time)
        Case c: validate that we sleep for 1 sec (which is the default in case not rest time is given)
    """
    time_mock = mocker.patch.object(time, "sleep")
    from NetskopeEventCollector import honor_rate_limiting

    honor_rate_limiting(headers=headers, endpoint=endpoint)
    if expected_sleep:
        time_mock.assert_called_once_with(expected_sleep)
    else:
        time_mock.assert_not_called()


@pytest.mark.parametrize(
    "last_run_dict, expected_operation_value",
    [
        ({}, 1672567200),
        (
            {
                "application": {"operation": "next"},
                "alert": {"operation": "next"},
                "page": {"operation": "next"},
                "audit": {"operation": "next"},
                "network": {"operation": "next"},
                "incident": {"operation": "next"},
            },
            "next",
        ),
    ],
)
def test_setup_last_run(mocker, last_run_dict, expected_operation_value):
    """
    Given:
        Case a: previous empty last run
        Case a: previous last run with operation= 'next' for all event types

    When:
        Setting the last run values for the current run

    Then:
        Case a: make sure all event types in last run are saved with operation= 1672567200
        Case b: make sure all event types in last run are saved with operation= 'next'

    """
    from NetskopeEventCollector import setup_last_run

    first_fetch = dateparser.parse("2023-01-01T10:00:00Z")
    mocker.patch.object(dateparser, "parse", return_value=first_fetch)
    last_run = setup_last_run(last_run_dict, ALL_SUPPORTED_EVENT_TYPES)
    assert all(val.get("operation") == expected_operation_value for _, val in last_run.items())


@pytest.mark.parametrize(
    "event_types_to_fetch_param, expected_value",
    [
        ("Application", ["application"]),
        ("Alert, Page, Audit", ["alert", "page", "audit"]),
        (["Application", "Audit", "Network", "Incident"], ["application", "audit", "network", "incident"]),
        ("Incident", ["incident"]),
        (None, ALL_SUPPORTED_EVENT_TYPES),
    ],
)
def test_event_types_to_fetch_parameter_handling(event_types_to_fetch_param, expected_value):
    """
    Given:
        Case a: event_types_to_fetch parameter has a single value
        Case b: event_types_to_fetch parameter has multiple values
        Case c: event_types_to_fetch parameter is a pythonic list
        Case d: event_types_to_fetch parameter is None

    When:
        Handling the event_types_to_fetch parameter

    Then:
        - Make sure the parameter converts into a valid pythonic list
        - The values are lowercase
        - In the case event_types_to_fetch in None, default ALL_SUPPORTED_EVENT_TYPES is used as parameter

    """
    from NetskopeEventCollector import handle_event_types_to_fetch

    assert handle_event_types_to_fetch(event_types_to_fetch_param) == expected_value


@pytest.mark.parametrize(
    "num_fetched_events, max_fetch_events, new_next_run, expected_result",
    [
        (200, 250, {"key": "value"}, {"nextTrigger": "0", "key": "value"}),
        (1000, 5000, {"nextTrigger": "0"}, {}),
        (0, 0, {"key": "value"}, {"key": "value"}),
        (0, 0, {}, {}),
        (2500, 5000, {"nextTrigger": "0"}, {}),
        (2501, 5000, {"key": "value", "nextTrigger": "0"}, {"key": "value", "nextTrigger": "0"}),
    ],
)
def test_next_trigger_time(num_fetched_events, max_fetch_events, new_next_run, expected_result):
    """
    Given:
        - The number of fetched events and the max_fetch integration parameter.

    When:
        - Setting the new last_run

    Then:
        - Check that the last run is modified with the nextTrigger: '0',
            only if more than half of the max_fetch amount was fetched.
    """
    from NetskopeEventCollector import next_trigger_time

    next_trigger_time(num_fetched_events, max_fetch_events, new_next_run)
    assert new_next_run == expected_result


@pytest.mark.parametrize(
    "last_run, supported_event_types, expected_result",
    [
        (
            {
                "alert": {"operation": "next"},
                "audit": {"operation": "next"},
                "network": {"operation": "next"},
                "nextTrigger": "0",
                "page": {"operation": "next"},
            },
            ["alert"],
            {"nextTrigger": "0", "alert": {"operation": "next"}},
        ),
        ({}, ["alert"], {}),
        (
            {
                "alert": {"operation": "next"},
                "audit": {"operation": "next"},
                "network": {"operation": "next"},
            },
            ["audit", "network"],
            {"audit": {"operation": "next"}, "network": {"operation": "next"}},
        ),
    ],
)
def test_fix_last_run(last_run, supported_event_types, expected_result):
    """
    Given:
        - last run dict and supported event types.
    When:
        - preparing the last_run before execution.
    Then:
        - remove unsupported event types.
    """
    from NetskopeEventCollector import remove_unsupported_event_types

    remove_unsupported_event_types(last_run, supported_event_types)
    assert last_run == expected_result


def test_incident_endpoint(mocker):
    """
    Given:
        - Netskope client set to fetch incident events.
    When:
        - Fetching events.
    Then:
        - Assert that the Netskope end point is called with the proper url and paras.
    """
    from datetime import datetime

    from NetskopeEventCollector import handle_data_export_single_event_type

    mocker.patch.object(demisto, "callingContext", {"context": {"IntegrationInstance": "test_instance"}})
    mocker.patch("NetskopeEventCollector.is_execution_time_exceeded", return_value=False)
    mocker.patch("NetskopeEventCollector.print_event_statistics_logs")
    client = Client(BASE_URL, "dummy_token", False, False, event_types_to_fetch=["incident"])
    mock_response = MagicMock()
    mock_response.json.return_value = {"result": EVENTS_RAW["result"], "wait_time": 0}
    request_mock = mocker.patch.object(Client, "_http_request", return_value=mock_response)
    handle_data_export_single_event_type(
        client, "incident", "next", limit=50, execution_start_time=datetime.now(), all_event_types=[]
    )
    kwargs = request_mock.call_args.kwargs
    assert kwargs["url_suffix"] == "events/dataexport/events/incident"
    assert kwargs["params"] == {"index": "xsoar_collector_test_instance_incident", "operation": "next"}