AppSentinels.ai

Appsentinels.ai offers a platform for collecting, analyzing, and managing security events to provide comprehensive application protection.

Analytics & SIEM · AppSentinels.ai

Details

IDAppSentinels.ai
ProviderAppSentinels Ai
CategoryAnalytics & SIEM
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesXSIAM

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 settings
  • isFetchEvents — Fetch events
  • max_audit_per_fetch — Maximum number of audit logs per fetch

Commands (1)

  • appsentinels-get-events

    Retrieves a list of events from the AppSentinels.ai instance.

import pytest
from CommonServerPython import *
from freezegun import freeze_time
from typing import Any
from datetime import timedelta

import json

MOCK_BASEURL = "https://example.com"
MOCK_USER_KEY = "dummy_user_key"
MOCK_API_KEY = "dummy_api_key"
MOCK_ORGANIZATION = "dummy_org"
EMPTY_RESPONSE = {"data": [], "pagination": 1, "total": 0}

from AppSentinelsAi import (
    Client,
    DATE_FORMAT,
    remove_first_run_params,
    fetch_events,
    get_events,
)


@pytest.fixture
def client():
    """
    A dummy client fixture for testing.
    """
    return Client(
        base_url=MOCK_BASEURL,
        user_key=MOCK_USER_KEY,
        api_key=MOCK_API_KEY,
        organization=MOCK_ORGANIZATION,
        verify=False,
        use_proxy=False,
    )


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


class TestHelperFunctions:
    @pytest.mark.parametrize(
        "input_params, expected_params",
        [
            ({"from_date": "2024-01-01", "to_date": "2024-01-31", "other": 1}, {"other": 1}),
            ({"from_date": "2024-01-01", "other": 1}, {"other": 1}),
            ({"to_date": "2024-01-31", "other": 1}, {"other": 1}),
            ({"other": 1}, {"other": 1}),
            ({}, {}),
        ],
    )
    def test_remove_first_run_params(self, input_params: dict[str, Any], expected_params: dict[str, Any]):
        """
        Test remove_first_run_params function behavior.

        Given:
            - Valid HTTP request parameters.
            - Expected params at the end of the function run,

        When:
            - Removing first run params
        Then:
            - Make sure the request will be sent with right parameters.
        """
        params_copy = input_params.copy()  # Avoid modifying original test data
        remove_first_run_params(params_copy)
        assert params_copy == expected_params


class TestFetchEvents:
    @pytest.fixture
    def mock_response_data(self):
        """Provides sample API response data."""
        response = util_load_json("test_data/events-dummy-data.json")
        return response

    @freeze_time("2023-10-26 10:05:00")  # Control the time for consistent testing
    def test_fetch_events_list_initial_fetch(self, client, mocker, mock_response_data):
        """
        Test fetch_events_list for the initial fetch (no last_log_id).

        Given:
            - Valid HTTP request parameters
            - No last_run object
            - Valid response data

        When:
            - Fetching events.
        Then:
            - Make sure the request is sent with right parameters.
            - Make sure the 'from' aql parameter request is sent with the "current" time 2023-10-26T10:05:00.
            - Make sure the pagination logic performs as expected.
        """
        mocker.patch.object(client, "get_events_request", return_value=mock_response_data)
        last_run = {}
        events, _ = fetch_events(client, last_run)

        assert len(events) == len(mock_response_data["data"])
        assert last_run == {"last_log_id": mock_response_data["data"][-1]["id"]}
        client.get_events_request.assert_called_once()  # Check if the API call was made

        current_time = get_current_time()
        start_time = (current_time - timedelta(minutes=1)).strftime(DATE_FORMAT)
        end_time = current_time.strftime(DATE_FORMAT)

        # Check the parameters of the API call
        expected_body = {
            "from_date": start_time,
            "to_date": end_time,
        }
        client.get_events_request.assert_called_with(body=expected_body, params_update={})

        # Check _TIME and source_log_type
        for event in events:
            assert event["_TIME"] == event["timestamp"]
            assert event["source_log_type"] == "auditlog"

    def test_fetch_events_list_subsequent_fetch(self, client, mocker, mock_response_data):
        """
        Test fetch_events_list for subsequent fetches (with last_log_id).

        Given:
            - Valid HTTP request parameters.
            - last_run argument.

        When:
            - Fetching events.
        Then:
            - Make sure the request is sent with right parameters.
            - Make sure the 'from' aql parameter request is sent with the correct id.
            - Make sure that XSIAM entries were added correctly.
        """
        mocker.patch.object(client, "get_events_request", return_value=mock_response_data)
        last_run = {"last_log_id": 0}  # Simulate a previous fetch
        events, _ = fetch_events(client, last_run)

        assert len(events) == len(mock_response_data["data"])
        assert last_run == {"last_log_id": mock_response_data["data"][-1]["id"]}
        client.get_events_request.assert_called_once()
        expected_body = {"last_log_id": 0}
        client.get_events_request.assert_called_with(body=expected_body, params_update={})

        # Check _TIME and source_log_type
        for event in events:
            assert event["_TIME"] == event["timestamp"]
            assert event["source_log_type"] == "auditlog"

    def test_fetch_events_list_fetch_limit(self, client, mocker, mock_response_data):
        """
        Test fetch_events_list with a fetch_limit.

        Given:
            - Valid HTTP request parameters.
            - Fetch limit.

        When:
            - Fetching events.
        Then:
            - Make Sure the number of events are according to the limit.
            - Make sure the pagination logic performs as expected.
        """
        mocker.patch.object(client, "get_events_request", return_value=mock_response_data)
        last_run = {"last_log_id": 2}
        fetch_limit = 2
        events, _ = fetch_events(client, last_run, fetch_limit)

        assert len(events) == fetch_limit
        assert last_run == {"last_log_id": mock_response_data["data"][1]["id"]}
        client.get_events_request.assert_called_once()

    def test_fetch_events_list_multiple_pages(self, client, mocker):
        """
        Test fetch_events_list with multiple pages of results.

        Given:
            - Valid HTTP request parameters.
            - Two different request response data.

        When:
            - Fetching events.
        Then:
            - Make sure the request is sent with right parameters.
            - Make sure the pagination logic performs as expected.
        """
        mock_response_page1 = {
            "data": [{"id": 1, "timestamp": "2023-10-26T10:00:00Z"}, {"id": 2, "timestamp": "2023-10-26T10:01:00Z"}],
            "pagination": 2,
            "total": 4,
        }
        mock_response_page2 = {
            "data": [{"id": 3, "timestamp": "2023-10-26T10:02:00Z"}, {"id": 4, "timestamp": "2023-10-26T10:03:00Z"}],
            "pagination": 2,
            "total": 2,
        }
        mocker.patch.object(client, "get_events_request", side_effect=[mock_response_page1, mock_response_page2])
        last_run = {"last_log_id": 0}
        events, _ = fetch_events(client, last_run)

        expected_body_1 = {"last_log_id": 0}
        expected_params_2 = {"page": 1}

        first_call_args = client.get_events_request.call_args_list[0][1]  # kwargs of first call
        second_call_args = client.get_events_request.call_args_list[1][1]  # kwargs of second call

        assert first_call_args["body"] == expected_body_1
        assert second_call_args["body"] == expected_body_1
        assert second_call_args["params_update"] == expected_params_2

        assert len(events) == 4
        assert last_run == {"last_log_id": 4}
        assert client.get_events_request.call_count == 2

    def test_fetch_events_list_no_events(self, client, mocker):
        """
        Test fetch_events_list when the API returns no events.

        Given:
            - Valid HTTP request parameters.
            - Empty response

        When:
            - Fetching events.
        Then:
            - Make sure the pagination logic performs as expected.
            - Make sure the request is returning with right parameters for last_run.
        """
        mocker.patch.object(client, "get_events_request", return_value={"data": [], "pagination": 1, "total": 0})
        last_run = {}
        events, _ = fetch_events(client, last_run)

        assert not events
        assert last_run == {}  # last_run should not be updated if no events are returned

    def test_fetch_events_list_with_fetch_limit_smaller_than_page_size(self, client, mocker, mock_response_data):
        """
        Test fetch_events_list with fetch_limit smaller than the events returned.

        Given:
            - Valid HTTP request parameters.
            - Fetch limit smaller than response.

        When:
            - Fetching events.
        Then:
            - Make Sure the number of events are according to the limit.
            - Make sure the last_run is updated correctly.
        """
        mocker.patch.object(client, "get_events_request", return_value=mock_response_data)
        last_run = {}
        fetch_limit = 2
        events, _ = fetch_events(client, last_run, fetch_limit)

        assert len(events) == fetch_limit
        assert last_run == {"last_log_id": 2}

    def test_fetch_events_list_with_fetch_limit_larger_than_total_events(self, client, mocker, mock_response_data):
        """
        Test fetch_events_list with fetch_limit larger than the total number of events.

        Given:
            - Valid HTTP request parameters.
            - Fetch limit smaller larger than response.

        When:
            - Fetching events.
        Then:
            - Make sure the request is sent with right parameters.
            - Make Sure the number of events are according to the limit.
            - Make sure the pagination logic performs as expected.
        """

        mocker.patch.object(client, "get_events_request", return_value=mock_response_data)
        last_run = {}
        fetch_limit = 5
        events, _ = fetch_events(client, last_run, fetch_limit)

        assert len(events) == 4
        assert last_run == {"last_log_id": 4}


class TestGetEvents:
    @pytest.fixture
    def mock_client_get_events_request(self, mocker):
        """Mocks the Client's get_events_request method."""
        return mocker.patch.object(Client, "get_events_request")

    def test_get_events_no_events(self, client, mock_client_get_events_request):
        """
        Test get_events when the API returns no events.

        Given:
            - Valid HTTP request parameters.
            - Empty Response.

        When:
            - Fetching events.
        Then:
            - Make sure that correct output is returned
        """
        mock_client_get_events_request.return_value = {"data": [], "pagination": 1, "total": 0}
        args = {}  # Empty args
        result = get_events(client, args)

        assert result.outputs == []
        assert result.readable_output == "### AppSentinels.ai Record(s)\n**No entries.**\n"

    def test_get_events_with_events(self, client, mock_client_get_events_request):
        """
        Test get_events with events returned.

        Given:
            - Valid HTTP request parameters.
            - Valid http response data.

        When:
            - Fetching events.
        Then:
            - Make sure the request output is the data given as the response.
            - Make sure the readable output looks as expected.
        """
        dummy_data = util_load_json("test_data/events-dummy-data.json")
        mock_client_get_events_request.return_value = dummy_data
        args = {}
        result = get_events(client, args)

        assert result.outputs == dummy_data["data"]
        assert result.outputs_prefix == "AppSentinels.ai."
        # Basic check for table format, more detailed checks could be added
        assert "ID" in result.readable_output
        assert "Action" in result.readable_output

    def test_get_events_with_limit(self, client, mock_client_get_events_request):
        """
        Test get_events with a limit argument.

        Given:
            - Valid HTTP request parameters.
            - Fetch limit.

        When:
            - Fetching events.
        Then:
            - Make Sure the number of events are according to the limit.
        """
        dummy_data = util_load_json("test_data/events-dummy-data.json")
        mock_client_get_events_request.return_value = dummy_data

        args = {"limit": 2}
        result = get_events(client, args)

        assert len(result.outputs) == 2

    @freeze_time("2025-01-15 12:00:00")
    def test_get_events_with_first_fetch(self, client, mock_client_get_events_request):
        """
        Test get_events with a first_fetch argument.

        Given:
            - Valid HTTP request parameters.
            - First fetch date.

        When:
            - Fetching events.
        Then:
            - Make sure the request is sent with right parameters.
        """
        mock_events_data = EMPTY_RESPONSE
        mock_client_get_events_request.return_value = mock_events_data
        first_fetch_time = "1 Days"
        args = {"first_fetch": first_fetch_time}
        get_events(client, args)
        mock_client_get_events_request.assert_called_once()
        expected_body = {"from_date": "2025-01-14 12:00"}
        mock_client_get_events_request.assert_called_with(body=expected_body, params_update={})