BMC Discovery
BMC Discovery is a SaaS-based, cloud-native discovery and dependency modeling system that provides instant visibility into hardware, software, and service dependencies across multi-cloud, hybrid, and on-premises environments.
Endpoint · BMC Discovery
Details
| ID | BMC Discovery |
|---|---|
| Provider | BMC Software Inc |
| Category | Endpoint |
| From Version | 6.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
BMC Discovery is a SaaS-based, cloud-native discovery and dependency modeling system that provides instant visibility into hardware, software, and service dependencies across multi-cloud, hybrid, and on-premises environments.
This integration was integrated and tested with BMC Discovery v.22.1.
Configure BMC Discovery Integration in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | BMC Discovery URL | True |
| API Token | BMC Discovery user API token* | True |
| Use system proxy settings | False | |
| Trust any certificate (not secure) | False |
- The BMC Discovery user must have the following permissions: admin, api-access, discovery, system
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.
bmc-discovery-scan-status-list
Get status of all currently running scans (runs) or a specific scan (run)
Base Command
bmc-discovery-scan-status-list
Input
| Argument Name | Description | Required |
|---|---|---|
| run_id | ID of a specific scan (run) | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| BmcDiscovery.Scan.Status.uuid | String | Scan UUID |
| BmcDiscovery.Scan.Status.blocked | Boolean | Is blocked |
| BmcDiscovery.Scan.Status.done | Number | Number of scanned hosts |
| BmcDiscovery.Scan.Status.total | Number | Number of hosts to scan |
| BmcDiscovery.Scan.Status.finished | Boolean | Is finished |
| BmcDiscovery.Scan.Status.label | String | Scan name |
| BmcDiscovery.Scan.Status.scan_kind | String | IP/Cloud/API |
| BmcDiscovery.Scan.Status.scan_level | String | Full discovery/Sweep scan |
| BmcDiscovery.Scan.Status.scan_type | String | Snapshot/scheduled |
| BmcDiscovery.Scan.Status.user | String | Initiating user |
| BmcDiscovery.Scan.Status.valid_ranges | String | IP ranges to scan |
| BmcDiscovery.Scan.Status.cancelled | String | Is canceled |
| BmcDiscovery.Scan.Status.key | String | Scan key |
| BmcDiscovery.Scan.Status.outpost_id | String | Outpost id |
| BmcDiscovery.Scan.Status.scope | String | Scan scope |
| BmcDiscovery.Scan.Status.scan_params.provider | String | Scan provider |
| BmcDiscovery.Scan.Status.scan_options.NO_PING | Boolean | Is NO-PING option set |
| BmcDiscovery.Scan.Status.scan_options.SESSION_LOGGING | Boolean | Is SESSION_LOGGING option set |
| BmcDiscovery.Scan.Status.scan_options.SKIP_IMPLICIT_SCANS | Boolean | Is SKIP_IMPLICIT_SCANS option set |
| BmcDiscovery.Scan.Status.scan_options.MAX_START_SSM_SESSIONS | Boolean | Is MAX_START_SSM_SESSIONS option set |
| BmcDiscovery.Scan.Status.scan_options.MAX_ACTIVE_SSM_SESSIONS | Boolean | Is MAX_ACTIVE_SSM_SESSIONS option set |
| BmcDiscovery.Scan.Status.scanning | Number | Number of entities in scanning status |
| BmcDiscovery.Scan.Status.pre_scanning | Number | Number of entities in pre_scanning status |
| BmcDiscovery.Scan.Status.starttime | Date | Scan start time |
| BmcDiscovery.Scan.Status.waiting | Number | Number of entities in waiting status |
| BmcDiscovery.Scan.Status.uri | String | Scan URI |
| BmcDiscovery.Scan.Status.inferred | String | Scan inferred URI |
| BmcDiscovery.Scan.Status.results | String | Scan results URI |
| BmcDiscovery.Scan.Status.consolidating | Boolean | Is consolidating |
| BmcDiscovery.Scan.Status.consolidation_source | String | Consolidation source |
bmc-discovery-scan-create
Create a new snapshot scan (run)
Base Command
bmc-discovery-scan-create
Input
| Argument Name | Description | Required |
|---|---|---|
| label | Scan (run) name | Yes |
| ranges | IP range | Yes |
Note: The following run parameters are predefined:
- Kind = IP
- Level = Full Discovery
- Type = Snapshot
Context Output
| Path | Type | Description |
|---|---|---|
| BmcDiscovery.Scan.Create.url | String | New scan URI |
| BmcDiscovery.Scan.Create.uuid | String | New scan UUID |
bmc-discovery-scan-summary
Retrieve scan (run) results summary
Base Command
bmc-discovery-scan-summary
Input
| Argument Name | Description | Required |
|---|---|---|
| run_id | An ID of the scan | Yes |
Note: The following run parameters are predefined:
- Kind = IP
- Level = Full Discovery
- Type = Snapshot
Context Output
| Path | Type | Description |
|---|---|---|
| BmcDiscovery.Scan.Summary.Success | Number | Number of successfully scanned entities |
| BmcDiscovery.Scan.Summary.Skipped | Number | Number of skipped entities |
| BmcDiscovery.Scan.Summary.NoAccess | Number | Number of entities with no access |
| BmcDiscovery.Scan.Summary.NoResponse | Number | Number of entities with no response |
| BmcDiscovery.Scan.Summary.Error | Number | Number of entities in error |
| BmcDiscovery.Scan.Summary.Dropped | Number | Number of dropped entities |
bmc-discovery-scan-stop
Cancel a currently running scan
Base Command
bmc-discovery-scan-stop
Input
| Argument Name | Description | Required |
|---|---|---|
| run_id | An ID of the scan | Yes |
Note: The following run parameters are predefined:
- Kind = IP
- Level = Full Discovery
- Type = Snapshot
Context Output
| Path | Type | Description |
|---|---|---|
| BmcDiscovery.Scan.Stop.cancelled | Boolean | Scan cancel status |
bmc-discovery-scan-results-list
Get a list of hosts by specific result type
Base Command
bmc-discovery-scan-results-list
Input
| Argument Name | Description | Required |
|---|---|---|
| run_id | Yes | |
| result_type | “Available options: Success, Skipped, NoAccess, NoResponse, Error, Dropped” | Yes |
| offset | Search results offset | Optional |
| limit | Search results limit | Optional |
| results_id | Search results id | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| BmcDiscovery.Scan.Result.count | Number | Number of hosts of the result type |
| BmcDiscovery.Scan.Result.kind | String | Result kind type |
| BmcDiscovery.Scan.Result.next_offset | Number | Next offset to be used |
| BmcDiscovery.Scan.Result.offset | Number | Current offset value |
| BmcDiscovery.Scan.Result.results_id | String | Current result id |
| BmcDiscovery.Scan.Result.results | Unknown | The actual scan result data |
bmc-discovery-search
Search for a node by IP address or hostname
Base Command
bmc-discovery-search
Input
| Argument name | Description | Required |
|---|---|---|
| ip | IP address | Optional |
| hostname | Hostname | Optional |
| kind | “Node kind (Host, NetWorkDevice, SNMPManagedDevice)” | Single Select |
| name | Search name | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| BmcDiscovery.Search.count | Number | Number of returned results |
| BmcDiscovery.Search.data | Unknown | Search results |
| BmcDiscovery.Search.name | String | Name of the search |
bmc-discovery-search-custom
Run a user defined query
Base Command
bmc-discovery-search-custom
Input
| Argument name | Description | Required |
|---|---|---|
| query | Full search query* | Yes |
| offset | Search results offset | Optional |
| limit | Search resuluts limit | Optional |
| results_id | Search results id | Optional |
*See the documentation for reference
Context Output
| Path | Type | Description |
|---|---|---|
| BmcDiscovery.Search.count | Number | Number of returned results |
| BmcDiscovery.Search.data | Unknown | Search results |
Configuration parameters
url— Server URL (required)api_token— (required)proxy— Use system proxy settingsinsecure— Trust any certificate (not secure)
Commands (7)
-
bmc-discovery-scan-createCreate a new Discovery scan (run) with pre-defined parameters (Kind: IP; Level: Full Discovery; Type: Snapshot)
-
bmc-discovery-scan-results-listGet detailed scan (run) results
-
bmc-discovery-scan-status-listGet status of all currently running scans (no arguments) or a specific scan (provide a run_id argument)
-
bmc-discovery-scan-stopStop a Discovery scan (run)
-
bmc-discovery-scan-summaryGet scan (run) results summary
-
bmc-discovery-searchSearch for a node by hostname or IP address. If not found, repeat the search with a different node kind.
-
bmc-discovery-search-customRun a user-defined search
import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * """ IMPORTS """ import re import urllib3 urllib3.disable_warnings() """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" class Client(BaseClient): """ Client will implement the service API, and should not contain any Demisto logic. Should only do requests and return data. """ def __init__(self, url, api_token, verify, proxy): super().__init__(base_url=url, proxy=proxy, verify=verify) self.api_token = api_token if self.api_token: self._headers = {"Authorization": f"Bearer {self.api_token}"} def discovery_process_status(self): url_suffix = "/discovery" return self._http_request(method="GET", url_suffix=url_suffix, resp_type="json", ok_codes=(200,)) def discovery_scan_status_list(self, **args): url_suffix = "/discovery/runs" run_id = args.get("run_id", "") if run_id: url_suffix += "/" + run_id return self._http_request(method="GET", url_suffix=url_suffix, resp_type="json", ok_codes=(200,)) def discovery_search(self, **args): url_suffix = "/data/search?format=object" method = "POST" kind = args.get("kind", "Host") ip = args.get("ip", "") hostname = args.get("hostname", "") if not ip and not hostname: raise DemistoException("Please specify ip or hostname parameter") elif ip and hostname: raise DemistoException("ip and hostname are mutually exclusive. Please specify just one parameter") if ip: ipv4_match = re.match(ipv4Regex, ip) ipv6_match = re.match(ipv6Regex, ip) if not ipv4_match and not ipv6_match: raise DemistoException("Specified ip address doesn't look valid") query = "SEARCH %s WHERE %s show *, __all_ip_addrs, __all_mac_addrs, __all_dns_names, #id" if ip: where = f"__all_ip_addrs LIKE '{ip}'" else: where = f"__all_dns_names LIKE '{hostname}'" query = query % (kind, where) data = {"query": query} return self._http_request(method=method, url_suffix=url_suffix, resp_type="json", json_data=data, ok_codes=(200,)) def discovery_search_custom(self, **args): url_suffix = "/data/search?format=object" method = "POST" query = args.get("query", "") if not query: raise DemistoException("Please specify query parameter") offset = args.get("offset", "") limit = args.get("limit", 50) results_id = args.get("results_id", "") if offset and not results_id: raise DemistoException('"offset" cannot be specified without "results_id"') params = {} if offset: params["offset"] = offset params["results_id"] = results_id if limit: params["limit"] = limit data = {"query": query} return self._http_request( method=method, url_suffix=url_suffix, resp_type="json", params=params, json_data=data, ok_codes=(200,) ) def discovery_scan_create(self, **args): url_suffix = "/discovery/runs" label = args.get("label", "") ranges = argToList(args.get("ranges", "")) settings = { "scan_kind": "IP", "scope": "", "ranges": ranges, # [ "xx.xx.xx.xx/xx" , "xx.xx.xx.xx/xx" ] "label": label, "scan_level": "Full Discovery", } return self._http_request(method="POST", url_suffix=url_suffix, resp_type="json", ok_codes=(200,), json_data=settings) def discovery_scan_stop(self, **args): run_id = args.get("run_id", "") url_suffix = "/discovery/runs/" + run_id data = {"cancelled": True} return self._http_request(method="PATCH", url_suffix=url_suffix, resp_type="text", json_data=data, ok_codes=(200,)) def discovery_scan_summary(self, **args): run_id = args.get("run_id", "") url_suffix = "/discovery/runs/" + run_id + "/results" return self._http_request(method="GET", url_suffix=url_suffix, resp_type="json", ok_codes=(200,)) def discovery_scan_results_list(self, **args): run_id = args.get("run_id", "") if not run_id: raise DemistoException("Please specify run_id parameter") result_type = args.get("result_type", "Success") limit = args.get("limit", 50) offset = args.get("offset", "") results_id = args.get("results_id", "") if offset and not results_id: raise DemistoException('"offset" cannot be specified without "results_id"') params = {} params["format"] = "object" if offset: params["offset"] = offset params["results_id"] = results_id if limit: params["limit"] = limit url_suffix = "/discovery/runs/" + run_id + "/results/" + result_type return self._http_request(method="GET", url_suffix=url_suffix, params=params, resp_type="json", ok_codes=(200,)) def test_module(client): try: response = client.discovery_process_status() status = demisto.get(response, "status") if not status: return f"Unexpected result from the service: status={status}" return "ok" except Exception as e: exception_text = str(e).lower() if "forbidden" in exception_text or "authorization" in exception_text: return "Authorization Error: make sure API Key is correctly set" else: raise def discovery_search_custom_command(client: Client, **args) -> CommandResults: response = client.discovery_search_custom(**args) if not response: raise DemistoException("Search command failed") user_output = [] for kind in response: row = {} row["count"] = kind["count"] row["offset"] = kind["offset"] for key in ("kind", "next_offset", "results_id"): if key in kind: row[key] = kind[key] user_output.append(row) search_name = args.get("name", "") context_output = {} context_output["data"] = response if search_name: context_output["name"] = search_name return CommandResults( outputs_prefix="BmcDiscovery.CustomSearch", outputs=context_output, raw_response=response, readable_output=tableToMarkdown(name="BMC Discovery Custom Search Results (see context for more details)", t=user_output), ) def discovery_search_command(client: Client, **args) -> CommandResults: response = client.discovery_search(**args) if not response: raise DemistoException("Search command failed") output: Dict = {} output["data"] = [] search_name = args.get("name", "") if search_name: output["name"] = search_name count = 0 for item in response: results = item["results"] count += item.get("count", 0) for result in results: remove_nulls_from_dictionary(result) output["data"].append(result) output["count"] = count user_map_dict = { "#OwnedItem:Ownership:ITOwner:Person.name": "IT Owner", "domain": "Domain", "os": "OS", "__all_ip_addrs": "IP Addresses", "local_fqdn": "Local FQDN", "name": "Name", "#id": "id", "#OwnedItem:Ownership:BusinessOwner:Person.name": "Business Owner", "hostname": "Hostname", "__all_dns_names": "DNS Names", "type": "Type", "#ElementInLocation:Location:Location:Location.name": "Location", "os_class": "OS Class", } user_output = [] for item in output["data"]: # type: ignore[not iterable] row = {} for p in user_map_dict: if p in item: row[user_map_dict[p]] = item[p] user_output.append(row) return CommandResults( outputs_prefix="BmcDiscovery.Search", outputs_key_field="data.#id", outputs=output, raw_response=response, readable_output=tableToMarkdown(name="BMC Discovery Search Results", t=user_output), ) def discovery_process_status_command(client: Client) -> CommandResults: response = client.discovery_process_status() status = demisto.get(response, "status") if not status: raise DemistoException("Get status failed", res=response) return CommandResults( outputs_prefix="BmcDiscovery.Process", outputs=response, raw_response=response, readable_output=tableToMarkdown(name="BMC Discovery Status", t=response), ) def discovery_scan_status_list_command(client: Client, **args) -> CommandResults: response = client.discovery_scan_status_list(**args) if isinstance(response, list): pass elif not response: raise DemistoException("Get runs failed") else: response = [response] user_map_dict = { "scan_level": "Scan Level", "done": "Done", "total": "Total", "user": "User", "starttime": "Start Time", "finished": "Finished", "label": "Label", "scan_kind": "Kind", "valid_ranges": "Ranges", "scan_type": "Type", "uuid": "UUID", } user_output = [] for item in response: row = {} for p in user_map_dict: if p in item: row[user_map_dict[p]] = item[p] user_output.append(row) return CommandResults( outputs_prefix="BmcDiscovery.Scan.Status", outputs_key_field="uuid", outputs=response, raw_response=response, readable_output=tableToMarkdown(name="BMC Discovery Scan Status", t=user_output), ) def discovery_scan_create_command(client: Client, **args) -> CommandResults: response = client.discovery_scan_create(**args) return CommandResults( outputs_prefix="BmcDiscovery.Scan.Create", outputs=response, raw_response=response, readable_output=tableToMarkdown(name="BMC Discovery New Scan", t=response), ) def discovery_scan_stop_command(client: Client, **args) -> CommandResults: response = client.discovery_scan_stop(**args) return CommandResults( outputs_prefix="BmcDiscovery.Scan", outputs=response, raw_response=response, readable_output=tableToMarkdown(name="BMC Discovery Scan Status", headers=["Stopped"], t=response), ) # noqa: E128 def discovery_scan_summary_command(client: Client, **args) -> CommandResults: response = client.discovery_scan_summary(**args) if not response: raise DemistoException("Failed to get scan summary") output = {} for key in response: if "count" in response[key]: output[key] = response[key]["count"] return CommandResults( outputs_prefix="BmcDiscovery.Scan.Summary", outputs=output, raw_response=response, readable_output=tableToMarkdown(name="BMC Discovery Scan Summary", t=output), ) def discovery_scan_results_list_command(client: Client, **args) -> CommandResults: response = client.discovery_scan_results_list(**args) if not response: raise DemistoException("Failed to get scan results") result_type = args.get("result_type", "Success") user_output = [] for kind in response: row = {} row["count"] = kind["count"] row["offset"] = kind["offset"] for key in ("kind", "next_offset", "results_id"): if key in kind: row[key] = kind[key] user_output.append(row) return CommandResults( outputs_prefix="BmcDiscovery.Scan.Result", outputs=response, raw_response=response, readable_output=tableToMarkdown( name=f'BMC Discovery Scan Results for "{result_type}" kind(see context for more details)', t=user_output ), ) def main() -> None: # pragma: no cover params = demisto.params() args = demisto.args() command = demisto.command() api_token = params.get("api_token", {}).get("password") url = params.get("url") verify = not params.get("insecure", False) proxy = params.get("proxy", False) demisto.debug(f"Command being called is {command}") try: client = Client(url=url, api_token=api_token, verify=verify, proxy=proxy) if command == "test-module": return_results(test_module(client)) elif command == "bmc-discovery-scan-status-list": return_results(discovery_scan_status_list_command(client, **args)) elif command == "bmc-discovery-search": return_results(discovery_search_command(client, **args)) elif command == "bmc-discovery-search-custom": return_results(discovery_search_custom_command(client, **args)) elif command == "bmc-discovery-scan-create": return_results(discovery_scan_create_command(client, **args)) elif command == "bmc-discovery-scan-stop": return_results(discovery_scan_stop_command(client, **args)) elif command == "bmc-discovery-scan-summary": return_results(discovery_scan_summary_command(client, **args)) elif command == "bmc-discovery-scan-results-list": return_results(discovery_scan_results_list_command(client, **args)) else: raise NotImplementedError(f"command {command} is not implemented.") except Exception as e: demisto.error(fix_traceback_line_numbers(traceback.format_exc())) return_error("\n".join(("Failed to execute {command} command.", "Error:", str(e)))) if __name__ in ("__main__", "__builtin__", "builtins"): main()