NetBox Event Collector

NetBox event collector integration for Cortex XSIAM.

Analytics & SIEM · NetBox

Details

IDNetBox Event Collector
ProviderNetBoxLabs
CategoryAnalytics & SIEM
From Version6.8.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesXSIAM

README

This is the NetBox event collector integration for Cortex XSIAM.
This integration was integrated and tested with version 3.0 and above of NetBox API.

Configure NetBox Event Collector in Cortex

Parameter Required
Server URL (e.g., https://www.example.com) True
API Key True
First fetch time False
The maximum number of alerts per fetch False
Trust any certificate (not secure) False
Use system proxy settings 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.

netbox-get-events


Gets events from NetBox.

Base Command

netbox-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 number of results to return. Optional

Context Output

There is no context output for this command.

Command Example

!netbox-get-events should_push_events=true limit=1

Human Readable Output

journal-entries Events

assigned_object assigned_object_id assigned_object_type comments created created_by custom_fields display id kind last_updated tags url
id: 4
url: https://www.example.com/api/dcim/devices/4/
display: test3
name: test3
4 dcim.device   2022-12-04T14:33:52.067484Z 1   2022-12-04 14:33 (Info) 6 value: info
label: Info
2022-12-07T08:19:57.807055Z   https://www.example.com/api/extras/journal-entries/6/

object-changes Events

action changed_object changed_object_id changed_object_type display id postchange_data prechange_data request_id time url user user_name
value: update
label: Updated
id: 6
url: https://www.example.com/api/extras/journal-entries/6/
display: 2022-12-04 14:33 (Info)
created: 2022-12-04T14:33:52.067484Z
6 extras.journalentry extras | journal entry 2022-12-04 14:33 (Info) updated by netbox 10 kind: info
tags:
created: 2022-12-04T14:33:52.067Z
comments:
created_by: 1
last_updated: 2022-12-07T08:19:57.807Z
custom_fields: {}
assigned_object_id: 4
assigned_object_type: 25
kind:
tags:
created: 2022-12-04T14:33:52.067Z
comments:
created_by: 1
last_updated: 2022-12-04T14:33:52.067Z
custom_fields: {}
assigned_object_id: 4
assigned_object_type: 25
12345678-abcd-1234-abcd-1234567890ab 2022-12-07T08:19:57.810348Z https://www.example.com/api/extras/object-changes/10/ id: 1
url: https://www.example.com/api/users/users/1/
display: netbox
username: netbox
netbox

Configuration parameters

  • url — Server URL (e.g., https://www.example.com) (required)
  • credentials — (required)
  • first_fetch — First fetch time
  • max_fetch — The maximum number of alerts per fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • netbox-get-events

    Gets events from NetBox.

import demistomock as demisto
from CommonServerPython import *
import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """

DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
VENDOR = "NetBox"
PRODUCT = "IRM"

LOG_TYPES = ["journal-entries", "object-changes"]
DEFAULT_LIMIT = "1000"

""" CLIENT CLASS """


class Client(BaseClient):
    """
    Client class to interact with the service API
    """

    def http_request(self, url_suffix=None, full_url=None, params=None):
        return self._http_request(method="GET", url_suffix=url_suffix, full_url=full_url, params=params)

    def search_events(
        self, url_suffix: str, limit: int, prev_id: int = 0, ordering: str = ""
    ) -> tuple[int, list[dict[str, Any]]]:
        """
        Searches for NetBox alerts using the '/<url_suffix>' API endpoint.
        Args:
            url_suffix: str, The API endpoint to request.
            limit: int, the limit of the results to return.
            prev_id: int, The id of the first event to fetch.
            ordering: str, The ordering of the results to return.
        Returns:
            int: The id of the next event to fetch.
            list: A list containing the events
        """
        next_id = prev_id
        results: list[dict] = []

        next_page = True
        params = {
            "limit": limit,
            "ordering": ordering,
            "id__gte": next_id,
        }

        while next_page and len(results) < limit:
            full_url = next_page if type(next_page) is str else ""
            response = self.http_request(url_suffix=url_suffix, full_url=full_url, params=params)

            results += response.get("results", [])

            next_page = response.get("next")
            params = {}

            if results:
                next_id = results[-1]["id"] + 1

        return next_id, results[:limit]

    def get_first_fetch_id(self, url_suffix, params):
        """
        Sets the first fetch id for log type.
        Args:
            url_suffix: str, the log type to fetch.
            params: dict, the params to send to the API.
        Returns:
            int: The first id to fetch.
        """
        first_log = self.http_request(url_suffix=url_suffix, params={"ordering": "id", "limit": 1} | params)

        if first_log.get("results"):
            next_run = first_log.get("results")[0].get("id")
        else:
            next_run = None

        return next_run


def add_time_key_to_events(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
    """
    Adds the _time key to the events.
    Args:
        events: list, the events to add the time key to.
    Returns:
        list: The events with the _time key.
    """
    for event in events:
        if event.get("created"):
            event["_time"] = event.get("created")
        elif event.get("time"):
            event["_time"] = event.get("time")

    return events


def test_module_command(client: Client) -> str:
    """
    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): NetBox client to use.
    Returns:
        str: 'ok' if test passed, anything else will raise an exception and will fail the test.
    """

    try:
        client.search_events(url_suffix=LOG_TYPES[0], limit=1)

    except Exception as e:
        if "Forbidden" in str(e):
            return "Authorization Error: make sure API Key is correctly set"
        else:
            raise e

    return "ok"


def get_events_command(client: Client, limit: int) -> tuple[list[dict[str, Any]], CommandResults]:
    """
    Gets all the events from the NetBox API for each log type.
    Args:
        client (Client): NetBox client to use.
        limit: int, the limit of the results to return per log_type.
    Returns:
        list: A list containing the events
        CommandResults: A CommandResults object that contains the events in a table format.
    """
    events: list[dict] = []
    hr = ""
    for log_type in LOG_TYPES:
        _, events_ = client.search_events(url_suffix=log_type, limit=limit)
        if events_:
            hr += tableToMarkdown(name=f"{log_type} Events", t=events_)
            events += events_
        else:
            hr = f"No events found for {log_type}."

    return events, CommandResults(readable_output=hr)


def fetch_events_command(
    client: Client, max_fetch: int, last_run: dict[str, int], first_fetch_time: str
) -> tuple[dict[str, int], list[dict[str, Any]]]:
    """
    Args:
        client (Client): NetBox client to use.
        max_fetch (int): The maximum number of events to fetch per log type.
        last_run (dict): A dict with a keys containing the first event id to fetch for each log type.
        first_fetch_time (str): In case of first fetch, fetch events from this date.
    Returns:
        dict: Next run dictionary containing the ids of the next events to fetch.
        list: List of events that will be created in XSIAM.
    """
    # In the first fetch, get the ids for the first fetch time
    params = {
        "journal-entries": {"created_after": first_fetch_time},
        "object-changes": {"time_after": first_fetch_time},
    }
    for log_type in LOG_TYPES:
        if last_run.get(log_type) is None:
            last_run[log_type] = client.get_first_fetch_id(url_suffix=log_type, params=params[log_type])

    next_run = last_run.copy()
    events = []

    for log_type in LOG_TYPES:
        if last_run[log_type] is None:
            continue
        next_run[log_type], events_ = client.search_events(
            url_suffix=log_type,
            limit=max_fetch,
            ordering="id",
            prev_id=last_run[log_type],
        )
        events += events_

    demisto.info(f'Fetched events with ids: {", ".join(f"{log_type}: {id_}" for log_type, id_ in last_run.items())}.')

    # Save the next_run as a dict with the last_fetch key to be stored
    demisto.info(f'Setting next run with ids: {", ".join(f"{log_type}: {id_}" for log_type, id_ in next_run.items())}.')
    return next_run, events


""" MAIN FUNCTION """


def main() -> None:  # pragma: no cover
    """
    main function, parses params and runs command functions
    """

    params = demisto.params()
    args = demisto.args()
    command = demisto.command()
    api_key = params.get("credentials", {}).get("password")
    base_url = urljoin(params.get("url"), "/api/extras")
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)

    # How much time before the first fetch to retrieve events
    first_fetch_time: datetime = arg_to_datetime(
        arg=params.get("first_fetch", "3 days"),
        arg_name="First fetch time",
        required=True,
    )  # type: ignore   # datetime.datetime(2022, 1, 1, 00, 00, 00, 0)
    first_fetch_time_strftime = first_fetch_time.strftime(DATE_FORMAT)  # 2022-01-01T00:00:00Z

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

        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            result = test_module_command(client)
            return_results(result)

        elif command == "netbox-get-events":
            should_push_events = argToBoolean(args.get("should_push_events"))
            events, results = get_events_command(
                client,
                limit=arg_to_number(args.get("limit", DEFAULT_LIMIT)),  # type: ignore
            )
            return_results(results)

            if should_push_events:
                events = add_time_key_to_events(events)
                send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)

        elif command == "fetch-events":
            last_run = demisto.getLastRun()
            next_run, events = fetch_events_command(
                client=client,
                max_fetch=arg_to_number(params.get("max_fetch", DEFAULT_LIMIT)),  # type: ignore
                last_run=last_run,
                first_fetch_time=first_fetch_time_strftime,
            )

            events = add_time_key_to_events(events)
            send_events_to_xsiam(events, vendor=VENDOR, product=PRODUCT)
            # saves next_run for the time fetch-events is invoked
            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()