Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Netskope Event Collector] Add support for event type selection #32300

Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import demistomock as demisto
from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import
from CommonServerUserPython import * # noqa
from typing import Any

import urllib3
from typing import Dict, Any, Tuple
from CommonServerUserPython import * # noqa

import demistomock as demisto
from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import

# Disable insecure warnings
urllib3.disable_warnings() # pylint: disable=no-member
Expand Down Expand Up @@ -35,8 +36,9 @@ class Client(BaseClient):
proxy (bool): Specifies if to use XSOAR proxy settings.
"""

def __init__(self, base_url: str, token: str, validate_certificate: bool, proxy: bool):
self.fetch_status: dict = {event_type: False for event_type in ALL_SUPPORTED_EVENT_TYPES}
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)
Expand Down Expand Up @@ -77,7 +79,7 @@ def honor_rate_limiting(headers, endpoint):
time.sleep(1)

except ValueError as ve:
logging.error("Value error when honoring the rate limiting wait time {} {}".format(headers, str(ve)))
logging.error(f"Value error when honoring the rate limiting wait time {headers} {str(ve)}")


def populate_parsing_rule_fields(event: dict, event_type: str):
Expand Down Expand Up @@ -150,7 +152,7 @@ def is_execution_time_exceeded(start_time: datetime) -> bool:
return secs_from_beginning > EXECUTION_TIMEOUT_SECONDS


def setup_last_run(last_run_dict: dict) -> dict:
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.

Expand All @@ -161,7 +163,7 @@ def setup_last_run(last_run_dict: dict) -> dict:
dict: the modified last run dictionary with the needed operation
"""
first_fetch = int(arg_to_datetime('now').timestamp()) # type: ignore[union-attr]
for event_type in ALL_SUPPORTED_EVENT_TYPES:
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}

Expand Down Expand Up @@ -234,7 +236,7 @@ def handle_data_export_single_event_type(client: Client, event_type: str, operat
return events, False


def get_all_events(client: Client, last_run: dict, limit: int = MAX_EVENTS_PAGE_SIZE) -> Tuple[list, dict]:
def get_all_events(client: Client, last_run: dict, limit: int = MAX_EVENTS_PAGE_SIZE) -> tuple[list, 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'.
Expand All @@ -251,7 +253,7 @@ def get_all_events(client: Client, last_run: dict, limit: int = MAX_EVENTS_PAGE_

all_types_events_result = []
execution_start_time = datetime.utcnow()
for event_type in ALL_SUPPORTED_EVENT_TYPES:
for event_type in client.event_types_to_fetch:
event_type_operation = last_run.get(event_type, {}).get('operation')

events, time_out = handle_data_export_single_event_type(client=client, event_type=event_type,
Expand All @@ -271,11 +273,11 @@ def get_all_events(client: Client, last_run: dict, limit: int = MAX_EVENTS_PAGE_


def test_module(client: Client, last_run: dict, max_fetch: int) -> str:
get_all_events(client, last_run, limit=max_fetch)
get_all_events(client, last_run, limit=max_fetch, )
return 'ok'


def get_events_command(client: Client, args: Dict[str, Any], last_run: dict) -> Tuple[CommandResults, list]:
def get_events_command(client: Client, args: dict[str, Any], last_run: dict) -> tuple[CommandResults, list]:
limit = arg_to_number(args.get('limit')) or MAX_EVENTS_PAGE_SIZE
events, _ = get_all_events(client=client, last_run=last_run, limit=limit)

Expand All @@ -296,6 +298,19 @@ def get_events_command(client: Client, args: Dict[str, Any], last_run: dict) ->
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.
"""
def format_event_type_name(event_name: str) -> str:
return event_name.lower()

return argToList(
arg=event_types_to_fetch,
transform=format_event_type_name,
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 While I love functions, we can make this a one liner-

Suggested change
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.
"""
def format_event_type_name(event_name: str) -> str:
return event_name.lower()
return argToList(
arg=event_types_to_fetch,
transform=format_event_type_name,
)
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,
transform=lambda x: x.lower(),
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.


''' MAIN FUNCTION '''


Expand All @@ -310,11 +325,13 @@ def main() -> None: # pragma: no cover
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', ALL_SUPPORTED_EVENT_TYPES))
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)
last_run = setup_last_run(demisto.getLastRun())
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}')

events: list[dict] = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ configuration:
section: Collect
type: 0
required: false
- display: Event Types To Fetch
section: Collect
name: event_types_to_fetch
type: 16
required: false
additionalinfo: 'Event types to be fetched by this instance. Default is: Application, Alert, Page, Audit, Network.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
additionalinfo: 'Event types to be fetched by this instance. Default is: Application, Alert, Page, Audit, Network.'
additionalinfo: 'Event types to fetch. Default is all available types: Application, Alert, Page, Audit, Network.'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

defaultvalue: Application,Alert,Page,Audit,Network
options:
- Application
- Alert
- Page
- Audit
- Network
- display: Trust any certificate (not secure)
section: Connect
name: insecure
Expand Down Expand Up @@ -53,7 +66,7 @@ script:
defaultValue: 10000
description: Returns events extracted from SaaS traffic and or logs.
name: netskope-get-events
dockerimage: demisto/python3:3.10.13.84405
dockerimage: demisto/python3:3.10.13.85667
runonce: false
script: '-'
subtype: python3
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import io
import json
import re
import time

import dateparser
import pytest

from NetskopeEventCollector import Client, ALL_SUPPORTED_EVENT_TYPES, RATE_LIMIT_REMAINING, RATE_LIMIT_RESET
from NetskopeEventCollector import ALL_SUPPORTED_EVENT_TYPES, RATE_LIMIT_REMAINING, RATE_LIMIT_RESET, Client


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


Expand All @@ -32,7 +30,7 @@ def test_test_module(mocker):
- Verify that 'ok' is returned.
"""
from NetskopeEventCollector import test_module
client = Client(BASE_URL, 'dummy_token', False, False)
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'
Expand Down Expand Up @@ -72,14 +70,15 @@ def json_callback(request, _):
return EVENTS_PAGE_RAW[endpoint]

from NetskopeEventCollector import get_all_events
client = Client(BASE_URL, 'netskope_token', validate_certificate=False, proxy=False)
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)
assert len(events) == 25
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])
assert all(new_last_run[event_type]['operation'] == 'next' for event_type in ALL_SUPPORTED_EVENT_TYPES)


def test_get_events_command(mocker):
Expand All @@ -94,7 +93,7 @@ def test_get_events_command(mocker):
- Make sure the outputs are set correctly.
"""
from NetskopeEventCollector import get_events_command
client = Client(BASE_URL, 'dummy_token', False, False)
client = Client(BASE_URL, 'dummy_token', False, False, event_types_to_fetch=ALL_SUPPORTED_EVENT_TYPES)
mocker.patch('NetskopeEventCollector.get_all_events', return_value=[MOCK_ENTRY, {}])
mocker.patch.object(time, "sleep")
results, events = get_events_command(client, args={}, last_run=FIRST_LAST_RUN)
Expand Down Expand Up @@ -161,5 +160,32 @@ def test_setup_last_run(mocker, last_run_dict, expected_operation_value):
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)
assert all([val.get('operation') == expected_operation_value for key, val in last_run.items()])
last_run = setup_last_run(last_run_dict, ALL_SUPPORTED_EVENT_TYPES)
assert all(val.get('operation') == expected_operation_value for key, val in last_run.items())


@pytest.mark.parametrize('event_types_to_fetch_param, expected_value', [
('Application', ['application']),
('Alert, Page, Audit', ['alert', 'page', 'audit']),
JasBeilin marked this conversation as resolved.
Show resolved Hide resolved
(['Application', 'Audit', 'Network'], ['application', 'audit', 'network']),
(ALL_SUPPORTED_EVENT_TYPES, 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

JasBeilin marked this conversation as resolved.
Show resolved Hide resolved
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
7 changes: 7 additions & 0 deletions Packs/Netskope/ReleaseNotes/3_3_5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

#### Integrations

##### Netskope Event Collector

- Added the *Event Types To Fetch* parameter, allowing to select which event types are being fetched for each instance.
- Updated the Docker image to: *demisto/python3:3.10.13.85667*.
2 changes: 1 addition & 1 deletion Packs/Netskope/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Netskope",
"description": "Cloud access security broker that enables to find, understand, and secure cloud apps.",
"support": "xsoar",
"currentVersion": "3.3.4",
"currentVersion": "3.3.5",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
Expand Down