Xpanse Feed
Use this feed to retrieve the discovered IPs/domains/certificates from the Cortex Xpanse asset database.
Data Enrichment & Threat Intelligence · Cortex Xpanse · Feed
Details
| ID | Xpanse Feed |
|---|---|
| Provider | Palo Alto Networks |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 6.10.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
README
Use this feed to retrieve the discovered IPs/Domains/Certificates from Cortex Xpanse asset database.
This integration was integrated and tested with version 2.5 of Cortex Xpanse.
Configure Xpanse Feed in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | The web UI with `api-` appended to front (e.g., https://api-xsiam.paloaltonetworks.com). For more information, see https://docs.paloaltonetworks.com/cortex/cortex-xdr/cortex-xdr-api/cortex-xdr-api-overview/get-started-with-cortex-xdr-apis. | True |
| API Key ID | For more information, see https://docs.paloaltonetworks.com/cortex/cortex-xdr/cortex-xdr-api/cortex-xdr-api-overview/get-started-with-cortex-xdr-apis. Only standard API key type is supported. | True |
| API Key | True | |
| Fetch indicators | False | |
| Indicator Reputation | Indicators from this integration instance will be marked with this reputation. | False |
| Source Reliability | Reliability of the source providing the intelligence data. | True |
| Traffic Light Protocol Color | The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the feed. | False |
| False | ||
| Bypass exclusion list | When selected, the exclusion list is ignored for indicators from this feed. This means that if an indicator from this feed is on the exclusion list, the indicator might still be added to the system. | False |
| Use system proxy settings | False | |
| Trust any certificate (not secure) | False | |
| Tags | Supports CSV values. | False |
| False | ||
| Feed Fetch Interval | False | |
| Indicators types to fetch | Indicator types to fetch. | True |
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.
xpanse-get-indicators
Retrieves a limited number of indicators.
Base Command
xpanse-get-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| limit | The maximum number of indicators to return. | Required |
| ip | Retrieve discovered IPs. Default is yes. | Optional |
| domain | Retrieve discovered domains. Default is yes. | Optional |
| certificate | Retrieve discovered certificates. Default is yes. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ASM.Indicators.Name | String | The name of the indicator. |
| ASM.Indicators.Description | String | The description of the indicator. |
| ASM.Indicators.Type | String | The type of the indicator. |
Command example
!xpanse-get-indicators limit=1 ip=yes certificate=no domain=no
Context Example
{
"ASM": {
"Indicators": {
"Description": "1.1.1.1 indicator of asset type IP from Cortex Xpanse",
"Name": "1.1.1.1",
"Type": "IP"
}
}
}
Human Readable Output
Xpanse indicators
Name Type Description 1.1.1.1 IP 1.1.1.1 indicator of asset type IP from Cortex Xpanse
Configuration parameters
url— Server URL (required)credentials— API Key ID (required)feed— Fetch indicatorsfeedReputation— Indicator ReputationfeedReliability— Source Reliability (required)tlp_color— Traffic Light Protocol ColorfeedExpirationInterval—feedBypassExclusionList— Bypass exclusion listproxy— Use system proxy settingsinsecure— Trust any certificate (not secure)feedTags— TagsfeedExpirationPolicy—feedFetchInterval— Feed Fetch IntervalindicatorTypes— Indicator types to fetch
Commands (1)
-
xpanse-get-indicatorsRetrieves a limited number of indicators.
from typing import Any import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa # pylint: disable=unused-wildcard-import # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DEFAULT_FEED_TAGS = {"XPANSE"} DEFAULT_ASSET_SEARCH_LIMIT = 5000 V1_URL_SUFFIX = "/public_api/v1" """ CLIENT CLASS """ class Client(BaseClient): """ Client class to interact with the service API """ def __init__(self, base_url: str, verify: bool, proxy: bool, feed_tags: list[str], tlp_color: str, headers: dict): """ Class initialization. """ super().__init__(base_url=base_url, verify=verify, proxy=proxy, headers=headers) self.feed_tags = feed_tags self.tlp_color = tlp_color self.verify = verify self.proxy = proxy self.headers = headers def list_asset_internet_exposure_request( self, search_params: list[dict] = [], search_from: int = 0, search_to: int = DEFAULT_ASSET_SEARCH_LIMIT, use_paging: bool = True, ) -> list: """Get a list of all your internet exposure assets using the '/assets/get_assets_internet_exposure/' endpoint. Args: search_params (list): list of search parameters to add to the API call body. search_from (int): Starting search index. search_to (int): Ending search index. use_paging (bool): whether to use paging or not (default is True) Returns: List: list containing dictionaries of internet exposure assets. """ body = { "request_data": { "filters": search_params, "search_to": int(search_to), "search_from": int(search_from), "use_page_token": True, } } full_response = [] while True: result = self._http_request( method="POST", url_suffix=f"{V1_URL_SUFFIX}/assets/get_assets_internet_exposure/", json_data=body ) data = result.get("reply", {}).get("assets_internet_exposure") if data: full_response.extend(data) if not use_paging: break pagination = result.get("reply", {}).get("next_page_token") if pagination is None: break body["request_data"]["next_page_token"] = pagination return full_response """ HELPER FUNCTIONS """ def create_x509_certificate_grids(string_object: Optional[str]) -> list: """ Creates a grid field related to the subject and issuer field of the x509 certificate object. Args: string_object (Optional[str]): A str in format of C=ZA, Inc.,ST=Western Cape,L=Cape Town,O=Thawte. Returns: list: The return value. A list of dict [{"title": "C", "data": "ZA"}]. """ result_grid_list = [] if string_object: key_value_pairs = string_object.split(",") for pair in key_value_pairs: result_grid = {} # '=' in pair means we extracted the right entries for k/v if "=" in pair: key, value = pair.split("=", 1) result_grid["title"] = key result_grid["data"] = value result_grid_list.append(result_grid) # If no '=' that means we had a ',' within the value we need to append else: result_grid_list[-1]["data"] = (result_grid_list[-1]["data"] + ", " + pair).replace("\\", "") return result_grid_list def map_indicator_fields(raw_indicator: dict[str, Any], asset_type: str) -> dict[str, Any]: """ Create indicator field mapping based on asset_type Args: raw_indicator (Dict[str, Any]): raw indicator as JSON. asset_type (str): indicator type Returns: Dict: dictionary of indicator field mappings. """ # name is a required API return parameter description = raw_indicator["name"] + " indicator of asset type " + asset_type + " from Cortex Xpanse" indicator_fields = {"internal": True, "description": description} if asset_type == "Domain": if domain_details := raw_indicator.get("domain_details"): domain_fields_mapping: dict = { "creationDate": "creationdate", "registryExpiryDate": "expirationdate", } for key, mapped_key in domain_fields_mapping.items(): if detail_value := domain_details.get(key): indicator_fields[mapped_key] = timestamp_to_datestring(detail_value) elif asset_type == "X509 Certificate" and (cert_details := raw_indicator.get("certificate_details")): cert_fields_mapping: dict = { "signatureAlgorithm": ("signaturealgorithm", None), "serialNumber": ("serialnumber", None), "validNotAfter": ("validitynotafter", timestamp_to_datestring), "validNotBefore": ("validitynotbefore", timestamp_to_datestring), "issuer": ("issuer", create_x509_certificate_grids), "subject": ("subject", create_x509_certificate_grids), } for key, (mapped_key, processing_func) in cert_fields_mapping.items(): if detail_value := cert_details.get(key): # Apply processing function if one is defined if processing_func: indicator_fields[mapped_key] = processing_func(detail_value) else: indicator_fields[mapped_key] = detail_value return indicator_fields def map_indicator_type(asset_type: str) -> str: """ Correlates asset_type to indicator type or returns "None" Args: asset_type (str): Xpanse asset type. Returns: str: indicator type or "None". """ asset_types_mapping = { "UNASSOCIATED_RESPONSIVE_IP": "IP", "DOMAIN": "Domain", "CERTIFICATE": "X509 Certificate", "CIDR": "CIDR", } return asset_types_mapping.get(asset_type, "None") def build_asset_indicators(client: Client, raw_indicators: list[dict[str, Any]]) -> list: """ Builds indicators JSON data in XSOAR expected format from the raw response. Args: client (Client): Xpanse client. raw_indicators (List[Dict[str, Any]]): raw indicators as JSON. Returns: List: list of indicators to be send to XSOAR. """ demisto.debug(f"Creating {len(raw_indicators)} asset indicators.") indicators: list = [] for raw_indicator in raw_indicators: asset_type = raw_indicator.get("asset_type", "None") indicator_type = map_indicator_type(asset_type) # Skip IPv6 responsive or not found type if raw_indicator.get("ipv6s") or indicator_type == "None": continue # name is a required API return parameter name = raw_indicator["name"] indicator_type = "DomainGlob" if "*" in name and indicator_type == "Domain" else indicator_type fields = map_indicator_fields(raw_indicator, indicator_type) # Add TLP color and feed tags if they exist if client.tlp_color: fields["trafficlightprotocol"] = client.tlp_color if client.feed_tags: fields["tags"] = client.feed_tags indicator = {"value": name, "type": indicator_type, "fields": fields, "rawJSON": raw_indicator} indicators.append(indicator) return indicators """ COMMAND FUNCTIONS """ def test_module(client: Client): # pragma: no cover """ Tests API connectivity and authentication' Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Raises exceptions if something goes wrong. :type client: ``Client`` :param Client: client to use :return: 'ok' if test passed, anything else will fail the test. :rtype: ``str`` """ client.list_asset_internet_exposure_request(search_to=1, use_paging=False) return_results("ok") def fetch_indicators(client: Client, limit: Optional[int] = None, asset_type: list = []) -> tuple | list: """ Fetch indicators from Xpanse API and create indicators in XSOAR. Args: client (Client): Xpanse client. limit (int): limt the number of indicators to return. asset_type (str): which asset_types to pull from API. Returns: List: list of indicators to be send to XSOAR. List: raw response from API. """ asset_list, asset_response = [], [] if asset_type == []: asset_list = ["DOMAIN", "CERTIFICATE", "UNASSOCIATED_RESPONSIVE_IP"] else: if "Domain" in asset_type: asset_list.append("DOMAIN") if "Certificate" in asset_type: asset_list.append("CERTIFICATE") if "IP" in asset_type: asset_list.append("UNASSOCIATED_RESPONSIVE_IP") if limit: # Had to add 1 to the limit to get the right return. asset_response = client.list_asset_internet_exposure_request( search_params=[{"field": "type", "operator": "in", "value": asset_list}], search_to=limit + 1, use_paging=False ) else: asset_response = client.list_asset_internet_exposure_request( search_params=[{"field": "type", "operator": "in", "value": asset_list}] ) assset_indicators = build_asset_indicators(client, asset_response) return assset_indicators, asset_response """ MAIN FUNCTION """ def get_indicators(client: Client, args: dict[str, Any]) -> CommandResults: """ Get indicators from Xpanse API, mainly for debug. Args: client (Client): Xpanse client. args (dict): all command arguments, usually passed from ``demisto.args()``. Returns: CommandResults: A ``CommandResults`` object that is then passed to ``return_results``, that contains Xpanse indicators. """ hr_list = [] asset_type = [] if argToBoolean(args.get("ip", "yes")): asset_type.append("IP") if argToBoolean(args.get("domain", "yes")): asset_type.append("Domain") if argToBoolean(args.get("certificate", "yes")): asset_type.append("Certificate") limit = arg_to_number(args.get("limit", None)) if limit and limit <= 0: raise ValueError("Limit must be a positive number.") if limit and limit > DEFAULT_ASSET_SEARCH_LIMIT: raise ValueError("Limit must be less that the API limit of " + str(DEFAULT_ASSET_SEARCH_LIMIT) + ".") if asset_type == []: raise ValueError("need to specify at least one asset type") indicators, raw_res = fetch_indicators(client=client, limit=limit, asset_type=asset_type) indicators = indicators[:limit] if isinstance(indicators, list) else [indicators] if indicators else [] for record in indicators: hr = {"Name": record.get("value"), "Type": record.get("type"), "Description": record["fields"]["description"]} hr_list.append(hr) return CommandResults( outputs=hr_list, outputs_prefix="ASM.Indicators", raw_response=raw_res, readable_output=tableToMarkdown("Xpanse indicators", hr_list, headers=["Name", "Type", "Description"]), outputs_key_field="Name", ) def main() -> None: # pragma: no cover """ main function """ params = demisto.params() base_url = params.get("url") verify_certificate = not params.get("insecure", False) proxy = params.get("proxy", False) # Append default tags. feed_tags = list(set(argToList(params.get("feedTags", []))) | DEFAULT_FEED_TAGS) tlp_color = params.get("tlp_color", "") indicator_types = params.get("indicatorTypes", []) creds = params.get("credentials", {}) api = creds.get("password", "") add_sensitive_log_strs(api) auth_id = creds.get("identifier", "") headers = {"Authorization": f"{api}", "x-xdr-auth-id": f"{auth_id}", "Content-Type": "application/json"} command = demisto.command() demisto.info(f"Command being called is {command}") try: client = Client( base_url=base_url, verify=verify_certificate, proxy=proxy, feed_tags=feed_tags, tlp_color=tlp_color, headers=headers ) if command == "test-module": test_module(client) elif command == "fetch-indicators": indicators, _ = fetch_indicators(client, asset_type=indicator_types) for iter_ in batch(indicators, batch_size=2000): try: demisto.createIndicators(iter_) except Exception: # find problematic indicator for indicator in iter_: try: demisto.createIndicators([indicator]) except Exception as err: demisto.debug( f"createIndicators Error: failed to create the following indicator: {indicator}\n {err}" ) raise elif command == "xpanse-get-indicators": return_results(get_indicators(client, demisto.args())) else: raise NotImplementedError(f'Command "{command}" is not implemented.') except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()