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

IDBMC Discovery
ProviderBMC Software Inc
CategoryEndpoint
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix 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 settings
  • insecure — Trust any certificate (not secure)

Commands (7)

  • bmc-discovery-scan-create

    Create a new Discovery scan (run) with pre-defined parameters (Kind: IP; Level: Full Discovery; Type: Snapshot)

  • bmc-discovery-scan-results-list

    Get detailed scan (run) results

  • bmc-discovery-scan-status-list

    Get status of all currently running scans (no arguments) or a specific scan (provide a run_id argument)

  • bmc-discovery-scan-stop

    Stop a Discovery scan (run)

  • bmc-discovery-scan-summary

    Get scan (run) results summary

  • bmc-discovery-search

    Search for a node by hostname or IP address. If not found, repeat the search with a different node kind.

  • bmc-discovery-search-custom

    Run 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()