RecordedFutureEventCollector

This integration fetches alerts from Recorded Future.

Analytics & SIEM · Recorded Future Intelligence

Details

IDRecordedFutureEventCollector
ProviderMastercard
CategoryAnalytics & SIEM
From Version6.8.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

This integration fetches alerts from Recorded Future.
This integration was integrated and tested with version 2 of the Recorded Future API.

This is the default integration for this content pack when configured by the Data Onboarder in Cortex XSIAM.

Configure Recorded Future Event Collector in Cortex

Parameter Description Required
API token The API token to use for the connection. True
Trust any certificate (not secure) Use SSL secure connection or not. False
Use system proxy settings Use proxy settings for connection or not. False
First fetch time First fetch query <number> <time unit>, e.g., 7 days. Default 3 days. False
Max fetch The maximum number of events per fetch. Default and maximum is 1000. 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.

recorded-future-get-events


Gets events from Recorded Future.

Base Command

recorded-future-get-events

Input

Argument Name Description Required
should_push_events If true, the command will create events, otherwise it will only display them. Possible values are: true, false. Default is false. Required
limit Maximum results to return. Default is 10. Optional

Context Output

There is no context output for this command.

Configuration parameters

  • credentials — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • first_fetch — First fetch time
  • max_fetch — The maximum number of events per fetch. Default and maximum is 1000.

Commands (1)

  • recorded-future-get-events

    Gets events from Recorded Future.

from CommonServerPython import *
import json
import urllib3
from typing import Any

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """
BASE_URL = "https://api.recordedfuture.com/gw/xsiam"
STATUS_TO_RETRY = [500, 501, 502, 503, 504]
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
VENDOR = "Recorded Future"
PRODUCT = "Intelligence Cloud"

""" CLIENT CLASS """


class Client(BaseClient):
    def _call(self, url_suffix, **kwargs):
        request_kwargs = {
            "method": "get",
            "url_suffix": url_suffix,
            "timeout": 90,
            "retries": 3,
            "status_list_to_retry": STATUS_TO_RETRY,
        }
        request_kwargs.update(kwargs)

        return self._http_request(**request_kwargs)

    def test_connection(self) -> dict[str, Any]:
        """Check connection."""
        return self._call(url_suffix="/config/info")

    def get_alerts(self, params: dict = None) -> dict[str, Any]:
        """Get alerts."""
        return self._call(url_suffix="/alert/search", params=params)


""" COMMAND FUNCTIONS """


def test_module(client: Client):
    """
    Tests API connectivity and authentication'
    When 'ok' is returned it indicates the integration works like it is supposed to and connection to the service is
    successful.
    Raises exceptions if something goes wrong.

    Args:
        client (Client): RecordedFuture client to use.

    Returns:
        str: 'ok' if test passed, anything else will raise an exception and will fail the test.
    """

    try:
        client.test_connection()
        return_results("ok")
    except Exception as err:
        message = str(err)
        try:
            error = json.loads(str(err).split("\n")[1])
            if "fail" in error.get("result", {}).get("status", ""):
                message = error.get("result", {})["message"]
        except Exception:
            message = f"Unknown error. Please verify that the API URL and Token are correctly configured. RAW Error: {err}"
        raise DemistoException(f"Failed due to - {message}")


def get_events(client, params: dict) -> list:
    """
        Retrieves events using the RecordedFuture API.
    Args:
        client (Client): RecordedFuture client to use.
        params (dict): The params to send to the API basically contains the limit for example {'limit': '1000'}.

    Returns:
        list: (list) of events that will be created in XSIAM.
    """

    result = client.get_alerts(params)
    events = result.get("data", [])

    hr = tableToMarkdown(name="Test Event", t=events)
    return_results(CommandResults(readable_output=hr, raw_response=events))

    return events


def get_triggered(event: dict) -> str:
    """Get the 'triggered' value from an event without milliseconds since the API ignores them.

    Args:
        event (dict): The event from API.

    Returns:
        str: the "triggered" value.
    """
    if event:
        return event.get("log", {}).get("triggered", "").split(".")[0]
    return ""


def fetch_events(client: Client, **kwargs) -> tuple[list, dict]:
    """
    Args:
        client (Client): RecordedFuture client to use.

    Returns:
        list: (list) of events that will be created in XSIAM.
        dict: The lastRun object to save for next run.
    """
    params = {
        "triggered": f'[{kwargs.get("last_run")},]',
        "orderby": "triggered",
        "direction": "asc",
        "limit": kwargs.get("limit"),
    }
    response = client.get_alerts(params)

    next_run = {}
    if events := response.get("data", []):
        # Obtain the latest triggered time (for the next fetch round)
        next_run_time = get_triggered(events[0])

        # We need the IDs of the events with the same trigger time as the latest,
        # So that we can remove them in the next fetch, Since we are fetching from (including) this time.
        next_run_ids = {event.get("id") for event in events if get_triggered(event) == next_run_time}

        # In case all events were triggered at the same time and the limit equals their amount,
        # We should increase the next run time, Otherwise the fetch will get stuck at this time forever.
        if len(next_run_ids) == int(kwargs.get("limit")):  # type: ignore
            next_run_time = (datetime.strptime(next_run_time, DATE_FORMAT) + timedelta(seconds=1)).strftime(DATE_FORMAT)

        # Filter out events that have already been fetched.
        if last_run_event_ids := demisto.getLastRun().get("last_run_ids"):
            demisto.info(f"this is the last_run_event_ids {last_run_event_ids}")
            events = list(filter(lambda x: x.get("id") not in last_run_event_ids, events))

        next_run = {"last_run_time": next_run_time, "last_run_ids": list(next_run_ids)}

    return events, next_run


""" HELPER FUNCTIONS """


def add_time_key_to_events(events: list = None):
    """
    Adds the _time key to the events.
    Args:
        events: list, the events to add the time key to.
    """
    for event in events or []:
        event["_time"] = demisto.get(event, "log.triggered")


""" MAIN FUNCTION """


def main() -> None:
    """
    main function, parses params and runs command functions
    """
    params = demisto.params()
    args = demisto.args()
    command = demisto.command()
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)
    api_key = params.get("credentials", {}).get("password")
    headers = {"X-RFToken": api_key}

    demisto.info(f"Command being called is {command}")
    try:
        client = Client(base_url=BASE_URL, headers=headers, verify=verify_certificate, proxy=proxy)

        if command == "test-module":
            test_module(client)

        elif command == "recorded-future-get-events":
            events = get_events(client, params={"limit": args.get("limit", 10)})
            if argToBoolean(args.get("should_push_events", False)):
                add_time_key_to_events(events)
                send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)

        elif command == "fetch-events":
            if not (last_run := demisto.getLastRun().get("last_run_time")):
                last_run = arg_to_datetime(params.get("first_fetch", "3 days")).strftime(DATE_FORMAT)  # type: ignore
            events, next_run = fetch_events(
                client=client, limit=args.get("limit") or params.get("max_fetch") or 1000, last_run=last_run
            )

            add_time_key_to_events(events)
            send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)
            if next_run:
                demisto.setLastRun(next_run)

    # Log exceptions and return errors
    except Exception as e:
        return_error(f"Failed to execute {command} command.\nError:\n{str(e)}")


""" ENTRY POINT """

if __name__ in ("__main__", "__builtin__", "builtins"):
    main()