BloodHoundEnterprise Deprecated

Deprecated. Use the SpecterOps BloodHound Enterprise integration instead. Use this integration to fetch audit logs from BloodHound Enterprise as events in Cortex XSIAM.

Analytics & SIEM · BloodHound Enterprise (Deprecated)

Details

IDBloodHoundEnterprise
ProviderSpecter Ops Inc
CategoryAnalytics & SIEM
From Version8.4.0
Docker Imagedemisto/python3:3.12.11.4819260
Supported ModulesXSIAM

README

BloodHound Enterprise Integration for Cortex XSIAM

Use this integration to fetch audit logs from BloodHound Enterprise as events in Cortex XSIAM.
This integration was integrated and tested with version 1.0.0 of BloodHound Enterprise.

Configure BloodHound Enterprise on Cortex XSIAM

  1. Navigate to Settings > Configurations > Automation & Feed Integrations.
  2. Search for BloodHoundEnterprise.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Required Description
    Server URL Yes The URL of your BloodHound Enterprise server.
    API Token ID Yes The ID associated with the API Token Key.
    API Token Key Yes The key used for authentication with the BloodHound Enterprise API.
    Fetch events No Enable this to fetch events automatically.
    Maximum number of events per fetch No Limits the number of events fetched in each cycle. Default is 5000.
    Trust any certificate (not secure) No Allow the integration to trust any SSL certificate, even if it’s not secure.
    Use system proxy settings No Use the system’s proxy configuration for connecting to the server.
  4. Click Test to validate the connection using the provided details.

Note

This API can be run only with ADMIN privileges.

Commands

The following commands can be executed from the Cortex XSIAM CLI, as part of an automation, or in a playbook.
Each command returns relevant information that you can use for further analysis or alerting.

bloodhound-get-events

Retrieves events from BloodHound Enterprise based on the specified date range.
This command allows you to monitor and analyze recent events directly from the Cortex XSIAM interface.

Base Command

bloodhound-get-events

Input

Argument Name Description Required
start The start date for filtering events. Use ISO 8601 format (e.g., 2024-07-10T08:08:46Z). Default is one minute back No
end The end date for filtering events. Use ISO 8601 format (e.g., 2024-07-11T08:09:47Z). Default is current time No
should_push_events Set to true to create events in Cortex XSIAM; false will only display them. No
limit The maximum number of events to return. Default is 10. No

Example Usage

```shell
!bloodhound-get-events start=”2024-07-10T08:00:00Z” end=”2024-07-11T08:00:00Z” limit=100 should_push_events=true

Configuration parameters

  • server_url — Server URL (e.g., https://example.bloodhoundenterprise.io) (required)
  • client — API Token ID (required)
  • max_events_per_fetch — Maximum number of events per fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • bloodhound-get-events

    Gets events from BloodHound Enterprise.

from freezegun import freeze_time
import demistomock as demisto
from unittest.mock import Mock
import json


def load_test_events():
    with open("test_data/event_list.json") as file:
        return json.load(file)


def create_mock_client(test_events):
    client = Mock()

    def mock_search_events(limit, from_date, until_date, offset):
        return test_events[offset : offset + limit]

    client.search_events.side_effect = mock_search_events
    return client


def test_client_request(mocker):
    """
    Given:
        - The `query_params` dictionary includes:
            - `limit`: 50
            - `sort_by`: "created_at"
            - `after`: "2024-11-18T11:16:09.076711Z"
            - `before`: "2024-11-18T14:00:20.303699Z"
        - The `_http_request` method is patched to avoid actual HTTP requests.
        - The `demisto.debug` method is patched to capture debug log outputs.
    When:
        - The `client._request` method is called with the provided `query_params`.
    Then:
        - Ensure that the debug log contains the expected query string with `sort_by=created_at` and `after` parameters.
        - Validate that the expected parameters are found in the log call arguments.
    """
    from BloodHoundEnterprise import Client, Credentials

    query_params = {
        "limit": 50,
        "sort_by": "created_at",
        "after": "2024-11-18T11:16:09.076711",
        "before": "2024-11-18T14:00:20.303699",
    }
    client = Client(
        base_url="example.com",
        verify=False,
        proxy=False,
        credentials=Credentials(token_id="token_id", token_key="token_key"),
    )

    mocker.patch.object(client, "_http_request")
    log = mocker.patch.object(demisto, "debug")
    client._request("GET", "/api/v2/audit", query_params=query_params)
    found = any(
        "/api/v2/audit?limit=50&sort_by=created_at&after=2024-11-18T11%3A16%3A09.076711&before=2024-11-18T14%3A00%3A20.303699,"
        in call.args[0]
        for call in log.call_args_list
    )
    assert found, "'sort_by=created_at&after' was not found in any demisto.debug calls."


@freeze_time("2024-11-22T13:28:27.698038Z")
def test_fetch_events_first_time(mocker):
    """
    Given:
        - A mock client with a predefined set of test events loaded from a JSON file.
        - The current date and time are frozen at "2024-11-22T13:28:27.698038Z".
        - No previous fetch history from `demisto.getLastRun` (indicating the first time fetching events).
        - The parameter for `max_events_per_fetch` is set to "7".

    When:
        - The `fetch_events` function is called with empty 'getLastRun' like the first fetch time.

    Then:
        - Ensure that exactly 7 events are fetched.
        - Validate that the next run metadata (`next_run`) is correctly updated with:
            - `last_event_date` timestamp.
            - `last_event_id` of the last fetched event.
            - `fetch_id` set to 1.
            - `skip` indicating the number of events fetched.
        - Verify that the first event in the fetched list has the correct ID (2051).
    """
    from BloodHoundEnterprise import fetch_events

    test_events = load_test_events()
    client = create_mock_client(test_events)
    mocker.patch.object(demisto, "getLastRun", return_value={})
    mocker.patch.object(demisto, "debug")

    next_run, events = fetch_events(
        client=client,
        params={"max_events_per_fetch": "7"},
    )

    assert len(events) == 7
    assert next_run.get("last_event_date", "").startswith("2024-11-22T13:27:27.698038+")
    assert all(
        (
            next_run.get("last_event_id") == 2057,
            next_run.get("fetch_id") == 1,
            next_run.get("offset") == 7,
        )
    )
    assert events[0].get("id") == 2051


@freeze_time("2024-11-20T13:17:24.074375+02:00")
def test_fetch_events_second_time(mocker):
    """
    Given:
        - A mock client with a predefined set of test events loaded from a JSON file.
        - The current date and time are frozen at "2024-11-20T13:17:24.074375+02:00".
        - The previous fetch history is simulated using `demisto.getLastRun`, with the following values:
            - `last_event_date` set to "2024-11-24T12:43:57.27948Z".
            - `last_event_id` set to 2072.
            - `fetch_id` set to 1.

    When:
        - The `fetch_events` function is called to retrieve events with the mock client,
            with a parameter for `max_events_per_fetch` set to "2".

    Then:
        - Ensure that exactly 2 events are fetched.
        - Validate that the first event in the fetched list has the correct ID (2073).
        - Verify that the next run metadata (`next_run`) is correctly updated with:
            - `last_event_date` timestamp.
            - `last_event_id` of the last fetched event (2074).
            - `fetch_id` incremented to 2.
            - `skip` indicating the number of events fetched (2).
    """
    from BloodHoundEnterprise import fetch_events

    mocker.patch.object(
        demisto,
        "getLastRun",
        return_value={
            "last_event_date": "2024-11-24T12:43:57.27948Z",
            "last_event_id": 2072,
            "fetch_id": 1,
        },
    )
    mocker.patch.object(demisto, "debug")
    test_events = load_test_events()
    client = create_mock_client(test_events)
    next_run, events = fetch_events(
        client=client,
        params={"max_events_per_fetch": "2"},
    )

    assert len(events) == 2
    assert events[0].get("id") == 2073
    assert next_run.get("last_event_date", "").startswith("2024-11-24T12:43:57.27948")
    assert all(
        (
            next_run.get("last_event_id") == 2074,
            next_run.get("fetch_id") == 2,
            next_run.get("offset") == 2,
        )
    )


def test_get_events_command(mocker):
    """
    Given:
        - A mock client with a predefined set of test events loaded from a JSON file.
        - The `args` dictionary contains the following parameters:
            - `start`: "2024-11-23T18:39:35.546751Z"
            - `end`: "2024-11-23T18:39:58.113381Z"
            - `limit`: "3"

    When:
        - The `get_events_command` function is called with the mock client and the provided arguments.

    Then:
        - Ensure that exactly 3 events are returned.
        - Validate that the first event in the list has the correct ID (2051).
        - Ensure that the human-readable output (`hr.readable_output`) contains the string "2052".
    """
    from BloodHoundEnterprise import get_events_command

    args = {
        "start": "2024-11-23T18:39:35.546751Z",
        "end": "2024-11-23T18:39:58.113381Z",
        "limit": "3",
    }
    test_events = load_test_events()
    client = create_mock_client(test_events)

    events, hr = get_events_command(
        client=client,
        args=args,
    )

    assert len(events) == 3
    assert events[0].get("id") == 2051
    assert "2052" in hr.readable_output


def test_fetch_all_events_in_second_fetch():
    """
    Given:
        - A mock client with a predefined set of test events loaded from a JSON file.
        - The `max_events` is set to the total number of events available in the `test_events` list.
        - The `last_event_id` is set to 2051, indicating the starting point for the second fetch.

    When:
        - The `get_events_with_pagination` function is called to fetch events within the specified
        date range from "2024-11-22T00:00:00Z" to "2024-11-24T23:59:59Z", with the `last_event_id` set to 2051.

    Then:
        - Ensure that the number of events returned is equal to the total number of events in `test_events`
            excluding the first event (i.e., starting from event ID 2052).
        - Validate that the `next_skip` value is 0, indicating that there are no more events to fetch.
    """
    from BloodHoundEnterprise import get_events_with_pagination

    test_events = load_test_events()
    client = create_mock_client(test_events)

    max_events = len(test_events)
    last_event_id = 2051

    events, next_skip = get_events_with_pagination(
        client=client,
        start_date="2024-11-22T00:00:00Z",
        end_date="2024-11-24T23:59:59Z",
        max_events=max_events,
        last_event_id=last_event_id,
    )

    assert len(events) == len(test_events[1:])
    assert next_skip == 0


def test_fetch_limited_events():
    """
    Given:
        - A mock client with a predefined set of test events loaded from a JSON file.
        - The `max_events` parameter is set to 10, limiting the number of events to fetch.
        - The date range for fetching events is from "2024-11-22T00:00:00Z" to "2024-11-24T23:59:59Z".

    When:
        - The `get_events_with_pagination` function is called to fetch a limited number of events within the specified date range.

    Then:
        - Ensure that the number of events returned matches the `max_events` limit, which should be 10.
        - Validate that the `next_skip` value is equal to `max_events` (10), indicating the number of events fetched.
    """
    from BloodHoundEnterprise import get_events_with_pagination

    test_events = load_test_events()
    client = create_mock_client(test_events)

    max_events = 10
    events, next_skip = get_events_with_pagination(
        client=client,
        start_date="2024-11-22T00:00:00Z",
        end_date="2024-11-24T23:59:59Z",
        max_events=max_events,
    )

    assert len(events) == max_events
    assert next_skip == max_events


def test_pagination_with_initial_skip():
    """
    Given:
        - A mock client with a predefined set of test events loaded from a JSON file.
        - The `initial_skip` parameter is set to 5, meaning the first 5 events should be skipped.
        - The `max_events` parameter is set to 10, limiting the number of events to fetch.
        - The date range for fetching events is from "2024-11-22T00:00:00Z" to "2024-11-24T23:59:59Z".

    When:
        - The `get_events_with_pagination` function is called to fetch a limited number
        of events (10) starting after the initial skip of 5.

    Then:
        - Verify that the first event returned matches the event after the initial skip
        (i.e., `events[0]['id']` should equal the ID of the event at `initial_skip` index).
        - Ensure that the number of events returned matches the `max_events` limit, which should be 10.
        - Confirm that the `next_skip` value equals `initial_skip + max_events`, indicating the total number of events processed.
    """
    from BloodHoundEnterprise import get_events_with_pagination

    test_events = load_test_events()
    client = create_mock_client(test_events)

    initial_skip = 5
    max_events = 10
    events, next_skip = get_events_with_pagination(
        client=client,
        start_date="2024-11-22T00:00:00Z",
        end_date="2024-11-24T23:59:59Z",
        max_events=max_events,
        offset=initial_skip,
    )

    assert events[0]["id"] == test_events[initial_skip]["id"]
    assert len(events) == max_events
    assert next_skip == initial_skip + max_events


def test_fetch_with_last_event_id():
    """
    Given:
        - A mock client with a predefined set of test events loaded from a JSON file.
        - The `last_event_id` parameter is set to 2060, meaning events with an ID less than or equal to 2060 should be excluded.
        - The `max_events` parameter is set to 5, limiting the number of events to fetch.
        - The date range for fetching events is from "2024-11-22T00:00:00Z" to "2024-11-24T23:59:59Z".

    When:
        - The `get_events_with_pagination` function is called with the
        specified `last_event_id` to fetch events that have a higher ID than 2060.

    Then:
        - Verify that all returned events have an `id` greater than 2060.
        - Ensure that the number of events fetched matches the `max_events` limit, which is 5.
        - Confirm that the first event in the returned list has an `id` greater than `last_event_id` (2060).
    """
    from BloodHoundEnterprise import get_events_with_pagination

    test_events = load_test_events()
    client = create_mock_client(test_events)

    last_event_id = 2060
    max_events = 5
    events, next_skip = get_events_with_pagination(
        client=client,
        start_date="2024-11-22T00:00:00Z",
        end_date="2024-11-24T23:59:59Z",
        max_events=max_events,
        last_event_id=last_event_id,
    )

    assert all(event["id"] > last_event_id for event in events)
    assert len(events) == max_events
    assert events[0]["id"] > last_event_id