Veeam ONE REST API
Veeam ONE REST API allows you to query information about Veeam ONE entities and perform operations with these entities using HTTP requests and standard HTTP methods.
Vulnerability Management · Veeam App
Details
| ID | Veeam ONE REST API |
|---|---|
| Provider | Veeam |
| Category | Vulnerability Management |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Veeam ONE REST API allows you to query information about Veeam ONE entities and perform operations with these entities using HTTP requests and standard HTTP methods.
This integration was integrated and tested with version 12.2.0 of Veeam ONE.
Configure Veeam ONE REST API in Cortex
| Parameter | Description | Required |
|---|---|---|
| Username | True | |
| Password | True | |
| Resource URL | True | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch incidents | False | |
| First fetch time | False | |
| Triggered Alarms Per Request | The maximum number of triggered alarms that can be fetched during command execution. | False |
| API Request Timeout (Seconds) | False | |
| Incidents Fetch Interval | False | |
| Incident type | 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.
veeam-vone-get-triggered-alarms
Get All Triggered Alarms.
Base Command
veeam-vone-get-triggered-alarms
Input
| Argument Name | Description | Required |
|---|---|---|
| Offset | Number of first resources in the output that are excluded. | Optional |
| Limit | Number of first resources in the output that are returned. Default is 100. | Optional |
| Filter | Conditions that a resource must meet to be included in the output. | Optional |
| Sort | Order in which resources are returned. | Optional |
| Select | Property that must be explicitly returned in a response. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Veeam.VONE.TriggeredAlarmInfoPage.items.triggeredAlarmId | Number | ID assigned to a triggered alarm. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.name | String | Name of an alarm template. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.alarmTemplateId | Number | ID assigned to an alarm template. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.predefinedAlarmId | Number | ID assigned to an alarm. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.triggeredTime | String | Date and time when an alarm triggered. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.description | String | Message containing alarm details. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.comment | String | Comment on a triggered alarm. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.repeatCount | Number | Number of times an alarm was triggered. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.childAlarmsCount | Number | Number of alarm child objects. |
| Veeam.VONE.TriggeredAlarmInfoPage.items.remediation.description | String | |
| Veeam.VONE.TriggeredAlarmInfoPage.totalCount | Number |
veeam-vone-resolve-triggered-alarms
Resolve Triggered Alarms.
Base Command
veeam-vone-resolve-triggered-alarms
Input
| Argument Name | Description | Required |
|---|---|---|
| triggeredAlarmIds | List of IDs assigned to triggered alarms that you want to resolve. | Required |
| comment | Additional information. | Required |
| resolveType | Type of alarm resolution. | Required |
Context Output
There is no context output for this command.
Configuration parameters
credentials— Username (required)url— Resource URL (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingshttp_request_timeout_sec— API Request Timeout (Seconds)isFetch— Fetch incidentsfirst_fetch— First fetch timemax_fetch— Triggered Alarms Per RequestincidentFetchInterval— Incidents Fetch IntervalincidentType— Incident type
Commands (2)
-
veeam-vone-get-triggered-alarmsGet All Triggered Alarms.
-
veeam-vone-resolve-triggered-alarmsResolve Triggered Alarms.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """ IMPORTS """ import ast import json from collections.abc import Callable from datetime import datetime from enum import Enum from http import HTTPStatus from typing import Any import urllib3 from dateutil import parser from packaging.version import Version # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ MAX_ATTEMPTS = 3 MAX_ALARMS_FOR_FETCH = 200 GRANT_TYPE = "password" DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" CONTENT_TYPE = "application/json" ERROR_IN_TRIGGERED_ALARMS = "error_in_triggered_alarms" SUPPORTED_VONE_VERSION = "12.2.0.0" NOT_APPLICABLE = "N/A" DEFAULT_PAGE_SIZE = 100 DEFAULT_SIZE_LIMIT = 0 # Unlimited DESIRED_TYPES = { 395: "Backup Server Security Status", 364: "Incremental Backup Size", 369: "Job Duration", 391: "Enterprise Application Backup", 365: "Backup Copy Creation Time", 370: "Physical Machine Backup", 314: "Virtual Machine Backup", 331: "Virtual Machine Replica", 376: "Immutability State", 377: "Immutability Change Tracking", 403: "Malware Detection Change Tracking", 316: "Job Disabling", 342: "Possible Malware Activity", 381: "Job Duration Deviation (Veeam Backup for Microsoft 365)", 419: "Recon Scanner Threat State", } SEVERITY_MAP = { 395: IncidentSeverity.MEDIUM, 364: IncidentSeverity.CRITICAL, 369: IncidentSeverity.MEDIUM, 391: IncidentSeverity.CRITICAL, 365: IncidentSeverity.CRITICAL, 370: IncidentSeverity.CRITICAL, 314: IncidentSeverity.CRITICAL, 331: IncidentSeverity.CRITICAL, 376: IncidentSeverity.MEDIUM, 377: IncidentSeverity.MEDIUM, 403: IncidentSeverity.CRITICAL, 316: IncidentSeverity.MEDIUM, 342: IncidentSeverity.CRITICAL, 381: IncidentSeverity.MEDIUM, 419: IncidentSeverity.HIGH, } ERROR_COUNT_MAP = { 2: IncidentSeverity.LOW, 6: IncidentSeverity.MEDIUM, 48: IncidentSeverity.CRITICAL, } DESIRED_STATUSES = ["Warning", "Error"] class Operation(Enum): EQUALS = "equals" NOT_EQUALS = "notEquals" GREATER_THAN = "greaterThan" GREATER_THAN_OR_EQUAL = "greaterThanOrEqual" LESS_THAN = "lessThan" LESS_THAN_OR_EQUAL = "lessThanOrEqual" IN = "in" CONTAINS = "contains" SUBSET = "subset" SUPERSET = "superset" OR = "or" AND = "and" EXCLUSIVE_OR = "exclusiveOr" NOT = "not" class FilterBuilder: def __init__(self, operation=None, items=None): self.operation = operation self.items = items or [] def add_property(self, property, operation, value, collation=None): item = {"property": property, "operation": operation.value, "value": value} if collation: item["collation"] = collation self.items.append(item) def add_node(self, node): if isinstance(node, FilterBuilder): self.items.append(json.loads(str(node))) def __str__(self): result = {} if len(self.items) == 1: result = self.items[0] elif len(self.items) > 1: result = {"operation": self.operation.value if self.operation else None, "items": self.items} return json.dumps(result) """ CLIENT CLASS """ class Client(BaseClient): def __init__(self, server_url, verify, proxy, headers, auth, timeout): super().__init__(base_url=server_url, verify=verify, proxy=proxy, headers=headers, auth=auth, timeout=timeout) def get_headers(self): """ Gets headers required for requests. Returns: dict: The header dictionary. """ return self._headers def set_headers(self, headers): """ Sets headers required for requests. Args: headers (dict): The header dictionary to set. """ self._headers = headers def get_about_request(self): """ Retrieves information about the Veeam ONE server. Returns: dict: The response data. """ headers = self._headers.copy() response = self._http_request("get", "api/v2.2/about", headers=headers) return response def authentication_create_token_request(self, grant_type, username=None, password=None, refresh_token=None): """ Creates an authentication token. Args: grant_type (str): The grant type (e.g., 'password' or 'refresh_token'). username (str): The username for the password grant type. password (str): The password for the password grant type. refresh_token (str): The refresh token for the refresh token grant type. Returns: dict: The response data. """ data = assign_params(grant_type=grant_type, username=username, password=password, refresh_token=refresh_token) headers = self._headers.copy() headers["Content-Type"] = "application/x-www-form-urlencoded" response = self._http_request("post", "api/token", data=data, headers=headers) return response def get_triggered_alarms_request(self, Offset=None, Limit=None, Filter=None, Sort=None, Select=None): """ Retrieves triggered alarms. Args: Offset (int): The number of records to skip. Limit (int): The maximum number of records to retrieve. Filter (str): The filter to apply to the results. Sort (str): The order in which to sort the results. Select (str): Property that must be explicitly returned in a response. Returns: dict: The response data. """ params = assign_params(Offset=Offset, Limit=Limit, Filter=Filter, Sort=Sort, Select=Select) headers = self._headers.copy() response = self._http_request("get", "api/v2.2/alarms/triggeredAlarms", params=params, headers=headers) return response def resolve_triggered_alarms_request(self, triggeredAlarmIds, comment, resolveType): """ Resolves triggered alarms. Args: triggeredAlarmIds (List[str]): The IDs of the triggered alarms to resolve. comment (str): The comment for resolving the alarms. resolveType (str): The type of resolution. Returns: str: The response result as a string. """ data = assign_params(triggeredAlarmIds=triggeredAlarmIds, comment=comment, resolveType=resolveType) headers = self._headers.copy() response = self._http_request( "post", "api/v2.2/alarms/triggeredAlarms/resolve", json_data=data, headers=headers, resp_type="response" ) result = str(response) return result """ HELPER FUNCTIONS """ def get_triggered_alarms_command(client: Client, args: dict[str, Any]) -> CommandResults: Offset = args.get("Offset", None) validate_int(Offset) Limit = args.get("Limit", None) validate_int(Limit) Filter = str(args.get("Filter", "")) Sort = str(args.get("Sort", "")) Select = str(args.get("Select", "")) response = client.get_triggered_alarms_request(Offset, Limit, Filter, Sort, Select) command_results = CommandResults( outputs_prefix="Veeam.VONE.TriggeredAlarmInfoPage", outputs_key_field="", outputs=response["items"], raw_response=response["items"], ) return command_results def resolve_triggered_alarms_command(client: Client, args: dict[str, Any]) -> CommandResults: triggeredAlarmIds_str = args.get("triggeredAlarmIds", "") triggeredAlarmIds = convert_to_list(triggeredAlarmIds_str) comment = str(args.get("comment", "")) resolveType = str(args.get("resolveType", "")) response = client.resolve_triggered_alarms_request(triggeredAlarmIds, comment, resolveType) command_results = CommandResults(outputs_prefix="Veeam.VONE", outputs_key_field="", outputs=response, raw_response=response) return command_results def convert_to_list(string: str) -> list: try: result = ast.literal_eval(string) if isinstance(result, list): return result else: return [] except Exception as e: raise Exception(f"Failed to convert '{string}' to list. Exception: {str(e)}") def validate_int(value: str) -> None: if value: try: int(value) except ValueError as e: raise ValueError(f"Invalid integer value: '{value}'") from e def check_version(version_: str) -> None: parsed_version = Version(version_) parsed_supported_version = Version(SUPPORTED_VONE_VERSION) if parsed_version < parsed_supported_version: raise ValueError(f"Minimum supported version is {SUPPORTED_VONE_VERSION}. Current version is {version_}") def test_module(client: Client) -> str: """ Tests the integration by making a request to the Veeam ONE server and checking the version. Args: client (Client): The Veeam ONE API client. Returns: str: The result of the test. Returns 'ok' if the test is successful. Raises: Exception: If an error occurred during the test. """ try: handle_command_with_token_refresh(client.get_about_request, {}, client) except Exception as e: exception_text = str(e).lower() if "forbidden" in exception_text or "authorization" in exception_text: return "Authentication Error: Invalid API Key" else: raise e return "ok" def get_access_token(client: Client, username: str, password: str) -> str: response = client.authentication_create_token_request(GRANT_TYPE, username, password) token = response.get("access_token") return token def search_with_paging( method: Callable[..., Any], args: dict[str, Any] = None, page_size=DEFAULT_PAGE_SIZE, size_limit=DEFAULT_SIZE_LIMIT ) -> list[dict]: if args is None: args = {} else: args = dict(args) skip_items = 0 args["Offset"] = 0 items_to_fetch = size_limit items: list[dict] = [] while True: if 0 < items_to_fetch < page_size: page_size = items_to_fetch args["Limit"] = page_size response = method(**args) items = items + response["items"] response_len = len(response["items"]) if response_len < page_size: break items_to_fetch -= response_len skip_items += page_size if size_limit and items_to_fetch <= 0: items = items[:size_limit] break args["Offset"] = skip_items return items def overwrite_last_fetch_time(last_fetch_time: str, alarm: dict) -> str: last_fetch_datetime = parser.isoparse(last_fetch_time) alarm_datetime = parser.isoparse(alarm["triggeredTime"]) if alarm_datetime > last_fetch_datetime: last_fetch_time = alarm["triggeredTime"] return last_fetch_time def process_error(error_count: int, error_message: str) -> tuple[dict, int]: error_count += 1 incident = {} if error_count in ERROR_COUNT_MAP: integration_instance = demisto.callingContext.get("context", {}).get("IntegrationInstance", "") incident_name = f"Veeam - Fetch incident error has occurred on {integration_instance}" incident = { "name": incident_name, "occurred": datetime.now().strftime(DATE_FORMAT), "rawJSON": json.dumps({"incident_type": "Incident Fetch Error", "details": error_message}), "severity": ERROR_COUNT_MAP[error_count], } return incident, error_count def convert_triggered_alarms_to_incidents( client: Client, start_time: datetime, existed_ids: set, max_results: int ) -> tuple[list[dict], set[str], str]: last_fetch_time = start_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ") sorting = '{"property": "triggeredTime", "direction": "ascending"}' filter_builder = FilterBuilder(operation=Operation.AND) filter_builder.add_property("triggeredTime", Operation.GREATER_THAN_OR_EQUAL, last_fetch_time) filter_builder.add_property("predefinedAlarmId", Operation.IN, list(DESIRED_TYPES.keys())) filter_builder.add_property("status", Operation.IN, DESIRED_STATUSES) Filter = str(filter_builder) def _paged_alarms_method(**kwargs): return handle_command_with_token_refresh(client.get_triggered_alarms_request, kwargs, client) response = search_with_paging(method=_paged_alarms_method, args={"Filter": Filter, "Sort": sorting}, size_limit=max_results) incidents: list[dict] = [] new_ids = set() for alarm in response: if len(incidents) >= max_results: break alarm_id = str(alarm.get("triggeredAlarmId")) if alarm_id not in existed_ids: object_name = alarm.get("alarmAssignment", {}).get("objectName", NOT_APPLICABLE) incident_name = f"Veeam - {alarm['name']} ({object_name})" alarm["incident_type"] = alarm["predefinedAlarmId"] incident = { "name": incident_name, "occurred": alarm["triggeredTime"], "rawJSON": json.dumps(alarm), "severity": SEVERITY_MAP.get(alarm["predefinedAlarmId"]), } new_ids.add(alarm_id) incidents.append(incident) last_fetch_time = overwrite_last_fetch_time(last_fetch_time, alarm) if not new_ids: new_ids = existed_ids return incidents, new_ids, last_fetch_time def fetch_converted_incidents( client: Client, last_run: dict, last_fetch: str, max_results: int, errors_by_command: dict ) -> tuple[list[dict], set[str], str]: last_fetch_time = last_fetch incidents: list[dict] = [] alarms_ids: set[str] = set() error_count: int = errors_by_command.get(ERROR_IN_TRIGGERED_ALARMS, 0) try: alarms_ids = set(last_run.get("alarms_ids", [])) incidents, alarms_ids, last_fetch_time = convert_triggered_alarms_to_incidents( client=client, start_time=parser.parse(last_fetch), existed_ids=alarms_ids, max_results=max_results ) error_count = 0 except Exception as e: error_message = str(e) demisto.debug(error_message) incident, error_count = process_error(error_count, error_message) if incident: incidents.append(incident) finally: errors_by_command[ERROR_IN_TRIGGERED_ALARMS] = error_count return incidents, alarms_ids, last_fetch_time def fetch_incidents( client: Client, last_run: dict, first_fetch_time: str, max_triggered_alarms_for_fetch: int ) -> tuple[dict, list[dict]]: demisto.debug(f"Last run: {json.dumps(last_run)}") last_fetch = last_run.get("last_fetch", None) # Handle first fetch time if last_fetch is None: # if missing, use what provided via first_fetch_time last_fetch = first_fetch_time else: # otherwise use the stored last fetch last_fetch = last_fetch assert last_fetch incidents: list[dict[str, Any]] = [] errors_by_command: dict = last_run.get("errors_by_command", {}) alarmIds: set[str] = set() last_fetch_time: str = datetime.now().strftime(DATE_FORMAT) if max_triggered_alarms_for_fetch > 0: incidents, alarmIds, last_fetch_time = fetch_converted_incidents( client=client, last_run=last_run, last_fetch=last_fetch, max_results=max_triggered_alarms_for_fetch, errors_by_command=errors_by_command, ) next_run = {"last_fetch": last_fetch_time, "alarms_ids": list(alarmIds), "errors_by_command": errors_by_command} demisto.debug(f"Number of incidents: {len(incidents)}") demisto.debug(f"Next run after incident fetching: {json.dumps(next_run)}") return next_run, incidents def process_command( command: Any, client: Client, first_fetch_time: datetime, params: dict, args: dict, max_attempts: int = MAX_ATTEMPTS ): commands = { "veeam-vone-get-triggered-alarms": get_triggered_alarms_command, "veeam-vone-resolve-triggered-alarms": resolve_triggered_alarms_command, } if command == "test-module": result = test_module(client) return result elif command == "fetch-incidents": max_triggered_alarms_for_fetch = int(params.get("max_fetch", MAX_ALARMS_FOR_FETCH)) next_run, incidents = fetch_incidents( client=client, last_run=demisto.getLastRun(), # getLastRun() gets the last run dict first_fetch_time=datetime.strftime(first_fetch_time, DATE_FORMAT), max_triggered_alarms_for_fetch=max_triggered_alarms_for_fetch, ) demisto.setLastRun(next_run) demisto.incidents(incidents) return None elif command in commands: result = handle_command_with_token_refresh(commands[command], {"client": client, "args": args}, client, max_attempts) return result else: raise NotImplementedError(f"Command {command} is not implemented.") def get_api_key(client: Client) -> str: credentials: dict[str, str] = demisto.params().get("credentials") username: str = credentials.get("identifier", "") password: str = credentials.get("password", "") token = get_access_token(client, username, password) api_key = f"Bearer {token}" return api_key def set_api_key(client: Client, api_key: str) -> None: headers = client.get_headers() headers["Authorization"] = api_key client.set_headers(headers) def handle_command_with_token_refresh(command: Callable, command_params: dict, client: Client, max_attempts: int = MAX_ATTEMPTS): attempts = 0 while attempts < max_attempts: try: context = demisto.getIntegrationContext() api_key = context.get("token") new_token = False if not api_key: api_key = get_api_key(client) new_token = True set_api_key(client, api_key) if new_token: response = client.get_about_request() version = response.get("version", "") check_version(version) context["token"] = api_key demisto.setIntegrationContext(context) res = command(**command_params) return res except Exception as e: status_code = getattr(getattr(e, "res", None), "status_code", None) if status_code == HTTPStatus.UNAUTHORIZED: attempts += 1 context = demisto.getIntegrationContext() context["token"] = None demisto.setIntegrationContext(context) else: raise e raise ValueError("Failed to obtain a valid API Key after 3 attempts") def main() -> None: params: dict[str, Any] = demisto.params() args: dict[str, Any] = demisto.args() url: str = params.get("url", "") verify_certificate: bool = not params.get("insecure", False) proxy: bool = params.get("proxy", False) first_fetch_time = arg_to_datetime(arg=params.get("first_fetch", "3 days"), arg_name="First fetch time", required=False) if not first_fetch_time: first_fetch_time = datetime.now() http_request_timeout_sec = int(params.get("http_request_timeout_sec", 120)) headers = {} headers["Content-Type"] = CONTENT_TYPE command = demisto.command() demisto.debug(f"Command {command} has been run with the following arguments: {args}") try: client: Client = Client( urljoin(url, "/"), verify_certificate, proxy, headers=headers, auth=None, timeout=http_request_timeout_sec ) result = process_command(command, client, first_fetch_time, params, args) return_results(result) except Exception as e: error_message: Union[str, dict[str, Any]] = str(e) res = getattr(e, "res", None) status_code = getattr(res, "status_code", None) if res is not None and status_code: error_message = {"status_code": status_code, "message": str(e)} return_error(error_message) """ ENTRY POINT """ if __name__ in ["__main__", "builtin", "builtins"]: main()