SentinelOneEventCollector
This integration fetches activities, threats, and alerts from SentinelOne.
Analytics & SIEM · SentinelOne
Details
| ID | SentinelOneEventCollector |
|---|---|
| Provider | SentinelOne |
| Category | Analytics & SIEM |
| From Version | 6.8.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
This integration fetches activities, threats, and alerts from SentinelOne.
This integration was integrated and tested with API version 2.1 of SentinelOne.
This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.
Configure SentinelOne Activity and Alerts in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g., https://usea1.sentinelone.net) | The URL to use for connection | True |
| API Token | The API Token to use for connection | True |
| Trust any certificate (not secure) | Use SSL secure connection or not. | False |
| Use system proxy settings | Use proxy settings for connection or not. | False |
| Fetch events | Whether to bring events or not. | False |
| Event types | What types of events to bring, Possible values are (activities, threats, alerts) | False |
| First fetch time | First fetch query <number> <time unit>, e.g., 7 days. Default 3 days) |
False |
| The maximum number of events per fetch should be between 1-1000 | The limit is per event type. For example, if you choose 3 event types (ACTIVITIES, THREATS, ALERTS) with a limit of 100, the actually limit will be 300. | 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.
sentinelone-get-events
Gets events from SentinelOne.
Base Command
sentinelone-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| should_push_events | If true, the command will create events, otherwise it will only display them. Possible values are: true, false. Default is false. | Optional |
| limit | Maximum number of results to return. Value should be between 1 - 1000. Default is 1000. | Optional |
Context Output
There is no context output for this command.
Configuration parameters
url— Server URL (e.g., https://usea1.sentinelone.net) (required)credentials— (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch eventsevent_type— Event typesfirst_fetch— First fetch timefetch_limit— The maximum number of events per fetch should be between 1-1000
Commands (1)
-
sentinelone-get-eventsGets events from SentinelOne.
from CommonServerPython import * import demistomock as demisto from freezegun import freeze_time import requests_mock """ CONSTANTS """ ACTIVITIES_MOCK_URL = ( "https://test.com/web/api/v2.1/activities?createdAt__gt=2022-01-04+00%3A00%3A00&limit=1000&sortBy=createdAt&sortOrder=asc" # noqa: E501 ) ACTIVITIES_SECOND_MOCK_URL = "https://test.com/web/api/v2.1/activities?createdAt__gt=2022-09-06T20%3A37%3A55.912951Z&limit=1000&sortBy=createdAt&sortOrder=asc" # noqa: E501 THREATS_MOCK_URL = ( "https://test.com/web/api/v2.1/threats?createdAt__gt=2022-01-04+00%3A00%3A00&limit=1000&sortBy=createdAt&sortOrder=asc" # noqa: E501 ) THREATS_SECOND_MOCK_URL = "https://test.com/web/api/v2.1/threats?createdAt__gt=2022-12-20T15%3A51%3A17.514437Z&limit=1000&sortBy=createdAt&sortOrder=asc" # noqa: E501 ALERTS_MOCK_URL = "https://test.com/web/api/v2.1/cloud-detection/alerts?limit=1000&createdAt__gt=2022-01-04+00%3A00%3A00&sortBy=alertInfoCreatedAt&sortOrder=asc" # noqa: E501 ALERTS_SECOND_MOCK_URL = "https://test.com/web/api/v2.1/cloud-detection/alerts?limit=1000&createdAt__gt=2022-12-20T13%3A54%3A43.027000Z&sortBy=alertInfoCreatedAt&sortOrder=asc" # noqa: E501 """ HELPER FUNCTIONS """ def util_load_json(path): with open(path, encoding="utf-8") as f: return json.loads(f.read()) def mock_send_events_to_xsiam(events, vendor, product): return events, vendor, product """ TEST FUNCTIONS """ @freeze_time("2022-01-07 00:00:00Z") def test_test_module(): from SentinelOneEventCollector import Client, test_module client = Client("https://test.com/web/api/v2.1") with requests_mock.Mocker() as m: m.get(ACTIVITIES_MOCK_URL, json={}) m.get(THREATS_MOCK_URL, json={}) m.get(ALERTS_MOCK_URL, json={}) result = test_module(client, ["activities", "threats", "alerts"]) assert result == "ok" @freeze_time("2022-01-07 00:00:00Z") def test_get_events_command(): """ Tests get-events command function. Given: - Few events in SentinelOne. When: - Running the 'get_events_command'. Then: - Ensures the outputs of the command function match the expected outputs. """ from SentinelOneEventCollector import Client, get_events_command client = Client(base_url="https://test.com/web/api/v2.1") with requests_mock.Mocker() as m: m.get(ACTIVITIES_MOCK_URL, json=util_load_json("test_data/activities.json")) m.get(THREATS_MOCK_URL, json=util_load_json("test_data/threats.json")) m.get(ALERTS_MOCK_URL, json=util_load_json("test_data/alerts.json")) events, _ = get_events_command(client, str(arg_to_datetime("3 days")), ["activities", "threats", "alerts"]) assert len(events) == 6 @freeze_time("2022-01-07 00:00:00Z") def test_fetch_events(): """ Tests fetch-events command function. Given: - Few events in SentinelOne. - fetch-events is limited to 2 events per fetch and to all 3 event types. (which mean actually 6 events per fetch since we are fetching from 3 endpoints). When: - Running the 'fetch-events' command. Then: - Ensures the outputs of the command function match the expected outputs. """ from SentinelOneEventCollector import Client, fetch_events, first_run client = Client(base_url="https://test.com/web/api/v2.1") last_run = first_run(arg_to_datetime("3 days")) with requests_mock.Mocker() as m: m.get(ACTIVITIES_MOCK_URL, json=util_load_json("test_data/activities.json")) m.get(THREATS_MOCK_URL, json=util_load_json("test_data/threats.json")) m.get(ALERTS_MOCK_URL, json=util_load_json("test_data/alerts.json")) m.get(ACTIVITIES_SECOND_MOCK_URL, json=util_load_json("test_data/activities_second_fetch.json")) m.get(THREATS_SECOND_MOCK_URL, json={}) m.get(ALERTS_SECOND_MOCK_URL, json={}) next_run, events = fetch_events(client, last_run, ["activities", "threats", "alerts"]) assert next_run.get("last_activity_created") == "2022-09-06T20:37:55.912951Z" assert next_run.get("last_alert_created") == "2022-12-20T13:54:43.027000Z" assert len(events) == 6 next_run, events = fetch_events(client, last_run, ["activities", "threats", "alerts"]) assert next_run.get("last_activity_created") == "2022-09-06T20:39:15.445218Z" assert next_run.get("last_alert_created") == "2022-12-20T13:54:43.027000Z" assert len(events) == 2 @freeze_time("2022-01-07 00:00:00Z") def test_main(mocker): """ Tests the main function. (the whole flow). Given: - Few events in SentinelOne. When: - Running the 'main' function. Then: - Ensures the outputs of the main function match the expected outputs. """ from SentinelOneEventCollector import main, VENDOR, PRODUCT mocker.patch.object(demisto, "command", return_value="sentinelone-get-events") mocker.patch.object(demisto, "params", return_value={"url": "https://test.com", "fetch_limit": 2}) mocker.patch.object(demisto, "args", return_value={"should_push_events": True}) events = mocker.patch("SentinelOneEventCollector.send_events_to_xsiam", side_effect=mock_send_events_to_xsiam) with requests_mock.Mocker() as m: m.get(ACTIVITIES_MOCK_URL.replace("limit=1000", "limit=2"), json=util_load_json("test_data/activities.json")) m.get(THREATS_MOCK_URL.replace("limit=1000", "limit=2"), json=util_load_json("test_data/threats.json")) m.get(ALERTS_MOCK_URL.replace("limit=1000", "limit=2"), json=util_load_json("test_data/alerts.json")) main() assert len(events.call_args[0][0]) == 6 assert events.call_args[0][0][0].get("_time") == events.call_args[0][0][0].get("updatedAt") assert events.call_args[1].get("vendor") == VENDOR assert events.call_args[1].get("product") == PRODUCT def test_add_keys_to_events_with_external_url(mocker): """ Tests add_keys_to_events function with _EXTERNAL_URL field construction. Given: - Events from SentinelOne (threats, alerts, and activities). - An instance URL for the SentinelOne instance. When: - Calling the 'add_keys_to_events' function with instance_url parameter. Then: - Ensure _EXTERNAL_URL field is correctly constructed for each event type: - Threats: {instance_url}/incidents/threats/{threat_id}/overview - Alerts: {instance_url}/incidents/alerts/{alert_id}/overview - Activities: {instance_url}/activity """ from SentinelOneEventCollector import add_keys_to_events mocker.patch.object(demisto, "debug") instance_url = "https://example-instance.com" # Test data threat_event = {"threatInfo": {"threatId": "123456", "updatedAt": "2022-12-20T15:51:17.514437Z"}} alert_event = {"alertInfo": {"alertId": "789012", "updatedAt": "2022-12-20T13:54:43.027000Z"}} activity_event = {"updatedAt": "2022-09-06T20:37:55.912951Z"} events = [threat_event, alert_event, activity_event] add_keys_to_events(events, instance_url) # Assertions for Threat assert threat_event.get("eventType") == "Threat" assert threat_event.get("_EXTERNAL_URL") == f"{instance_url}/incidents/threats/123456/overview" assert threat_event.get("_time") == "2022-12-20T15:51:17.514437Z" # Assertions for Alert assert alert_event.get("eventType") == "Alert" assert alert_event.get("_EXTERNAL_URL") == f"{instance_url}/incidents/alerts/789012/overview" assert alert_event.get("_time") == "2022-12-20T13:54:43.027000Z" # Assertions for Activity assert activity_event.get("eventType") == "Activity" assert activity_event.get("_EXTERNAL_URL") == f"{instance_url}/activity" assert activity_event.get("_time") == "2022-09-06T20:37:55.912951Z" def test_add_keys_to_events_missing_ids(mocker): """ Tests add_keys_to_events function when alert/threat IDs are missing. Given: - Events from SentinelOne with missing alertId or threatId. - An instance URL for the SentinelOne instance. When: - Calling the 'add_keys_to_events' function. Then: - Ensure _EXTERNAL_URL field is NOT added when IDs are missing. - Ensure debug messages are logged for missing IDs. - Ensure other fields (_time, eventType) are still added correctly. """ from SentinelOneEventCollector import add_keys_to_events instance_url = "https://example-instance.com" mock_debug = mocker.patch.object(demisto, "debug") # Test data - threat without threatId threat_event = {"threatInfo": {"updatedAt": "2022-12-20T15:51:17.514437Z"}} # Test data - alert without alertId alert_event = {"alertInfo": {"updatedAt": "2022-12-20T13:54:43.027000Z"}} events = [threat_event, alert_event] add_keys_to_events(events, instance_url) # Assertions for Threat assert threat_event.get("eventType") == "Threat" assert threat_event.get("_time") == "2022-12-20T15:51:17.514437Z" assert "_EXTERNAL_URL" not in threat_event # Assertions for Alert assert alert_event.get("eventType") == "Alert" assert alert_event.get("_time") == "2022-12-20T13:54:43.027000Z" assert "_EXTERNAL_URL" not in alert_event # Verify debug messages were called (1 for the count log + 2 for missing IDs) assert mock_debug.call_count == 3