NetskopeEventCollector Deprecated
Deprecated. Use Netskope Event Collector v2 instead.
Analytics & SIEM · Netskope
Details
| ID | NetskopeEventCollector |
|---|---|
| Provider | Netskope |
| Category | Analytics & SIEM |
| From Version | 6.8.0 |
| Docker Image | demisto/python3:3.12.11.4508456 |
| Supported Modules | Agentix 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 fetcheventFetchInterval— Events Fetch Intervalevent_types_to_fetch— Event Types To Fetchinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
netskope-get-eventsDeprecatedReturns events extracted from SaaS traffic and or logs.
from typing import Any import demistomock as demisto import urllib3 from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import from CommonServerUserPython import * # noqa # Disable insecure warnings urllib3.disable_warnings() # pylint: disable=no-member """ CONSTANTS """ ALL_SUPPORTED_EVENT_TYPES = ["application", "alert", "page", "audit", "network", "incident"] MAX_EVENTS_PAGE_SIZE = 10000 MAX_SKIP = 50000 EXECUTION_TIMEOUT_SECONDS = 190 # 3:30 minutes # Netskope response constants WAIT_TIME = "wait_time" # Wait time between queries RATE_LIMIT_REMAINING = "ratelimit-remaining" # Rate limit remaining RATE_LIMIT_RESET = "ratelimit-reset" # Rate limit RESET value is in seconds """ CLIENT CLASS """ class Client(BaseClient): """ Client for Netskope RESTful API. Args: base_url (str): The base URL of Netskope. token (str): The token to authenticate against Netskope API. validate_certificate (bool): Specifies whether to verify the SSL certificate or not. proxy (bool): Specifies if to use XSOAR proxy settings. """ def __init__(self, base_url: str, token: str, validate_certificate: bool, proxy: bool, event_types_to_fetch: list[str]): self.fetch_status: dict = {event_type: False for event_type in event_types_to_fetch} self.event_types_to_fetch: list[str] = event_types_to_fetch headers = {"Netskope-Api-Token": token} super().__init__(base_url, verify=validate_certificate, proxy=proxy, headers=headers) def perform_data_export(self, endpoint: str, _type: str, index_name: str, operation: str): url_suffix = f"events/dataexport/{endpoint}/{_type}" params = {"index": index_name, "operation": operation} response = self._http_request(method="GET", url_suffix=url_suffix, params=params, resp_type="response", retries=10) honor_rate_limiting(headers=response.headers, endpoint=url_suffix) return response.json() """ HELPER FUNCTIONS """ def honor_rate_limiting(headers, endpoint): """ Identify the response headers carrying the rate limiting value. If the rate limit remaining for this endpoint is 0 then wait for the rate limit reset time before sending the response to the client. """ try: if RATE_LIMIT_REMAINING in headers: remaining = headers.get(RATE_LIMIT_REMAINING) demisto.debug(f"Remaining rate limit is: {remaining}") if int(remaining) <= 0: demisto.debug(f"Rate limiting reached for the endpoint: {endpoint}") if to_sleep := headers.get(RATE_LIMIT_RESET): demisto.debug(f"Going to sleep for {to_sleep} seconds to avoid rate limit error") time.sleep(int(to_sleep)) else: # if the RESET value does not exist in the header then # sleep for default 1 second as the rate limit remaining is 0 demisto.debug("Did not find a rate limit reset value, going to sleep for 1 second to avoid rate limit error") time.sleep(1) except ValueError as ve: logging.error(f"Value error when honoring the rate limiting wait time {headers} {ve!s}") def populate_parsing_rule_fields(event: dict, event_type: str): """ Handles the source_log_event and _time fields. Sets the source_log_event to the given event type and _time to the time taken from the timestamp field Args: event (dict): the event to edit event_type (str): the event type tp set in the source_log_event field """ event["source_log_event"] = event_type try: event["_time"] = timestamp_to_datestring(event["timestamp"] * 1000, is_utc=True) except TypeError: # modeling rule will default on ingestion time if _time is missing pass def prepare_events(events: list, event_type: str) -> list: """ Iterates over a list of given events and add/modify special fields like event_id, _time and source_log_event. Args: events (list): list of events to modify. event_type (str): the type of events given in the list. Returns: list: the list of modified events """ for event in events: populate_parsing_rule_fields(event, event_type) event_id = event.get("_id") event["event_id"] = event_id return events def print_event_statistics_logs(events: list, event_type: str): """ Helper function for debugging purposes. This function is responsible to print statistics about pulled events, like the amount of pulled events and the first event and last event times. Args: events (list): list of events. event_type (str): the type of events given in the list. """ demisto.debug(f"__[{event_type}]__ - Total events fetched this round: {len(events)}") if events: event_times = ( f'__[{event_type}]__ - First event: {events[0].get("timestamp")} __[{event_type}]__ - Last event: ' f'{events[-1].get("timestamp")}' ) demisto.debug(event_times) def is_execution_time_exceeded(start_time: datetime) -> bool: """ Checks if the execution time so far exceeded the timeout limit. Args: start_time (datetime): the time when the execution started. Returns: bool: true, if execution passed timeout settings, false otherwise. """ end_time = datetime.utcnow() secs_from_beginning = (end_time - start_time).seconds demisto.debug(f"Execution length so far is {secs_from_beginning} secs") return secs_from_beginning > EXECUTION_TIMEOUT_SECONDS def remove_unsupported_event_types(last_run_dict: dict, event_types_to_fetch: list): keys_to_remove = [] for key in last_run_dict: if (key in ALL_SUPPORTED_EVENT_TYPES) and (key not in event_types_to_fetch): keys_to_remove.append(key) for key in keys_to_remove: last_run_dict.pop(key, None) def setup_last_run(last_run_dict: dict, event_types_to_fetch: list[str]) -> dict: """ Setting the last_tun object with the right operation to be used throughout the integration run. Args: last_run_dict (dict): The dictionary of the last run to be configured Returns: dict: the modified last run dictionary with the needed operation """ remove_unsupported_event_types(last_run_dict, event_types_to_fetch) first_fetch = int(arg_to_datetime("now").timestamp()) # type: ignore[union-attr] for event_type in event_types_to_fetch: if not last_run_dict.get(event_type, {}).get("operation"): last_run_dict[event_type] = {"operation": first_fetch} demisto.debug(f"Initialize last run to - {last_run_dict}") return last_run_dict def handle_data_export_single_event_type( client: Client, event_type: str, operation: str, limit: int, execution_start_time: datetime, all_event_types: list ) -> bool: """ Pulls events per each given event type. Each event type receives a dedicated index name that is constructed using the event type and the integration instance name. The function keeps pulling events as long as the limit was not exceeded. - First thing it validates is that execution time of the entire run was not exceeded. - Then it checks if we need to wait some time before making another call to the same endpoint by checking the wait_time value received in the previous response. - The operation variable marks the next operation to perform on this endpoint (besides the first fetch it is always 'next') - After it is done pulling, it marks this event type as successfully done in the 'fetch_status' dictionary. Args: client (Client): The Netskope client. event_type (str): The type of event to pull. operation (str): The operation to perform. Can be 'next' or a timestamp string. limit (int): The limit which after we stop pulling. execution_start_time (datetime): The time when we started running the fetch mechanism. Return: list: The list of events pulled for the given event type. bool: Was execution timeout reached. """ wait_time: int = 0 events: list[dict] = [] # We use the instance name to allow multiple instances in parallel without causing a collision in index names instance_name = demisto.callingContext.get("context", {}).get("IntegrationInstance") index_name = f"xsoar_collector_{instance_name}_{event_type}" while len(events) < limit: # If the execution exceeded the timeout we will break if is_execution_time_exceeded(start_time=execution_start_time): return True # Wait time between queries if wait_time: demisto.debug(f"Going to sleep between queries, wait_time is {wait_time} seconds") time.sleep(wait_time) # pylint: disable=E9003 else: demisto.debug("No wait time received, going to sleep for 1 second") time.sleep(1) response = client.perform_data_export("events", event_type, index_name, operation) results = response.get("result", []) demisto.debug(f"The number of received events - {len(results)}") operation = "next" # The API responds with the time we should wait between requests, the server needs this time to prepare the next response. # It will be used to sleep in the beginning of the next iteration wait_time = arg_to_number(response.get(WAIT_TIME, 5)) or 5 demisto.debug(f"Wait time is {wait_time} seconds") events.extend(results) all_event_types.extend(prepare_events(results, event_type)) if not results or len(results) < MAX_EVENTS_PAGE_SIZE: break print_event_statistics_logs(events=events, event_type=event_type) # We mark this event type as successfully fetched client.fetch_status[event_type] = True return False def get_all_events(client: Client, last_run: dict, all_event_types: list, limit: int = MAX_EVENTS_PAGE_SIZE) -> dict: """ Iterates over all supported event types and call the handle data export logic. Once each event type is done the operation for next run is set to 'next'. Args: client (Client): The Netskope client. last_run (dict): The execution last run dict where the relevant operations are stored. limit (int): The limit which after we stop pulling. Returns: list: The accumulated list of all events. dict: The updated last_run object. """ execution_start_time = datetime.utcnow() for event_type in client.event_types_to_fetch: event_type_operation = last_run.get(event_type, {}).get("operation") time_out = handle_data_export_single_event_type( client=client, event_type=event_type, operation=event_type_operation, limit=limit, execution_start_time=execution_start_time, all_event_types=all_event_types, ) last_run[event_type] = {"operation": "next"} if time_out: demisto.info("Timeout reached, stopped pulling events") break return last_run """ COMMAND FUNCTIONS """ def test_module(client: Client, last_run: dict, max_fetch: int) -> str: get_all_events(client, last_run, limit=max_fetch, all_event_types=[]) return "ok" def get_events_command(client: Client, args: dict[str, Any], last_run: dict, events: list) -> tuple[CommandResults, list]: limit = arg_to_number(args.get("limit")) or 10 _ = get_all_events(client=client, last_run=last_run, limit=limit, all_event_types=events) for event in events: event["timestamp"] = timestamp_to_datestring(event["timestamp"] * 1000) readable_output = tableToMarkdown( "Events List:", events, removeNull=True, headers=["_id", "timestamp", "type", "access_method", "app", "traffic_type"], headerTransform=string_to_table_header, ) results = CommandResults( outputs_prefix="Netskope.Event", outputs_key_field="_id", outputs=events, readable_output=readable_output, raw_response=events, ) return results, events def handle_event_types_to_fetch(event_types_to_fetch) -> list[str]: """Handle event_types_to_fetch parameter. Transform the event_types_to_fetch parameter into a pythonic list with lowercase values. """ return argToList( arg=event_types_to_fetch if event_types_to_fetch else ALL_SUPPORTED_EVENT_TYPES, transform=lambda x: x.lower(), ) def next_trigger_time(num_of_events, max_fetch, new_last_run): """Check wether to add the next trigger key to the next_run dict based on number of fetched events. Args: num_of_events (int): The number of events fetched. max_fetch (int): The maximum fetch limit. new_last_run (dict): the next_run to update """ if num_of_events > (max_fetch / 2): new_last_run["nextTrigger"] = "0" else: new_last_run.pop("nextTrigger", None) """ MAIN FUNCTION """ def main() -> None: # pragma: no cover try: params = demisto.params() url = params.get("url") token = params.get("credentials", {}).get("password") base_url = urljoin(url, "/api/v2/") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) max_fetch: int = arg_to_number(params.get("max_fetch")) or 10000 vendor, product = params.get("vendor", "netskope"), params.get("product", "netskope") event_types_to_fetch = handle_event_types_to_fetch(params.get("event_types_to_fetch")) demisto.debug(f"Event types that will be fetched in this instance: {event_types_to_fetch}") command_name = demisto.command() demisto.debug(f"Command being called is {command_name}") client = Client(base_url, token, verify_certificate, proxy, event_types_to_fetch) last_run = setup_last_run(demisto.getLastRun(), event_types_to_fetch) demisto.debug(f"Running with the following last_run - {last_run}") all_event_types: list[dict] = [] new_last_run: dict = {} if command_name == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client, last_run, max_fetch=MAX_EVENTS_PAGE_SIZE) # type: ignore[arg-type] return_results(result) elif command_name == "netskope-get-events": results, events = get_events_command(client, demisto.args(), last_run, events=[]) if argToBoolean(demisto.args().get("should_push_events", "true")): send_events_to_xsiam(events=events, vendor=vendor, product=product, chunk_size=XSIAM_EVENT_CHUNK_SIZE_LIMIT) # type: ignore return_results(results) elif command_name == "fetch-events": # We have this try-finally block for fetch events where wrapping up should be done if errors occur start = datetime.utcnow() try: demisto.debug(f"Sending request with last run {last_run}") new_last_run = get_all_events(client=client, last_run=last_run, limit=max_fetch, all_event_types=all_event_types) finally: demisto.debug(f"sending {len(all_event_types)} to xsiam") send_events_to_xsiam( events=all_event_types, vendor=vendor, product=product, chunk_size=XSIAM_EVENT_CHUNK_SIZE_LIMIT ) for ( event_type, status, ) in client.fetch_status.items(): if not status: new_last_run[event_type] = {"operation": "resend"} end = datetime.utcnow() demisto.debug(f"Handled {len(all_event_types)} total events in {(end - start).seconds} seconds") next_trigger_time(len(all_event_types), max_fetch, new_last_run) demisto.debug(f"Setting the last_run to: {new_last_run}") demisto.setLastRun(new_last_run) # Log exceptions and return errors except Exception as e: last_run = new_last_run if new_last_run else demisto.getLastRun() last_run.pop("nextTrigger", None) demisto.setLastRun(last_run) demisto.debug(f"last run after removing nextTrigger {last_run}") return_error(f"Failed to execute {command_name} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()