Exabeam Data Lake

Exabeam Data Lake provides a searchable log management system. Data Lake is used for log collection, storage, processing, and presentation.

Analytics & SIEM · ExabeamDataLake

Details

IDExabeam Data Lake
ProviderExabeam
CategoryAnalytics & SIEM
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Exabeam Data Lake provides a searchable log management system.
Data Lake is used for log collection, storage, processing, and presentation.
This integration was integrated and tested with version LMS-i40.3 of Exabeam Data Lake.

Configure Exabeam Data Lake in Cortex

Parameter Description Required
Server URL   True
User Name   True
Password   True
Cluster Name The default value is usually ‘local’, suitable for standard setups. For custom cluster deployments, consult Exabeam Support Team. True
Trust any certificate (not secure)    
Use system proxy settings    

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.

exabeam-data-lake-search


Get events from Exabeam Data Lake.

Base Command

exabeam-data-lake-search

Input

Argument Name Description Required
query The search query string to filter the events by. Examples can be found in the syntax documentation section of the integration description. Required
start_time The starting date for the search range. The search range should be at least one day long and can extend up to a maximum of 10 days. Required
end_time The ending date for the search range. This defines the end of the search range, which should be within one to ten days after the start_time. Required
limit The maximal number of results to return. Maximum value is 3000. Optional
page The page number for pagination. Optional
page_size The maximal number of results to return per page. Maximum value is 3000. Optional

Context Output

Path Type Description
ExabeamDataLake.Event._id str The event ID.
ExabeamDataLake.Event._source.Vendor str Vendor of the event.
ExabeamDataLake.Event._source.Product str Product of the event.
ExabeamDataLake.Event._source.@timestamp str The time of the event.
ExabeamDataLake.Event._source.message str The message of the event.

Command example

!exabeam-data-lake-search query="risk_score:3" start_time="2024.02.27" end_time="2024.02.28" limit=2

Context Example

{
    "ExabeamDataLake": {
        "Event": [
            {
                "_id": "some_id",
                "_index": "exabeam-2024.02.28",
                "_routing": "SfA86vqw",
                "_score": null,
                "_source": {
                    "@timestamp": "2024-02-28T16:15:50.614Z",
                    "@version": "1",
                    "Product": "Exabeam AA",
                    "Vendor": "Exabeam",
                    "data_type": "exabeam-security-alert",
                    "exa_activity_type": [
                        "alert/security",
                        "alert"
                    ],
                    "exa_adjustedEventTime": "2024-02-28T16:15:29.000Z",
                    "exa_category": "Exabeam Alerts",
                    "exa_device_type": [
                        "security"
                    ],
                    "exa_rawEventTime": "2024-02-28T16:15:29.000Z",
                    "indexTime": "2024-02-28T16:15:51.626Z",
                    "is_ransomware_src_ip": false,
                    "is_threat_src_ip": false,
                    "is_tor_src_ip": false,
                    "log_type": "dlp-alert",
                    "message": "<86>1 2024-02-28T16:15:50.609Z exabeam-analytics-master Exabeam - - - timestamp=\"2024-02-28T16:15:29.192Z\" score=\"3\" user=\"ghardin\" event_time=\"2024-02-28 14:35:35\" event_type=\"dlp-alert\" domain=\"kenergy\" time=\"1709130935833\" source=\"ObserveIT\" vendor=\"ObserveIT\" lockout_id=\"NA\" session_id=\"ghardin-20240228143533\" session_order=\"2\" account=\"ghardin\" getvalue('zone_info', src)=\"new york office\" alert_name=\" rule violation\" local_asset=\"lt-ghardin-888\" alert_type=\"DATA EXFILTRATION\" os=\"Win\" rule_name=\"Abnormal DLP alert name for user\" rule_description=\"Exabeam noted that this alert name has been triggered for this user in the past yet it is still considered abnormal activity. This activity may be an early indication of compromise of a user by malware or other malicious actors.\" rule_reason=\"Abnormal DLP alert with name  rule violation for user\" ",
                    "port": 41590,
                    "risk_score": "3",
                    "rule_description": "Exabeam noted that this alert name has been triggered for this user in the past yet it is still considered abnormal activity. This activity may be an early indication of compromise of a user by malware or other malicious actors.",
                    "rule_name": "Abnormal DLP alert name for user",
                    "score": "3",
                    "session_id": "ghardin-20240228143533",
                    "time": "2024-02-28T16:15:29.000Z",
                    "user": "ghardin"
                },
                "_type": "logs",
                "sort": [
                    1709136950614
                ]
            },
            {
                "_id": "another_id",
                "_index": "exabeam-2024.02.27",
                "_routing": "XUXxevyv",
                "_score": null,
                "_source": {
                    "@timestamp": "2024-02-27T16:21:45.721Z",
                    "@version": "1",
                    "Product": "Exabeam AA",
                    "Vendor": "Exabeam",
                    "data_type": "exabeam-security-alert",
                    "event_code": "4768",
                    "exa_activity_type": [
                        "alert/security",
                        "alert"
                    ],
                    "exa_adjustedEventTime": "2024-02-24T16:16:29.000Z",
                    "exa_category": "Exabeam Alerts",
                    "exa_device_type": [
                        "security"
                    ],
                    "exa_rawEventTime": "2024-02-24T16:16:29.000Z",
                    "host": "exabeamdemodc1",
                    "indexTime": "2024-02-27T16:23:56.271Z",
                    "is_ransomware_dest_ip": false,
                    "is_threat_dest_ip": false,
                    "is_tor_dest_ip": false,
                    "log_type": "kerberos-logon",
                    "message": "<86>1 2024-02-27T16:21:45.539Z exabeam-analytics-master Exabeam - - - timestamp=\"2024-02-24T16:16:29.975Z\" id=\"ghardin-20240224140716\" score=\"3\" user=\"ghardin\" event_time=\"2024-02-24 14:34:42\" event_type=\"kerberos-logon\" host=\"exabeamdemodc1\" domain=\"ktenergy\" time=\"1708785282052\" source=\"DC\" lockout_id=\"NA\" session_id=\"ghardin-20240224140716\" session_order=\"4\" account=\"ghardin\" ticket_options_encryption=\"0x40810010:0x12\" nonmachine_user=\"ghardin\" event_code=\"4768\" ticket_encryption_type=\"0x12\" ticket_options=\"0x40810010\" rule_name=\"IT presence without badge access\" rule_description=\"This user is logged on to the company network but did not use their badge to access a physical location. It is unusual to have IT access without badge access.\" rule_reason=\"IT presence without badge access\" ",
                    "port": 56920,
                    "risk_score": "3",
                    "rule_description": "This user is logged on to the company network but did not use their badge to access a physical location. It is unusual to have IT access without badge access.",
                    "rule_name": "IT presence without badge access",
                    "score": "3",
                    "session_id": "ghardin-20240224140716",
                    "time": "2024-02-24T16:16:29.000Z",
                    "user": "ghardin"
                },
                "_type": "logs",
                "sort": [
                    1709050905721
                ]
            }
        ]
    }
}

Human Readable Output

Logs

Created_at Id Message Product Vendor
2024-02-28T16:15:50.614Z some_id <86>1 2024-02-28T16:15:50.609Z exabeam-analytics-master Exabeam - - - timestamp=”2024-02-28T16:15:29.192Z” id=”ghardin-20240228143533” score=”3” user=”ghardin” event_time=”2024-02-28 14:35:35” event_type=”dlp-alert” domain=”kenergy” time=”1709130935833” source=”ObserveIT” vendor=”ObserveIT” lockout_id=”NA” session_id=”ghardin-20240228143533” session_order=”2” account=”ghardin” getvalue(‘zone_info’, src)=”new york office” alert_name=” rule violation” local_asset=”lt-ghardin-888” alert_type=”DATA EXFILTRATION” os=”Win” rule_name=”Abnormal DLP alert name for user” rule_description=”Exabeam noted that this alert name has been triggered for this user in the past yet it is still considered abnormal activity. This activity may be an early indication of compromise of a user by malware or other malicious actors.” rule_reason=”Abnormal DLP alert with name rule violation for user” Exabeam AA Exabeam
2024-02-27T16:21:45.721Z another_id <86>1 2024-02-27T16:21:45.539Z exabeam-analytics-master Exabeam - - - timestamp=”2024-02-24T16:16:29.975Z” id=”ghardin-20240224140716” score=”3” user=”ghardin” event_time=”2024-02-24 14:34:42” event_type=”kerberos-logon” host=”exabeamdemodc1” domain=”ktenergy” time=”1708785282052” source=”DC” lockout_id=”NA” session_id=”ghardin-20240224140716” session_order=”4” account=”ghardin” ticket_options_encryption=”0x40810010:0x12” nonmachine_user=”ghardin” event_code=”4768” ticket_encryption_type=”0x12” ticket_options=”0x40810010” rule_name=”IT presence without badge access” rule_description=”This user is logged on to the company network but did not use their badge to access a physical location. It is unusual to have IT access without badge access.” rule_reason=”IT presence without badge access” Exabeam AA Exabeam

Configuration parameters

  • url — Server URL (required)
  • credentials — User Name (required)
  • cluster_name — Cluster Name (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • exabeam-data-lake-search

    Get events from Exabeam Data Lake.

import json

import pytest
from CommonServerPython import DemistoException
from ExabeamDataLake import (
    Client,
    _parse_entry,
    calculate_page_parameters,
    dates_in_range,
    get_date,
    get_limit,
    query_data_lake_command,
)


class MockClient(Client):
    def __init__(self, base_url: str, username: str, password: str, verify: bool, proxy: bool):
        pass

    def query_data_lake_command(self) -> None:
        return


def test_query_data_lake_command(mocker):
    """
    GIVEN:
        a mocked Client with an empty response,

    WHEN:
        'query_data_lake_command' function is called with the provided arguments,

    THEN:
        it should query the data lake, return log entries, and format them into readable output.
    """
    args = {"page": 1, "page_size": 50, "start_time": "2024-05-01T00:00:00", "end_time": "2024-05-08T00:00:00", "query": "*"}
    mock_response = {
        "responses": [
            {
                "hits": {
                    "hits": [
                        {"_id": "FIRST_ID", "_source": {"@timestamp": "2024-05-01T12:00:00", "message": "example message 1"}},
                        {
                            "_id": "SECOND_ID",
                            "_source": {
                                "@timestamp": "2024-05-02T12:00:00",
                                "message": "example message 2",
                                "only_hr": "nothing",
                            },
                        },
                    ]
                }
            }
        ]
    }

    mocker.patch.object(Client, "query_datalake_request", return_value=mock_response)

    client = MockClient("", "", "", False, False)

    response = query_data_lake_command(client, args, cluster_name="local")

    result = response.to_context().get("EntryContext", {}).get("ExabeamDataLake.Event", [])

    assert {"_id": "FIRST_ID", "_source": {"@timestamp": "2024-05-01T12:00:00", "message": "example message 1"}} in result
    assert {
        "_id": "SECOND_ID",
        "_source": {"@timestamp": "2024-05-02T12:00:00", "message": "example message 2", "only_hr": "nothing"},
    } in result
    expected_result = (
        "### Logs\n"
        "|Id|Vendor|Product|Created_at|Message|\n"
        "|---|---|---|---|---|\n"
        "| FIRST_ID |  |  | 2024-05-01T12:00:00 | example message 1 |\n"
        "| SECOND_ID |  |  | 2024-05-02T12:00:00 | example message 2 |\n"
    )
    assert expected_result in response.readable_output


def test_query_data_lake_command_no_response(mocker):
    """
    GIVEN:
        a mocked Client with an empty response,
    WHEN:
        'query_data_lake_command' function is called with the provided arguments,
    THEN:
        it should return a readable output indicating no results found.

    """
    args = {"page": 1, "page_size": 50, "start_time": "2024-05-01T00:00:00", "end_time": "2024-05-08T00:00:00", "query": "*"}

    mocker.patch.object(Client, "query_datalake_request", return_value={})

    response = query_data_lake_command(MockClient("", "", "", False, False), args, "local")

    assert response.readable_output == "### Logs\n**No entries.**\n"


def test_get_date(mocker):
    """
    GIVEN:
        a mocked CommonServerPython.arg_to_datetime function returning a specific time string,

    WHEN:
        'get_date' function is called with the provided time string,

    THEN:
        it should return the date part of the provided time string in the 'YYYY-MM-DD' format.
    """
    time = "2024.05.01T14:00:00"
    expected_result = "2024-05-01"

    with mocker.patch("CommonServerPython.arg_to_datetime", return_value=time):
        result = get_date(time, "start_time")

    assert result == expected_result


@pytest.mark.parametrize(
    "start_time_str, end_time_str, expected_output",
    [
        (
            "2024-05-01",
            "2024-05-10",
            [
                "2024.05.01",
                "2024.05.02",
                "2024.05.03",
                "2024.05.04",
                "2024.05.05",
                "2024.05.06",
                "2024.05.07",
                "2024.05.08",
                "2024.05.09",
                "2024.05.10",
            ],
        ),
        ("2024-05-01", "2024-05-05", ["2024.05.01", "2024.05.02", "2024.05.03", "2024.05.04", "2024.05.05"]),
    ],
)
def test_dates_in_range_valid(start_time_str, end_time_str, expected_output):
    """
    GIVEN:
        start_time_str, end_time_str, and expected_output representing start time, end time, and expected output, respectively,

    WHEN:
        'dates_in_range' function is called with the provided start and end time strings,

    THEN:
        it should return a list of dates in range between the start time and end time.
    """
    result = dates_in_range(start_time_str, end_time_str)
    assert result == expected_output


@pytest.mark.parametrize(
    "start_time_str, end_time_str, expected_output",
    [
        ("2024-05-10", "2024-05-01", "Start time must be before end time"),
        ("2024-05-01", "2024-05-15", "Difference between start time and end time must be less than or equal to 10 days"),
    ],
)
def test_dates_in_range_invalid(start_time_str, end_time_str, expected_output):
    """
    GIVEN:
        start_time_str, end_time_str, and expected_output representing start time, end time, and expected output, respectively,

    WHEN:
        'dates_in_range' function is called with the provided start and end time strings that are invalid,

    THEN:
        it should raise a DemistoException with the expected error message.
    """
    with pytest.raises(DemistoException, match=expected_output):
        dates_in_range(start_time_str, end_time_str)


@pytest.mark.parametrize(
    "args, from_param_expected, size_param_expected",
    [({"page": "1", "page_size": "50", "limit": None}, 0, 50), ({"page": None, "page_size": None, "limit": "100"}, 0, 100)],
)
def test_calculate_page_parameters_valid(args, from_param_expected, size_param_expected):
    """
    GIVEN:
        args, from_param_expected, and size_param_expected representing input arguments,
        expected 'from' parameter, and expected 'size' parameter, respectively,

    WHEN:
        'calculate_page_parameters' function is called with the provided arguments,

    THEN:
        it should return the expected 'from' and 'size' parameters based on the input arguments.
    """
    from_param, size_param = calculate_page_parameters(args)
    assert from_param == from_param_expected
    assert size_param == size_param_expected


@pytest.mark.parametrize(
    "args",
    [
        ({"page": "1", "page_size": None, "limit": "100"}),
        ({"page": "1", "page_size": "25", "limit": "100"}),
        ({"page": None, "page_size": "25", "limit": None}),
    ],
)
def test_calculate_page_parameters_invalid(mocker, args):
    """
    GIVEN:
        args representing input arguments with invalid combinations of 'page', 'page_size', and 'limit',

    WHEN:
        'calculate_page_parameters' function is called with the provided arguments,

    THEN:
        it should raise a DemistoException with the expected error message.
    """
    with pytest.raises(DemistoException, match="You can only provide 'limit' alone or 'page' and 'page_size' together."):
        calculate_page_parameters(args)


def test_parse_entry():
    """
    GIVEN:
        an entry dictionary representing a log entry with various fields such as '_id', '_source', 'Vendor', '@timestamp',
        'Product', and 'message',

    WHEN:
        '_parse_entry' function is called with the provided entry dictionary,

    THEN:
        it should parse the entry and return a dictionary with the expected fields renamed for consistency.
    """
    entry = {
        "_id": "12345",
        "_source": {
            "Vendor": "VendorName",
            "@timestamp": "2024-05-09T12:00:00Z",
            "Product": "ProductA",
            "message": "Some message here",
        },
    }

    parsed_entry = _parse_entry(entry)
    assert parsed_entry["Id"] == "12345"
    assert parsed_entry["Vendor"] == "VendorName"
    assert parsed_entry["Created_at"] == "2024-05-09T12:00:00Z"
    assert parsed_entry["Product"] == "ProductA"
    assert parsed_entry["Message"] == "Some message here"


def test_query_datalake_request(mocker):
    """
    GIVEN:
        a mocked '_login' method and '_http_request' method of the Client class,
        a base URL, username, password, headers, proxy, and search query,

    WHEN:
        'query_datalake_request' method of the Client class is called with the provided search query,

    THEN:
        it should send a POST request to the data lake API with the search query,
        using the correct base URL and headers including 'kbn-version' and 'Content-Type'.
    """
    mock_login = mocker.patch("ExabeamDataLake.Client._login")
    mock_http_request = mocker.patch("ExabeamDataLake.Client._http_request")

    base_url = "http://example.com"
    username = "user123"
    password = "password123"
    proxy = False
    args = {"query": "*"}
    from_param = 0
    size_param = 10
    cluster_name = "example_cluster"
    dates_in_format = ["index1", "index2"]

    instance = Client(base_url=base_url, username=username, password=password, verify=False, proxy=proxy)

    expected_search_query = {
        "sortBy": [{"field": "@timestamp", "order": "desc", "unmappedType": "date"}],
        "query": "*",
        "from": 0,
        "size": 10,
        "clusterWithIndices": [{"clusterName": "example_cluster", "indices": ["index1", "index2"]}],
    }

    instance.query_datalake_request(args, from_param, size_param, cluster_name, dates_in_format)

    mock_http_request.assert_called_once_with(
        "POST",
        full_url="http://example.com/dl/api/es/search",
        data=json.dumps(expected_search_query),
        headers={"Content-Type": "application/json", "Csrf-Token": "nocheck"},
    )
    mock_login.assert_called_once()


@pytest.mark.parametrize(
    "args, arg_name, expected_output",
    [({}, "limit", 50), ({"limit": None}, "limit", 50), ({"limit": 1000}, "limit", 1000), ({"limit": 5000}, "limit", 3000)],
)
def test_get_limit(args, arg_name, expected_output):
    """
    GIVEN:
        a dictionary containing the 'limit' argument with various values.

    WHEN:
        'get_limit' function is called with the provided dictionary.

    THEN:
        it should return the limit value if specified and less than or equal to 3000;
        otherwise, it should return 3000 as the maximum limit.
        If the 'limit' argument is not present in the dictionary or is None, it should return 50 as the default limit.
    """
    assert get_limit(args, arg_name) == expected_output