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
| ID | Exabeam Data Lake |
|---|---|
| Provider | Exabeam |
| Category | Analytics & SIEM |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix 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-searchGet 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