AdminByRequest

AdminByRequest is a Privileged Access Management (PAM) solution that enables secure, temporary elevation to local admin rights.

Analytics & SIEM · Admin By Request

Details

IDAdminByRequest
ProviderAdmin By Request
CategoryAnalytics & SIEM
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

AdminByRequest is a Privileged Access Management (PAM) solution that enables secure, temporary elevation to local admin rights.

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

Configure Admin By Request in Cortex

Parameter Description Required
Server URL   True
API Key The API Key allows you to interact with the AdminByRequest API service. True
Trust any certificate (not secure)   False
Use system proxy settings   False
Fetch events   False
Event types to fetch Which records the integration should fetch from the AdminByRequest API. Available for Auditlogs, Events, and Requests. True
Maximum number of Auditlog per fetch Maximum number of audit log entries to retrieve per fetch cycle. Applies only if the “Auditlog” event type is enabled for fetching. False
Maximum number of Events per fetch Maximum number of event entries to retrieve per fetch cycle. Applies only if the “Events” event type is enabled for fetching. False
Maximum number of Requests per fetch Maximum number of request entries to retrieve per fetch cycle. Applies only if the “Requests” event type is enabled for fetching. 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.

adminbyrequest-get-events


Retrieves a list of entries logs events from the AdminByRequest instance.

Base Command

adminbyrequest-get-events

Input

Argument Name Description Required
should_push_events Set this argument to ‘true’ in order to create events, otherwise it will only display them. Possible values are: true, false. Default is false. Required
event_type The type of event to fetch. Default is Auditlog. Optional
limit Returns no more than the specified number of events (for entries of type ‘Requests’ the default value is 5000). Optional
first_fetch The UTC date or relative timestamp from when to start fetching incidents. Notice that for event type ‘Requests’ there is the option to set a start date. Supported formats: N days, N weeks, N months, N years, yyyy-mm-dd. Optional

Context Output

There is no context output for this command.

API Limitations

  • Please DO NOT consistently use a high “limit” number or flood the API. The account will be automatically throttled.
  • Daily quota: 100,000 API calls (approximately 60 calls per minute maximum).

adminbyrequest-list-requests


Lists requests from AdminByRequest.

Base Command

adminbyrequest-list-requests

Input

Argument Name Description Required
request_id The ID of a specific request to retrieve. Optional
status Filters requests by status. Possible values are: Pending, Open, Approved, Denied, Quarantined. Optional
want_scan_details Set to true to include scan details in the response. Possible values are: true, false. Optional
limit The maximum number of requests to return. Default is 50. Optional
all_results Set to true to fetch all available results, overriding the limit. Possible values are: true, false. Optional

Context Output

Path Type Description
AdminByRequest.Request.id Number The ID of the request.
AdminByRequest.Request.type String The type of the request.
AdminByRequest.Request.settingsName String The name of the settings.
AdminByRequest.Request.application.name String The name of the application.
AdminByRequest.Request.application.scanResult String The scan result of the application.
AdminByRequest.Request.user Unknown The user associated with the request.
AdminByRequest.Request.computer.name String The name of the computer.
AdminByRequest.Request.status String The status of the request.
AdminByRequest.Request.reason String The reason for the request.
AdminByRequest.Request.approvedBy String The user who approved the request.
AdminByRequest.Request.approvedByEmail String The email of the user who approved the request.
AdminByRequest.Request.deniedReason String The reason for denying the request.
AdminByRequest.Request.deniedBy String The user who denied the request.
AdminByRequest.Request.deniedByEmail String The email of the user who denied the request.
AdminByRequest.Request.requestTime Date The time the request was made.
AdminByRequest.Request.startTime Date The start time of the request.
AdminByRequest.Request.eventText String The text of the request.
AdminByRequest.Request.eventTime Date The time the request occurred.

adminbyrequest-request-deny


Denies a request in AdminByRequest.

Base Command

adminbyrequest-request-deny

Input

Argument Name Description Required
request_id The ID of the request to deny. Required
denied_by The user who denied the request. Optional
reason The reason for denying the request. Optional

Context Output

There is no context output for this command.

adminbyrequest-request-approve


Approves a request in AdminByRequest.

Base Command

adminbyrequest-request-approve

Input

Argument Name Description Required
request_id The ID of the request to approve. Required
approved_by The user who approved the request. Optional

Context Output

There is no context output for this command.

Configuration parameters

  • url — Server URL (required)
  • credentials — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetchEvents — Fetch events
  • event_types_to_fetch — Event types to fetch (required)
  • max_auditlog_per_fetch — Maximum number of Auditlog per fetch
  • max_events_per_fetch — Maximum number of Events per fetch
  • max_requests_per_fetch — Maximum number of Requests per fetch

Commands (4)

  • adminbyrequest-get-events

    Retrieves a list of entry log events from the AdminByRequest instance.

  • adminbyrequest-list-requests

    Lists requests from AdminByRequest.

  • adminbyrequest-request-approve

    Approves a request in AdminByRequest.

  • adminbyrequest-request-deny

    Denies a request in AdminByRequest.

import pytest
from CommonServerPython import *
from freezegun import freeze_time

import json

MOCK_BASEURL = "https://example.com"
MOCK_API = "api_key"

from AdminByRequestEventCollector import (
    deny_request_command,
    approve_request_command,
    list_requests_command,
    Client,
    EventType,
    remove_first_run_params,
    validate_fetch_events_params,
    set_event_type_fetch_limit,
    fetch_events_list,
    fetch_events,
    get_events,
    EVENT_TYPES,
    prepare_list_output,
)


@pytest.fixture
def client():
    """
    A dummy client fixture for testing.
    """
    return Client(
        base_url=MOCK_BASEURL,
        api_key=MOCK_API,
        verify=False,
        use_proxy=False,
    )


def util_load_json(path):
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


class TestHelperFunction:
    audit_log_event = EVENT_TYPES["Auditlog"]
    events_event = EVENT_TYPES["Events"]
    requests_event = EVENT_TYPES["Requests"]

    @pytest.mark.parametrize(
        "input_params, expected_params",
        [
            ({"startdate": "2024-01-01", "enddate": "2024-01-31", "other": 1}, {"other": 1}),
            ({"startdate": "2024-01-01", "other": 1}, {"other": 1}),
            ({"enddate": "2024-01-31", "other": 1}, {"other": 1}),
            ({"other": 1}, {"other": 1}),
            # Case 5: empty dict
            ({}, {}),
        ],
    )
    def test_remove_first_run_params(self, input_params: Dict[str, Any], expected_params: Dict[str, Any]):
        """
        Test remove_first_run_params function behavior on initial fetch.

        Given:
            - Dictionary Containing params from "last_run"

        When:
            - Checking if to update the params or not
        Then:
            - Make sure the correct params are coming back - not containing "first run" params"
        """
        # make a copy to avoid modifying original test data
        params_copy = input_params.copy()
        remove_first_run_params(params_copy)
        assert params_copy == expected_params

    def test_prepare_list_output(self):
        """
        Given: A list of records.
        When: Calling the prepare_list_output function.
        Then: Ensure the output is a markdown table as expected.
        """
        records = [
            {"id": "1", "status": "open", "user": "test"},
            {"id": "2", "status": "closed", "user": "test"},
        ]
        output = prepare_list_output(records)
        assert "|ID|Status|User|" in output
        assert "|---|---|---|" in output
        assert "| 1 | open | test |" in output
        assert "| 2 | closed | test |" in output

        output = prepare_list_output([])
        assert "No entries." in output

    result_param = {"startid": 1, "take": 1}

    case1_validate_fetch_events_params = (
        ({"start_id_auditlog": 1}, audit_log_event, False),
        ({**audit_log_event.default_params, **result_param}, "auditlog", "start_id_auditlog"),
    )
    case2_validate_fetch_events_params = (
        ({"start_id_events": 1}, events_event, False),
        ({**events_event.default_params, **result_param}, "events", "start_id_events"),
    )
    case3_validate_fetch_events_params = (
        ({"start_id_requests": 1}, requests_event, False),
        ({**requests_event.default_params, **result_param}, "requests", "start_id_requests"),
    )

    # cases where we dont have key then use date 2025-01-01 01:00:00
    date_params = {"startdate": "2025-01-01", "enddate": "2025-01-01"}
    result_param_no_start_id = {"take": 1}

    case4_validate_fetch_events_params = (
        ({}, audit_log_event, False),
        ({**date_params, **result_param_no_start_id}, "auditlog", "start_id_auditlog"),
    )
    case5_validate_fetch_events_params = (
        ({}, events_event, False),
        ({**date_params, **result_param_no_start_id}, "events", "start_id_events"),
    )
    case6_validate_fetch_events_params = (
        ({}, requests_event, False),
        ({**requests_event.default_params, **result_param_no_start_id}, "requests", "start_id_requests"),
    )

    # cases where we dont have key then use date 2025-01-01 01:00:00
    last_run_start_date = {"startdate": "2025-01-01"}

    # using
    case7_validate_fetch_events_params = (
        (last_run_start_date, audit_log_event, True),
        ({**last_run_start_date, **result_param_no_start_id}, "auditlog", "start_id_auditlog"),
    )

    @pytest.mark.parametrize(
        "input_params, expected_results",
        [
            case1_validate_fetch_events_params,
            case2_validate_fetch_events_params,
            case3_validate_fetch_events_params,
            case4_validate_fetch_events_params,
            case5_validate_fetch_events_params,
            case6_validate_fetch_events_params,
            case7_validate_fetch_events_params,
        ],
    )
    @freeze_time("2025-01-01 01:00:00")
    def test_validate_fetch_events_params(
        self, input_params: tuple[dict, EventType, bool], expected_results: tuple[dict, str, str]
    ) -> None:
        """
        Test validate_fetch_events_params function behavior on initial fetch.

        Given:
            - An input params containing params from "last_run", event type and a flag mention should
             we use lat run as params.

        When:
            - Validating params before fetch action.
        Then:
            - Make sure the request is sent with right parameters.
        """
        results = validate_fetch_events_params(*input_params)
        assert results == expected_results

    case1_set_event_type_fetch_limit = (
        {
            "event_types_to_fetch": ["Auditlog", "Events", "Requests"],
            "max_auditlog_per_fetch": 50000,
            "max_events_per_fetch": 50000,
            "max_requests_per_fetch": 5000,
        },
        3,
        (50000, 50000, 5000),
    )
    case2_set_event_type_fetch_limit = (
        {
            "event_types_to_fetch": ["Auditlog", "Events"],
            "max_auditlog_per_fetch": 50000,
            "max_events_per_fetch": 50000,
            "max_requests_per_fetch": 5000,
        },
        2,
        (50000, 50000),
    )
    case3_set_event_type_fetch_limit = (
        {
            "event_types_to_fetch": ["Auditlog", "Requests"],
            "max_auditlog_per_fetch": 50000,
            "max_events_per_fetch": 10,
            "max_requests_per_fetch": 10,
        },
        2,
        (50000, 10),
    )
    case4_set_event_type_fetch_limit = (
        {
            "event_types_to_fetch": [],
            "max_auditlog_per_fetch": 50000,
            "max_events_per_fetch": 50000,
            "max_requests_per_fetch": 5000,
        },
        0,
        (),
    )

    @pytest.mark.parametrize(
        "input_params, expected_len, expected_limits",
        [
            case1_set_event_type_fetch_limit,
            case2_set_event_type_fetch_limit,
            case3_set_event_type_fetch_limit,
            case4_set_event_type_fetch_limit,
        ],
    )
    def test_set_event_type_fetch_limit(
        self, input_params: Dict[str, Any], expected_len: int, expected_limits: tuple[int, int, int]
    ) -> None:
        """
        Test set_event_type_fetch_limit function behavior.

        Given:
            - An input params containing integration params from integration configuration.

        When:
            - VUpdating Event types before making a fetch action
        Then:
            - Make dure each event type has the correct max_fetch limit.
        """
        event_types = set_event_type_fetch_limit(input_params)
        assert len(event_types) == expected_len
        for i in range(expected_len):
            assert event_types[i].max_fetch == expected_limits[i]


class TestFetchEvents:
    event_requests = EVENT_TYPES["Requests"]
    event_events = EVENT_TYPES["Events"]
    event_audit = EVENT_TYPES["Auditlog"]

    raw_detections_audit = util_load_json("test_data/auditlogs_response.json")
    raw_detections_events = util_load_json("test_data/events_response.json")
    raw_detections_requests = util_load_json("test_data/requests_response.json")

    def test_fetch_events_update_last_run(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events.
        Then: Make sure that the last run object was updated as expected
        """
        self.event_audit.max_fetch = 3
        raw_detections = self.raw_detections_audit
        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", return_value=raw_detections)
        last_run = {}

        output = fetch_events_list(client, last_run=last_run, event_type=self.event_audit, use_last_run_as_params=False)

        assert len(output) == 3
        assert last_run.get("start_id_auditlog") == raw_detections[-1]["id"] + 1

    def test_fetch_events_update_last_run_with_old_params(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events.
        Then: Make sure that the last run object was updated as expected
        """
        self.event_audit.max_fetch = 3
        raw_detections = self.raw_detections_audit
        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", return_value=raw_detections)
        last_run = {"start_id_auditlog": 1, "start_id_events": 1, "start_id_requests": 1}

        output = fetch_events_list(client, last_run=last_run, event_type=self.event_audit, use_last_run_as_params=False)

        assert len(output) == 3
        assert last_run.get("start_id_auditlog") == raw_detections[-1]["id"] + 1
        assert last_run.get("start_id_events") == 1
        assert last_run.get("start_id_requests") == 1

    def test_fetch_response_bigger_then_limit(self, client, mocker):
        """
        Given: A mock raw response containing events.
        When: Fetching events with a fetch limit smaller than the number of available logs.
        Then: Ensure the function returns exactly the requested number of events and updates the last run timestamp correctly.
        """
        self.event_events.max_fetch = 2
        raw_detections = self.raw_detections_events
        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", return_value=raw_detections)
        last_run = {"start_id_auditlog": 1, "start_id_events": 1, "start_id_requests": 1}

        output = fetch_events_list(client, last_run=last_run, event_type=self.event_events, use_last_run_as_params=False)

        assert len(output) == 2
        assert last_run.get("start_id_events") == raw_detections[-1]["id"]

    def test_fetch_limit_bigger_then_response(self, client, mocker):
        """
        Given: A mock raw response containing events.
        When: Fetching events with a fetch limit higher than the number of available logs.
        Then: Ensure the function returns exactly the requested number of events and updates the last run timestamp correctly.
        """
        self.event_requests.max_fetch = 3
        raw_detections = self.raw_detections_requests[:-1]
        first_response = raw_detections
        second_response = []

        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", side_effect=[first_response, second_response])
        last_run = {"start_id_auditlog": 1, "start_id_events": 1, "start_id_requests": 1}

        output = fetch_events_list(client, last_run=last_run, event_type=self.event_requests, use_last_run_as_params=False)

        assert len(output) == len(raw_detections)
        assert last_run.get("start_id_requests") == raw_detections[-1]["id"] + 1

    def test_fetch_all_types(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events from all types of EventType
        Then: Ensure the function returns exactly the requested number of events and updates the last run correctly.
        """
        self.event_requests.max_fetch = 3
        self.event_events.max_fetch = 3
        self.event_audit.max_fetch = 3

        raw_detections = self.raw_detections_audit + self.raw_detections_events + self.raw_detections_requests

        first_response = self.raw_detections_audit
        second_response = self.raw_detections_events
        third_response = self.raw_detections_requests

        events_types = [self.event_audit, self.event_events, self.event_requests]

        mocker.patch(
            "AdminByRequestEventCollector.Client.get_events_request",
            side_effect=[first_response, second_response, third_response],
        )

        output, last_run = fetch_events(client, last_run={}, fetch_events_types=events_types, use_last_run_as_params=False)

        assert len(output) == len(raw_detections)
        assert last_run.get("start_id_auditlog") == self.raw_detections_audit[-1]["id"] + 1
        assert last_run.get("start_id_events") == self.raw_detections_events[-1]["id"] + 1
        assert last_run.get("start_id_requests") == self.raw_detections_requests[-1]["id"] + 1

    def test_fetch_all_types_different_lengths(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events from all types of EventType
        Then: Ensure the function returns exactly the requested number of events and updates the last run correctly.
        """
        self.event_audit.max_fetch = 3
        self.event_events.max_fetch = 2
        self.event_requests.max_fetch = 1

        raw_detections = self.raw_detections_audit + self.raw_detections_events[:-1] + self.raw_detections_requests[:-2]

        first_response = self.raw_detections_audit
        second_response = self.raw_detections_events
        third_response = self.raw_detections_requests

        events_types = [self.event_audit, self.event_events, self.event_requests]

        mocker.patch(
            "AdminByRequestEventCollector.Client.get_events_request",
            side_effect=[first_response, second_response, third_response],
        )

        output, last_run = fetch_events(client, last_run={}, fetch_events_types=events_types, use_last_run_as_params=False)

        assert len(output) == len(raw_detections)
        assert last_run.get("start_id_auditlog") == self.raw_detections_audit[-1]["id"] + 1
        assert last_run.get("start_id_events") == self.raw_detections_events[-2]["id"] + 1
        assert last_run.get("start_id_requests") == self.raw_detections_requests[-3]["id"] + 1

    def test_fetch_all_types_field_values_audits(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events.
        Then: Make sure that the special XSIAM fields was updated as expected
        """
        self.event_audit.max_fetch = 3
        raw_detections = self.raw_detections_audit
        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", return_value=raw_detections)
        last_run = {}

        output = fetch_events_list(client, last_run=last_run, event_type=self.event_audit, use_last_run_as_params=False)

        for i in range(len(output)):
            assert output[i].get(self.event_audit.time_field) == raw_detections[i]["startTimeUTC"]
            assert output[i].get("source_log_type") == self.event_audit.source_log_type

    def test_fetch_all_types_field_values_events(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events.
        Then: Make sure that the special XSIAM fields was updated as expected
        """
        self.event_events.max_fetch = 3
        raw_detections = self.raw_detections_events
        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", return_value=raw_detections)
        last_run = {}

        output = fetch_events_list(client, last_run=last_run, event_type=self.event_events, use_last_run_as_params=False)

        for i in range(len(output)):
            assert output[i].get(self.event_events.time_field) == raw_detections[i]["eventTimeUTC"]
            assert output[i].get("source_log_type") == self.event_events.source_log_type

    def test_fetch_all_types_field_values_requests(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events.
        Then: Make sure that the special XSIAM fields was updated as expected
        """
        self.event_requests.max_fetch = 3
        raw_detections = self.raw_detections_requests
        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", return_value=raw_detections)
        last_run = {}

        output = fetch_events_list(client, last_run=last_run, event_type=self.event_requests, use_last_run_as_params=False)

        for i in range(len(output)):
            assert output[i].get(self.event_requests.time_field) == raw_detections[i]["requestTime"]
            assert output[i].get("source_log_type") == self.event_requests.source_log_type

    @freeze_time("2025-01-01 01:00:00")
    def test_list_requests_command(self, client, mocker):
        """
        Given: A mock raw response containing requests.
        When: fetching requests using the list_requests_command function.
        Then: Make sure that output is as expected.
        """
        raw_detections = self.raw_detections_requests
        args = {"limit": 2}
        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", return_value=raw_detections)
        output = list_requests_command(client, args=args)
        assert len(output.outputs) == len(raw_detections)
        assert output.outputs_prefix == "AdminByRequest.Request"

    def test_approve_request_command(self, client, mocker):
        """
        Given: A request ID and an approver email.
        When: Calling the approve_request_command function.
        Then: Ensure the client's approve_request method is called with the correct arguments and a success message is returned.
        """
        args = {"request_id": "12345", "approved_by": "test@example.com"}
        mock_response = mocker.Mock()
        mock_response.status_code = 204
        mocker.patch("AdminByRequestEventCollector.Client.approve_request", return_value=mock_response)

        result = approve_request_command(client, args)

        client.approve_request.assert_called_once_with("requests/12345", {"approvedby": "test@example.com"})
        assert result.readable_output == "Request with 12345 id was successfully approved."

    def test_deny_request_command(self, client, mocker):
        """
        Given: A request ID, a reason, and a denier email.
        When: Calling the deny_request_command function.
        Then: Ensure the client's deny_request method is called with the correct arguments and a success message is returned.
        """
        args = {"request_id": "12345", "reason": "Security risk", "denied_by": "test@example.com"}
        mock_response = mocker.Mock()
        mock_response.status_code = 204
        mocker.patch("AdminByRequestEventCollector.Client.deny_request", return_value=mock_response)

        result = deny_request_command(client, args)

        client.deny_request.assert_called_once_with("requests/12345", {"reason": "Security risk", "deniedby": "test@example.com"})
        assert result.readable_output == "Request with 12345 id was successfully denied."

    @freeze_time("2025-01-01 01:00:00")
    def test_get_events(self, client, mocker):
        """
        Given: A mock raw response containing audit logs.
        When: fetching events using the get events function.
        Then: Make sure that output is as expected.
        """

        raw_detections = self.raw_detections_events[:-1]

        first_response = self.raw_detections_events
        second_response = []

        args = {"limit": 2, "event_type": "Events"}

        mocker.patch("AdminByRequestEventCollector.Client.get_events_request", side_effect=[first_response, second_response])

        output = get_events(client, args=args)

        assert len(output.outputs) == len(raw_detections)
        assert output.outputs_prefix == "AdminByRequest." + "Events"


class TestCommandFunctions:
    def test_approve_request_command_fail(self, client, mocker):
        """
        Given: A request ID and an approver email.
        When: Calling the approve_request_command function and the API fails.
        Then: Ensure a DemistoException is raised.
        """
        args = {"request_id": "12345", "approved_by": "test@example.com"}
        mock_response = mocker.Mock()
        mock_response.status_code = 400
        mocker.patch("AdminByRequestEventCollector.Client.approve_request", return_value=mock_response)

        with pytest.raises(DemistoException) as e:
            approve_request_command(client, args)
        assert "Failed to approve request 12345. Status code: 400" in str(e.value)

    def test_approve_request_command_no_id(self, client):
        """
        Given: No request ID.
        When: Calling the approve_request_command function.
        Then: Ensure a ValueError is raised.
        """
        with pytest.raises(ValueError) as e:
            approve_request_command(client, {})
        assert "request_id is required" in str(e.value)

    def test_deny_request_command_fail(self, client, mocker):
        """
        Given: A request ID, a reason, and a denier email.
        When: Calling the deny_request_command function and the API fails.
        Then: Ensure a DemistoException is raised.
        """
        args = {"request_id": "12345", "reason": "Security risk", "denied_by": "test@example.com"}
        mock_response = mocker.Mock()
        mock_response.status_code = 400
        mocker.patch("AdminByRequestEventCollector.Client.deny_request", return_value=mock_response)

        with pytest.raises(DemistoException) as e:
            deny_request_command(client, args)
        assert "Failed to deny request 12345. Status code: 400" in str(e.value)

    def test_deny_request_command_no_id(self, client):
        """
        Given: No request ID.
        When: Calling the deny_request_command function.
        Then: Ensure a ValueError is raised.
        """
        with pytest.raises(ValueError) as e:
            deny_request_command(client, {})
        assert "request_id is required" in str(e.value)