AdminByRequest
AdminByRequest is a Privileged Access Management (PAM) solution that enables secure, temporary elevation to local admin rights.
Analytics & SIEM · Admin By Request
Details
| ID | AdminByRequest |
|---|---|
| Provider | Admin By Request |
| Category | Analytics & SIEM |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix 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 settingsisFetchEvents— Fetch eventsevent_types_to_fetch— Event types to fetch (required)max_auditlog_per_fetch— Maximum number of Auditlog per fetchmax_events_per_fetch— Maximum number of Events per fetchmax_requests_per_fetch— Maximum number of Requests per fetch
Commands (4)
-
adminbyrequest-get-eventsRetrieves a list of entry log events from the AdminByRequest instance.
-
adminbyrequest-list-requestsLists requests from AdminByRequest.
-
adminbyrequest-request-approveApproves a request in AdminByRequest.
-
adminbyrequest-request-denyDenies a request in AdminByRequest.
""" Event Collector Source file for AdminByRequest API. """ from typing import Any import re import demistomock as demisto import urllib3 from CommonServerPython import * from CommonServerUserPython import * # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ VENDOR = "Admin" PRODUCT = "By_Request" MAX_FETCH_AUDIT_LIMIT = 50000 MAX_FETCH_EVENT_LIMIT = 50000 MAX_FETCH_REQUEST_LIMIT = 5000 EVENTS_LIMIT = 100_000 DATE_FORMAT_CALLS = "%Y-%m-%d" class EventType: """ This class defines an AdminByRequest API Event - used to dynamically store different types of events data. """ def __init__(self, suffix: str, take: int, source_log_type: str, time_field: str, default_params: dict): """ Prepare constructor for EventType class. Args: suffix: The url suffix of AdminByRequest API endpoint. take: Maximum events to fetch per API call. source_log_type: Key name for "source_log_type" field mapping inside XSIAM time_field: Key name for "_TIME" field mapping inside XSIAM """ self.suffix = suffix self.take = take self.source_log_type = source_log_type self.time_field = time_field self.last_run_key = "start_id_" + suffix self.max_fetch = 1 self.default_params = default_params EVENT_TYPES: dict[str, EventType] = { "Auditlog": EventType( suffix="auditlog", take=10000, source_log_type="auditlog", time_field="startTimeUTC", default_params={} ), "Events": EventType(suffix="events", take=10000, source_log_type="events", time_field="eventTimeUTC", default_params={}), "Requests": EventType( suffix="requests", take=1000, source_log_type="request", time_field="requestTime", default_params={"wantscandetails": 1} ), } """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API""" def __init__(self, base_url: str, api_key: str, verify: bool, use_proxy: bool) -> None: """ Prepare constructor for Client class. Calls the constructor of BaseClient class and updates the header with the authentication token. Args: base_url: The url of AdminByRequest instance. api_key: The Api key for AdminByRequest API - specific for every licensing. verify: True if verify SSL certificate is checked in integration configuration, False otherwise. use_proxy: True if the proxy server needs to be used, False otherwise. """ super().__init__(base_url=base_url, verify=verify, proxy=use_proxy) self._api_key = api_key self._headers: dict[str, Any] = {"apiKey": self._api_key} def approve_request(self, url_suffix: str, headers: dict) -> requests.Response: """Approve a request.""" request_headers = self._headers.copy() request_headers.update(headers) return self._http_request("PUT", url_suffix=url_suffix, headers=request_headers, resp_type="response", ok_codes=(204,)) def deny_request(self, url_suffix: str, headers: dict) -> requests.Response: """Deny a request.""" request_headers = self._headers.copy() request_headers.update(headers) return self._http_request("DELETE", url_suffix=url_suffix, headers=request_headers, resp_type="response", ok_codes=(204,)) def get_events_request(self, url_suffix: str, params: dict) -> dict: """Retrieve the detections from AdminByRequest API.""" return self._http_request("GET", url_suffix=url_suffix, params=params, resp_type="json") """ HELPER FUNCTIONS """ def validate_email_address(email: str) -> bool: """ Validate an email address. Args: email (str): The email address to validate. Returns: bool: True if the email is valid, False otherwise. """ return re.fullmatch(emailRegex, email) is not None def remove_first_run_params(params: dict[str, Any]) -> None: """ Remove the "First Run" items from the param dictionary. Args: params (Dict[str, Any]): Integration parameters. """ if "startdate" in params: params.pop("startdate") if "enddate" in params: params.pop("enddate") def validate_fetch_events_params(last_run: dict, event_type: EventType, use_last_run_as_params: bool) -> tuple[dict, str, str]: """ Validate and update the params needed for the api call Args: last_run (dict): The last_run dictionary having the state of previous cycle. event_type (EventType): Event Type to fetch from API use_last_run_as_params (boolean): Flag that sign do we use the last-run as params for the API call Returns: Tuple[dict, str, str]: A tuple containing: - API call parameters. - URL suffix for the API endpoint. - Key used to update the `last_run` dictionary. """ suffix = event_type.suffix key = event_type.last_run_key if use_last_run_as_params: params = last_run elif key in last_run: # Not First fetch: Use last run's tracking ID as startid. params = {**event_type.default_params, "startid": last_run[key]} else: # First-time fetch: use today's date for time-based fetch (except for 'requests') today = get_current_time().strftime(DATE_FORMAT_CALLS) date_params = {} if suffix == "requests" else {"startdate": today, "enddate": today} params = {**event_type.default_params, **date_params} # Limit the number of records per fetch params["take"] = min(event_type.take, event_type.max_fetch) return params, suffix, key def fetch_events_list(client: Client, last_run: dict, event_type: EventType, use_last_run_as_params) -> list[dict[str, Any]]: """ Main Function that Handles the Fetch action to the API service of AdminByRequest. Args: client (Client): The client object used to interact with the AdminByRequest service. last_run (dict): The last_run dictionary having the state of previous cycle. event_type (EventType): Event Type to fetch from API use_last_run_as_params (bool): Flag that sign do we use the last-run as params for the API call Returns: list[dict[str, Any]]: List of records retrieved from the api call. """ params, suffix, last_run_key = validate_fetch_events_params(last_run, event_type, use_last_run_as_params) time_field, source_log_type = event_type.time_field, event_type.source_log_type fetch_limit = event_type.max_fetch last_id: int = 0 output: list[dict[str, Any]] = [] while True: try: # API call events = list(client.get_events_request(url_suffix=suffix, params=params)) except DemistoException as error: err_type = getattr(error, "exception", None) # If we have a Connection error with the server - return clean error message if isinstance(err_type, requests.exceptions.ConnectionError): clean_msg = str(error).split("\nError Type")[0] raise DemistoException(f"AdminByRequest: During fetch, exception occurred {clean_msg}") else: raise DemistoException(f"AdminByRequest: During fetch, exception occurred {str(error)}") if not events: break for event in events: # Updates each records in the list with _TIME and source_log_type fields # based on specific fields for each EventType. last_id = event["id"] event["_TIME"] = time_field event["source_log_type"] = source_log_type output.append(event) if len(output) >= fetch_limit: # update last run and return because we reach limit last_run.update({last_run_key: int(last_id + 1)}) return output # If it was the first run, we have a first run "params values" remove_first_run_params(params) params["startid"] = last_id + 1 # If we got at list one entity to add to output - update last ID if last_id: last_run.update({last_run_key: int(last_id + 1)}) return output def set_event_type_fetch_limit(params: dict[str, Any]) -> list[EventType]: """ Parses the event types to fetch from parameters and returns a dictionary mapping each selected event type's suffix to its corresponding max fetch limit. Args: params (Dict[str, Any]): Integration parameters. Returns: list[EventType]: List of event type to fetch from the api call. """ event_types_to_fetch = argToList(params.get("event_types_to_fetch", [])) event_types_to_fetch = [event_type.strip() for event_type in event_types_to_fetch] max_auditlog_per_fetch = arg_to_number(params.get("max_auditlog_per_fetch")) or MAX_FETCH_AUDIT_LIMIT max_events_per_fetch = arg_to_number(params.get("max_events_per_fetch")) or MAX_FETCH_EVENT_LIMIT max_requests_per_fetch = arg_to_number(params.get("max_requests_per_fetch")) or MAX_FETCH_REQUEST_LIMIT event_types = [] if "Auditlog" in event_types_to_fetch: EVENT_TYPES["Auditlog"].max_fetch = max_auditlog_per_fetch event_types.append(EVENT_TYPES["Auditlog"]) if "Events" in event_types_to_fetch: EVENT_TYPES["Events"].max_fetch = max_events_per_fetch event_types.append(EVENT_TYPES["Events"]) if "Requests" in event_types_to_fetch: EVENT_TYPES["Requests"].max_fetch = max_requests_per_fetch event_types.append(EVENT_TYPES["Requests"]) return event_types def prepare_list_output(records: List[dict[str, Any]]) -> str: """Prepare human-readable output. Args: records: List of entities response from the API. Returns: markdown string to be displayed in the war room. """ hr_outputs = [] for rec in records: hr_output = { "ID": rec.get("id"), "Type": rec.get("type"), "Settings Name": rec.get("settingsName"), "Application Name": demisto.get(rec, "application.name"), "Application Scan Result": demisto.get(rec, "application.scanResult"), "User": rec.get("user", {}), "Computer name": demisto.get(rec, "computer.name"), "Status": rec.get("status"), "Reason": rec.get("reason"), "Approved By": rec.get("approvedBy"), "Approved By Email": rec.get("approvedByEmail"), "Denied Reason": rec.get("deniedReason"), "Denied By": rec.get("deniedBy"), "Denied By Email": rec.get("deniedByEmail"), "Request Time": rec.get("requestTime"), "Start Time": rec.get("startTime"), "Event Text": rec.get("eventText"), "Event Time": rec.get("eventTime"), } hr_outputs.append(hr_output) return tableToMarkdown(name="AdminByRequests Record(s)", t=hr_outputs, removeNull=True) """ COMMAND FUNCTIONS """ def test_module(client: Client) -> str: # pragma: no cover """ Tests the connection to the service by calling each one of the api endpoints. Args: client (Client): The client object used to interact with the service. Returns: str: 'ok' if the connection is successful. If an authorization error occurs, an appropriate error message is returned. """ event_types = list(EVENT_TYPES.values()) for e in event_types: e.max_fetch = 1 last_run: dict[str, Any] = {} fetch_events(client, last_run, event_types) return "ok" def fetch_events( client: Client, last_run: dict, fetch_events_types: list[EventType], use_last_run_as_params: bool = False ) -> tuple[list[dict[str, Any]], dict]: """Fetch the specified AdminByRequest entity records. Args: client (Client): The client object used to interact with the AdminByRequest service. last_run (dict): The last_run dictionary having the state of previous cycle. fetch_events_types (list[EventType]) : list of Event Types to fetch from API use_last_run_as_params (bool): Flag that sign do we use the last-run as params for the API call Returns: - List of new records to be pushed into XSIAM. - Updated last_run dictionary. """ demisto.debug("AdminByRequest fetch_events invoked") events = [] for event_type in fetch_events_types: output = fetch_events_list(client, last_run, event_type, use_last_run_as_params) events.extend(output) demisto.debug(f"AdminByRequest next_run is {last_run}") return events, last_run def get_events(client: Client, args: dict) -> CommandResults: """ Inner Test Function to make sure the integration works Args: client: AdminByRequest client to be used. args: command arguments. Returns: Command results object that contain the results. """ max_events = arg_to_number(args.get("limit")) or None # User start date in the get events arguments, else get from today first_fetch = arg_to_datetime(args.get("first_fetch")) or get_current_time() first_fetch_date = first_fetch.strftime(DATE_FORMAT_CALLS) call_type: str = args.get("event_type", "") if not max_events: if call_type == "Auditlog": max_events = MAX_FETCH_AUDIT_LIMIT elif call_type == "Events": max_events = MAX_FETCH_EVENT_LIMIT else: max_events = MAX_FETCH_REQUEST_LIMIT event_type = EVENT_TYPES[call_type] event_type.max_fetch = max_events first_parm = {"startdate": first_fetch_date} last_run_to_use_as_params = {**event_type.default_params, **first_parm} output, _ = fetch_events(client, last_run_to_use_as_params, [event_type], use_last_run_as_params=True) human_readable = prepare_list_output(output) command_results = CommandResults( readable_output=human_readable, outputs=output, outputs_prefix="AdminByRequest." + call_type, ) return command_results def list_requests_command(client: Client, args: dict) -> CommandResults: """ Lists requests from AdminByRequest. Args: client: AdminByRequest client to be used. args: command arguments. Returns: CommandResults: Command results object that contains the results. """ request_id = args.get("request_id") status = args.get("status") want_scan_details = argToBoolean(args.get("want_scan_details", False)) limit = arg_to_number(args.get("limit", 50)) all_results = argToBoolean(args.get("all_results", False)) params: dict[str, Any] = {} if status: params["status"] = status if want_scan_details: params["wantscandetails"] = 1 results: list[dict[str, Any]] = [] if request_id: url_suffix = f"requests/{request_id}" result = client.get_events_request(url_suffix, params) if result: results.append(result) else: url_suffix = "requests" if all_results: params["take"] = 1000 while len(results) < EVENTS_LIMIT: response = client.get_events_request(url_suffix, params) if not response: break results.extend(response) if len(response) < 1000: break params["startid"] = response[-1]["id"] + 1 else: params["take"] = limit response = client.get_events_request(url_suffix, params) if response: results.extend(response) human_readable = prepare_list_output(results) command_results = CommandResults( readable_output=human_readable, outputs=results, outputs_prefix="AdminByRequest.Request", ) return command_results def approve_request_command(client: Client, args: dict) -> CommandResults: """ Approves a request in AdminByRequest. Use adminbyrequest-list-requests command to list all available requests. Args: client: AdminByRequest client to be used. args: command arguments. Returns: CommandResults: Command results object that contains the results. """ request_id = args.get("request_id") if not request_id: raise ValueError("request_id is required.") approved_by = args.get("approved_by") headers = {} if approved_by: if not validate_email_address(approved_by): raise DemistoException("approved_by must be a valid email address.") headers["approvedby"] = approved_by url_suffix = f"requests/{request_id}" response = client.approve_request(url_suffix, headers) if response.status_code == 204: readable_output = f"Request with {request_id} id was successfully approved." else: raise DemistoException(f"Failed to approve request {request_id}. Status code: {response.status_code}") return CommandResults(readable_output=readable_output) def deny_request_command(client: Client, args: dict) -> CommandResults: """ Denies a request in AdminByRequest. Use adminbyrequest-list-requests command to list all available requests. Args: client: AdminByRequest client to be used. args: command arguments. Returns: CommandResults: Command results object that contains the results. """ request_id = args.get("request_id") if not request_id: raise ValueError("request_id is required.") denied_by = args.get("denied_by") if denied_by and not validate_email_address(denied_by): raise ValueError("denied_by must be a valid email address.") reason = args.get("reason") headers = {} if denied_by: headers["deniedby"] = denied_by if reason: headers["reason"] = reason url_suffix = f"requests/{request_id}" response = client.deny_request(url_suffix, headers) if response.status_code == 204: readable_output = f"Request with {request_id} id was successfully denied." else: raise DemistoException(f"Failed to deny request {request_id}. Status code: {response.status_code}") return CommandResults(readable_output=readable_output) def main(): # pragma: no cover """main function, parses params and runs command functions""" params = demisto.params() args = demisto.args() command = demisto.command() # get the service API url base_url = params.get("url") verify_certificate = not argToBoolean(params.get("insecure", False)) proxy = argToBoolean(params.get("proxy", False)) api_key = params.get("credentials", {}).get("password") demisto.debug(f"Command being called is {command}") try: client = Client(base_url=base_url, api_key=api_key, verify=verify_certificate, use_proxy=proxy) events: List[dict[str, Any]] if command == "test-module": # Command made to test the integration result = test_module(client) return_results(result) elif command == "fetch-events": fetch_events_types = set_event_type_fetch_limit(params) last_run = demisto.getLastRun() events, next_run = fetch_events(client, last_run, fetch_events_types) if len(events): demisto.debug(f"Sending {len(events)} events to XSIAM AdminByRequest, before server call.") send_events_to_xsiam(events=events, vendor=VENDOR, product=PRODUCT) demisto.setLastRun(next_run) demisto.debug(f"Successfully saved last_run= {demisto.getLastRun()}") elif command == "adminbyrequest-get-events": command_results = get_events(client, args) events = cast(List[dict[str, Any]], command_results.outputs) if events and argToBoolean(args.get("should_push_events", False)): demisto.debug(f"Sending {len(events)} events.") send_events_to_xsiam(events=events, vendor=VENDOR, product=PRODUCT) return_results(command_results) elif command == "adminbyrequest-list-requests": return_results(list_requests_command(client, args)) elif command == "adminbyrequest-request-approve": return_results(approve_request_command(client, args)) elif command == "adminbyrequest-request-deny": return_results(deny_request_command(client, args)) else: raise NotImplementedError(f"Command {command} is not implemented") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): main()