AppSentinels.ai
Appsentinels.ai offers a platform for collecting, analyzing, and managing security events to provide comprehensive application protection.
Analytics & SIEM · AppSentinels.ai
Details
| ID | AppSentinels.ai |
|---|---|
| Provider | AppSentinels Ai |
| Category | Analytics & SIEM |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | XSIAM |
README
Appsentinels.ai offers a platform for collecting, analyzing, and managing security events to provide comprehensive application protection.
This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.
Configure AppSentinels.ai in Cortex
| Parameter | Description | Required |
|---|---|---|
| Your server URL | True | |
| User Key | The Client User key for connection with AppSentinels.ai. | True |
| API Key | The Client API key for connection with AppSentinels.ai. | True |
| Organization name | The organization name. | True |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch events | False | |
| Maximum number of audit logs per fetch | Maximum number of Audit Log entries to retrieve per fetch cycle. default value is 5000. | 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.
appsentinels-get-events
Retrieves a list of events from the AppSentinels.ai instance.
Base Command
appsentinels-get-events
Input
| Argument Name | Description | Required |
|---|---|---|
| should_push_events | Set this argument to true to create events, otherwise it only displays them. Possible values are: true, false. Default is false. | Required |
| limit | Returns no more than the specified number of events. | Optional |
| first_fetch | The UTC date or relative timestamp from when to start fetching events. Supported formats: N minutes, N hours, N days, N weeks, N months, N years, yyyy-mm-dd, yyyy-mm-ddTHH:MM. | Optional |
Context Output
There is no context output for this command.
Configuration parameters
url— Server URL (required)credentials— User Key (required)organization— Organization name (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetchEvents— Fetch eventsmax_audit_per_fetch— Maximum number of audit logs per fetch
Commands (1)
-
appsentinels-get-eventsRetrieves a list of events from the AppSentinels.ai instance.
""" Event Collector Source file for AppSentinels.ai API. """ from typing import Any import demistomock as demisto import urllib3 from CommonServerPython import * from CommonServerUserPython import * # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ VENDOR = "AppSentinels" PRODUCT = "AppSentinels" DATE_FORMAT = "%Y-%m-%d %H:%M" MAX_FETCH_AUDIT_LIMIT = 5000 """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API""" def __init__( self, base_url: str, user_key: str, api_key: str, organization: str, verify: bool, use_proxy: bool, ) -> None: """ Prepare constructor for Client class. Calls the constructor of BaseClient class and updates the header with the authentication token. Args: base_url: The url of AppSentinels.ai instance. user_key: The user key for AppSentinels.ai API. api_key: The Api key for AppSentinels.ai API - specific for every licensing. organization: The organization ID for AppSentinels.ai API. verify: True if verify SSL certificate is checked in integration configuration, False otherwise. use_proxy: True if the proxy server needs to be used, False otherwise. """ super().__init__(base_url=base_url, verify=verify, proxy=use_proxy) self._headers = { "accept": "application/json", "apikey": api_key, "x-user-key": user_key, "Content-Type": "application/json", } self.organization = organization self.api_key = api_key self.user_key = user_key def get_events_request(self, params_update: dict, body: dict) -> dict: """ Retrieve the detections from AppSentinels.ai API. Args: params_update (dict): The param update to add to the base params. body (dict): The body update to add to the base body. """ url_suffix = f"/api/v1/{self.organization}/audit-logs" params = {"page": "0", "limit": "1000", "sort": "timestamp", "sort_by": "asc", "include_system": "false"} # Page can be updated params.update(params_update) return self._http_request( "POST", url_suffix=url_suffix, headers=self._headers, json_data=body.copy(), params=params, resp_type="json" ) """ HELPER FUNCTIONS """ def remove_first_run_params(params: dict[str, Any]) -> None: """ Remove the "First Run" items from the param dictionary. Args: params (Dict[str, Any]): Integration parameters. """ for key in ("from_date", "to_date"): params.pop(key, None) def fetch_events_list(client: Client, last_run: Dict, fetch_limit: int | None, use_last_run_as_body: bool) -> List[Dict]: """ Fetches events from the AppSentinels.ai API, handling pagination and last_run. Args: client (Client): The client object for interacting with the AppSentinels.ai API. last_run (Dict): A dictionary containing the last processed event ID. fetch_limit (Optional[int]): The maximum number of events to fetch. use_last_run_as_body (bool): Flag that sign do we use the last-run as params for the API call Returns: List[Dict]: A list of fetched events. """ events: List[Dict] = [] current_pagination: int = 0 params: Dict[str, Any] = {} # Initialize params body: Dict[str, Any] = {} # Initialize body # Determine the fetch params if use_last_run_as_body: body.update(last_run) elif "last_log_id" not in last_run: # Initial fetch: from one minute ago to now current_time = get_current_time() body.update( { "from_date": (current_time - timedelta(minutes=1)).strftime(DATE_FORMAT), "to_date": current_time.strftime(DATE_FORMAT), } ) else: # Subsequent fetches: use last_log_id body["last_log_id"] = last_run["last_log_id"] while True: try: # API call demisto.debug(f"AppSentinels.ai sending http requests with arguments: {params=} {body=}") response = client.get_events_request(params_update=params, body=body) # Use the client method except DemistoException as error: raise DemistoException(f"AppSentinels.ai: During fetch, exception occurred {str(error)}") new_events = response.get("data") if response else [] if not new_events: demisto.debug("AppSentinels.ai: No Audit logs returned from API.") break pagination = response.get("pagination") last_log_id = new_events[-1].get("id") last_run["last_log_id"] = last_log_id demisto.debug(f"AppSentinels.ai fetched events with: {last_log_id=}, {pagination=}, in length: {len(new_events)}") for event in new_events: event["_TIME"] = event.get("timestamp") event["source_log_type"] = "auditlog" events.append(event) if fetch_limit and len(events) >= fetch_limit: last_run["last_log_id"] = event["id"] return events if pagination and current_pagination + 1 >= pagination: break # First run - using timestamps if "last_log_id" not in body: remove_first_run_params(body) # we make a new call with new architecture - using last_log_id as filter # pagination stays the same body["last_log_id"] = last_log_id # Not first run - we have used the "last_log_id" as a filter else: # Update Params for next call current_pagination += 1 params["page"] = current_pagination return events def prepare_list_output(events: List[dict[str, Any]]) -> str: """Prepare human-readable output. Args: events: List of entities response from the API. Returns: markdown string to be displayed in the war room. """ hr_outputs = [] for event in events: hr_output = { "ID": event.get("id"), "Action": event.get("action"), "Category": event.get("category"), "Description": event.get("description"), "Type": event.get("type"), "Time": event.get("timestamp"), } hr_outputs.append(hr_output) return tableToMarkdown(name="AppSentinels.ai Record(s)", t=hr_outputs, removeNull=True) """ COMMAND FUNCTIONS """ def test_module(client: Client) -> str: """ Tests the connection to the service by calling each one of the api endpoints. Args: client (Client): The client object used to interact with the service. Returns: str: 'ok' if the connection is successful. If an authorization error occurs, an appropriate error message is returned. """ demisto.debug("AppSentinels.ai test_module invoked") last_run: dict[str, Any] = {} fetch_events(client, last_run, fetch_limit=1) return "ok" def fetch_events( client: Client, last_run: dict, fetch_limit: int | None = None, use_last_run_as_body: bool = False ) -> tuple[list[dict[str, Any]], dict]: """Fetch the specified AppSentinels.ai entity records. Args: client (Client): The client object used to interact with the AppSentinels.ai service. last_run (dict): The last_run dictionary having the state of previous cycle. fetch_limit (int | None): The maximum number of events to fetch. use_last_run_as_body (bool): Flag that sign do we use the last-run as params for the API call Returns: Tuple[list[dict[str, Any]], dict]: A tuple containing: - List of new records to be pushed into XSIAM. - Updated last_run dictionary. """ demisto.debug("AppSentinels.ai fetch_events invoked") events = [] output = fetch_events_list(client, last_run, fetch_limit, use_last_run_as_body) events.extend(output) demisto.debug(f"AppSentinels.ai next_run is {last_run}") return events, last_run def get_events(client: Client, args: dict) -> CommandResults: """ Inner Test Function to make sure the integration works Args: client: AppSentinels.ai client to be used. args: command arguments. Returns: Command results object that contain the results. """ demisto.debug("AppSentinels.ai get_events invoked") params_run = {} default_max = 50 max_events = arg_to_number(args.get("limit")) or default_max # User start date in the get events arguments first_fetch = arg_to_datetime(args.get("first_fetch")) if first_fetch: first_fetch_date = first_fetch.strftime(DATE_FORMAT) params_run.update({"from_date": first_fetch_date}) output, _ = fetch_events(client, params_run, max_events, use_last_run_as_body=True) human_readable = prepare_list_output(output) command_results = CommandResults( readable_output=human_readable, outputs=output, outputs_prefix="AppSentinels.ai.", ) return command_results def main(): """main function, parses params and runs command functions""" demisto.debug("AppSentinels.ai has been called") params = demisto.params() args = demisto.args() command = demisto.command() # get the service API url base_url = params.get("url") verify_certificate = not argToBoolean(params.get("insecure", False)) proxy = argToBoolean(params.get("proxy", False)) user_key = params.get("credentials", {}).get("identifier") api_key = params.get("credentials", {}).get("password") organization = params.get("organization", "") fetch_limit = arg_to_number(params.get("max_audit_per_fetch")) or MAX_FETCH_AUDIT_LIMIT demisto.debug(f"AppSentinels.ai - Command being called is {command}") try: client = Client( base_url=base_url, user_key=user_key, api_key=api_key, organization=organization, verify=verify_certificate, use_proxy=proxy, ) events: List[dict[str, Any]] if command == "test-module": # Command made to test the integration result = test_module(client) return_results(result) elif command == "fetch-events": last_run = demisto.getLastRun() events, next_run = fetch_events(client, last_run, fetch_limit) if len(events): demisto.debug(f"Sending {len(events)} events to XSIAM AppSentinels.ai, before server call.") send_events_to_xsiam(events=events, vendor=VENDOR, product=PRODUCT) demisto.setLastRun(next_run) demisto.debug(f"Successfully saved last_run= {demisto.getLastRun()}") elif command == "appsentinels-get-events": command_results = get_events(client, args) events = cast(List[dict[str, Any]], command_results.outputs) if events and argToBoolean(args.get("should_push_events", False)): demisto.debug(f"Sending {len(events)} events.") send_events_to_xsiam(events=events, vendor=VENDOR, product=PRODUCT) return_results(command_results) else: raise NotImplementedError(f"Command {command} is not implemented") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): main()