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 urllib3 from typing import Any # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" VENDOR = "sentinelone" PRODUCT = "xdr" """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API This Client implements API calls, and does not contain any Demisto logic. Should only do requests and return data. It inherits from BaseClient defined in CommonServer Python. Most calls use _http_request() that handles proxy, SSL verification, etc. For this SentinelOne implementation, no special attributes defined """ def __init__(self, base_url, verify: bool = True, headers: dict = None, proxy: bool = False, fetch_limit: int | None = 1000): super().__init__(base_url=base_url, verify=verify, headers=headers, proxy=proxy) self.limit = fetch_limit def get_activities(self, from_time: str) -> list[dict[str, Any]]: """ Returns SentinelOne activities using the '/activities' API endpoint. All the parameters are passed directly to the API as HTTP GET parameters in the request Args: from_time: Time (the incident was created) to start fetching. Returns: list: The activities. """ params = { "createdAt__gt": from_time, "limit": self.limit, "sortBy": "createdAt", "sortOrder": "asc", } result = self._http_request("GET", url_suffix="/activities", params=params) return result.get("data", []) def get_threats(self, from_time: str) -> list[dict[str, Any]]: """ Returns SentinelOne threats using the '/threats' API endpoint. All the parameters are passed directly to the API as HTTP GET parameters in the request Args: from_time: Time (the incident was created) to start fetching. Returns: list: The threats. """ params = { "createdAt__gt": from_time, "limit": self.limit, "sortBy": "createdAt", "sortOrder": "asc", } result = self._http_request("GET", url_suffix="/threats", params=params) return result.get("data", []) def get_alerts(self, from_time: str) -> list[dict[str, Any]]: """ Returns SentinelOne alerts using the '/cloud-detection/alerts' API endpoint. All the parameters are passed directly to the API as HTTP GET parameters in the request Args: from_time: Time (the incident was created) to start fetching. Returns: list: The alerts. """ params = { "limit": self.limit, "createdAt__gt": from_time, "sortBy": "alertInfoCreatedAt", "sortOrder": "asc", } result = self._http_request("GET", url_suffix="/cloud-detection/alerts", params=params) return result.get("data", []) """ HELPER FUNCTIONS """ def get_events(client: Client, event_type: list[str], from_time: str = str(arg_to_datetime("3 days"))) -> list[dict[str, Any]]: """ Returns SentinelOne events (activities, threats, alerts). Args: client (Client): SentinelOne client to make the API requests. event_type (list): A list of evnet types to retrieve possible value are: [activities, threats, alerts]. from_time (str): The time the first event we retrieve was created. Returns: list: The events. """ events = [] if "activities" in event_type: events.extend(client.get_activities(from_time)) if "threats" in event_type: events.extend(client.get_threats(from_time)) if "alerts" in event_type: events.extend(client.get_alerts(from_time)) return events def first_run(from_time: str = str(arg_to_datetime("3 days"))) -> dict[str, str]: """ Returns a dictionary for each event type from which time to retrieve the events. Args: from_time (str): From what time to retrieve events. Returns: dict: The time when the first event we retrieve was created for each event type. """ return { "last_activity_created": from_time, "last_threat_created": from_time, "last_alert_created": from_time, } def add_keys_to_events(events: list[dict[str, Any]] | None, instance_url: str): """ Adds the _time, eventType, and _EXTERNAL_URL keys to the events. Args: events (list): The events to add the fields key to. instance_url (str): The base URL of the SentinelOne instance (e.g., 'https://your-instance.sentinelone.net'). """ demisto.debug(f"Adding keys to {len(events) if events else 0} events") instance_url = instance_url.rstrip("/") for event in events or []: if alert_info := event.get("alertInfo"): event["_time"] = alert_info.get("updatedAt") event["eventType"] = "Alert" if alert_id := alert_info.get("alertId"): event["_EXTERNAL_URL"] = f"{instance_url}/incidents/alerts/{alert_id}/overview" else: demisto.debug(f"Alert event is missing alertId: {alert_info}") elif threat_info := event.get("threatInfo"): event["_time"] = threat_info.get("updatedAt") event["eventType"] = "Threat" if threat_id := threat_info.get("threatId"): event["_EXTERNAL_URL"] = f"{instance_url}/incidents/threats/{threat_id}/overview" else: demisto.debug(f"Threat event is missing threatId: {threat_info}") else: # Otherwise, it's an activity. event["_time"] = event.get("updatedAt") event["eventType"] = "Activity" event["_EXTERNAL_URL"] = f"{instance_url}/activity" """ COMMAND FUNCTIONS """ def test_module(client: Client, event_type: list[str]) -> str: """ Tests API connectivity and authentication' When 'ok' is returned it indicates the integration works like it is supposed to and connection to the service is successful. Raises exceptions if something goes wrong. Args: client (Client): SentinelOne client to use. event_type (list): A list of evnet types to retrieve possible value are: [activities, threats, alerts]. Returns: str: 'ok' if test passed, Anything else will raise an exception and will fail the test. """ try: get_events(client, event_type=event_type) except Exception as e: if "UNAUTHORIZED" in str(e): return "Authorization Error: make sure API Key is correctly set" else: raise e return "ok" def get_events_command( client: Client, first_fetch_time: str, event_type: list[str] ) -> tuple[list[dict[str, Any]], CommandResults]: """ Returns SentinelOne events (activities, threats, alerts). Args: client (Client): SentinelOne client to make the API requests. first_fetch_time (str): The time the first event we retrieve was created. event_type (list): A list of evnet types to retrieve possible value are: [activities, threats, alerts]. Returns: list: The events returned from SentinelOne. CommandResults: The CommandResults. """ events = get_events(client, from_time=first_fetch_time, event_type=event_type) hr = tableToMarkdown(name="Test Event", t=events) return events, CommandResults(readable_output=hr) def fetch_events( client: Client, last_run: dict[str, str], event_type: list | None ) -> tuple[dict[str, str], list[dict[str, Any]]]: """ Args: client (Client): SentinelOne client to use. last_run (dict): A dict containing the latest event (for each event type) created time we got from last fetch. For example: {'last_activity_created': '2023-01-01T00:00:00', 'last_threat_created': '2023-01-01T00:00:00'}. event_type (list): Event type to be fetched possible value are: [activities, threats, alerts]. Returns: dict: Next run dictionary containing the timestamp that will be used in ``last_run`` on the next fetch. list: list of events that will be created in XSIAM. """ if not event_type: event_type = ["activities", "threats", "alerts"] demisto.debug(f"Fetched event of type: {event_type} from time {last_run}.") events = [] if "activities" in event_type and (activities := client.get_activities(last_run["last_activity_created"])): events.extend(activities) last_run["last_activity_created"] = str(activities[-1].get("createdAt", "")) if "threats" in event_type and (threats := client.get_threats(last_run["last_threat_created"])): events.extend(threats) last_run["last_threat_created"] = str(threats[-1].get("threatInfo", {}).get("createdAt", "")) if "alerts" in event_type and (alerts := client.get_alerts(last_run["last_alert_created"])): events.extend(alerts) last_run["last_alert_created"] = str(alerts[-1].get("alertInfo", {}).get("createdAt", "")) demisto.info(f"Setting next run {last_run}.") return last_run, events """ MAIN FUNCTION """ def main() -> None: """ main function, parses params and runs command functions """ params = demisto.params() args = demisto.args() command = demisto.command() api_key = params.get("credentials", {}).get("password") instance_url = params.get("url") or "" base_url = urljoin(instance_url, "web/api/v2.1") verify_certificate = not params.get("insecure", False) # How much time before the first fetch to retrieve events first_fetch_time = str(arg_to_datetime(arg=params.get("first_fetch", "3 days"), arg_name="First fetch time", required=True)) fetch_limit = arg_to_number(args.get("limit") or params.get("fetch_limit", 1000)) proxy = params.get("proxy", False) event_type = [e_type.strip().lower() for e_type in params.get("event_type", ["activities", "threats", "alerts"])] demisto.debug(f"Command being called is {command}") try: headers = {"Authorization": f"ApiToken {api_key}"} client = Client(base_url=base_url, verify=verify_certificate, headers=headers, proxy=proxy, fetch_limit=fetch_limit) if command == "test-module": result = test_module(client, event_type) return_results(result) elif command == f"{VENDOR}-get-events": events, results = get_events_command(client, first_fetch_time, event_type) # type: ignore return_results(results) if argToBoolean(args.get("should_push_events", False)): add_keys_to_events(events, instance_url) send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) elif command == "fetch-events": last_run = demisto.getLastRun() or first_run(first_fetch_time) # type: ignore next_run, events = fetch_events(client=client, last_run=last_run, event_type=event_type) add_keys_to_events(events, instance_url) send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT) demisto.setLastRun(next_run) # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()