InferWhetherServiceIsDev
Identify whether the service is a "development" server. Development servers have no external users and run no production workflows. These servers might be named "dev", but they might also be named "qa", "pre-production", "user acceptance testing", or use other non-production terms. This automation uses both public data visible to anyone (`active_classifications` as derived by Xpanse ASM) as well as checking internal data for AI-learned indicators of development systems (`asm_tags` as derived from integrations with non-public systems).
python · Cortex Attack Surface Management
Source
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import traceback from collections.abc import Mapping from typing import Any from collections.abc import Callable DEV_ENV_CLASSIFICATION = "DevelopmentEnvironment" EXACT_DEV_MATCH = ["dv", "noprod", "np", "ppe"] PARTIAL_DEV_MATCH = [ "stg", "stag", "qa", "quality", "test", "tst", "exp", "non_prod", "non-prod", "nonprod", "nprd", "n-prd", "npe", "npd", "pre_prod", "pre-prod", "preprod", "pprd", "pov", "proof of value", "poc", "sbx", "sandbox", "internal", "validation", "lab", ] EXACT_PROD_MATCH = ["pr"] PARTIAL_PROD_MATCH = ["prod", "prd", "release", "live"] def _canonicalize_string(in_str: str) -> str: """ Converts `in_str` to a canonical-for-dev-evaluation version of itself through: * lowercasing * stripping whitespace (space and tab) * stripping quotation marks (" and ') """ return in_str.lower().strip(" \t\"'") def get_indicators_from_list(observed_list: list, is_indicator_match: Callable, comparison_type: str) -> list: """ Returns list of matches based on criteria. Args: observed_list (List[str]): list of tags to process. is_indicator_match (callable): what function to call depending on dev or prod checking. comparison_type (str): if comparing list of dictionaries or a list of strings. Returns: list: list of matches based on exact/partial dev criteria. """ indicators = [] for list_entry in observed_list: if comparison_type == "dictionary": if not isinstance(list_entry, Mapping): demisto.info(f"Ignoring item because it is not a mapping: {list_entry}") else: if "key" not in list_entry or "value" not in list_entry: demisto.info(f"Ignoring item because it lacks the keys 'key' and/or 'value': {sorted(list_entry.keys())}") else: key = _canonicalize_string(list_entry.get("key", "")) value = _canonicalize_string(list_entry.get("value", "")) if (("env" in key) or (key in ("stage", "function", "lifecycle", "usage", "tier"))) and is_indicator_match( value ): indicators.append(list_entry) elif comparison_type == "string": value = _canonicalize_string(list_entry) if is_indicator_match(value): indicators.append(list_entry) else: break return indicators def is_dev_indicator(value: str) -> bool: """ Returns boolean based on match on exact/partial dev criteria. Args: value (str): value of the kv pair of tag. Returns: bool: whether there was a match based on exact/partial dev criteria. """ return ( ("dev" in value and "devops" not in value) or ("uat" in value and "prod" not in value) or any(m == value for m in EXACT_DEV_MATCH) or any(m in value for m in PARTIAL_DEV_MATCH) ) def is_prod_indicator(value: str) -> bool: """ Returns boolean based on match on exact/partial prod criteria. Args: value (str): value of the kv pair of tag. Returns: bool: whether there was a match based on exact/partial prod criteria. """ # Check if matches dev first for values like "non-production" if is_dev_indicator(value): return False else: return any(m == value for m in EXACT_PROD_MATCH) or any(m in value for m in PARTIAL_PROD_MATCH) def get_indicators_from_external_classification(classifications: list[str]) -> list: """ Returns whether any of the classification strings indicate that this service is a development environment. The Xpanse ASM classification strings are a defined vocabulary of facts that are derivable from the publicly observed service without joining on any additional data. Args: classifications (List[str]): list of Xpanse ASM classification terms (a defined vocabulary) Example value for `classifications`: ["RdpServer", "SelfSignedCertificate"] Returns: List: whether there is an indication within `classifications` that the described system is used for development. Empty list means no matches. """ ext_classification_match = [DEV_ENV_CLASSIFICATION] if DEV_ENV_CLASSIFICATION in classifications else [] return ext_classification_match def determine_reason(external_indicators: list, tags: list, hierarchy: list, provider: str) -> str: """ Craft the 'reason' for the final verdict of "development" server or not. Args: external_indicators (list): to determine there is an external service classification match. Empty list means no matches. tags (list): list of matches of tags with DEV or PROD characteristics. hierarchy (list): list of matches of hierarchy information with DEV or PROD characteristics. provider (str): provider of the asset as returned by Xpanse. Returns: str: complete `reason` string to be added to the gridfield. """ reason_parts = [] if len(external_indicators) == 1: reason_parts.append("external classification of " + DEV_ENV_CLASSIFICATION) for tag in tags: reason_parts.append("tag {" + f"{tag.get('key')}: {tag.get('value')}" + "} from " + tag.get("source")) for match in hierarchy: if provider: reason_parts.append("infrastructure hierarchy information `" + f"{match}" + "` from " + provider) else: reason_parts.append("infrastructure hierarchy information `" + f"{match}" + "`") reason_final = "match on " for reason in reason_parts: reason_final += reason + ", " # Strip last ',' reason_final = reason_final[:-2] # Replace last ','' with ' and ' reason_final = " and ".join(reason_final.rsplit(", ", 1)) return reason_final def final_decision( external_indicators: list, dev_tags: list, prod_tags: list, dev_hierarchy: list, prod_hierarchy: list, provider: str ) -> dict: """ Final decision to be set in gridfield. Args: external_indicators (list): list of matches of external service classification match. dev_tags (list): list of matches of tags with DEV characteristics. prod_tags (list): list of matches of tags with PROD characteristics. dev_hierarchy (list): list of matches of hierarchy information with DEV characteristics. prod_hierarchy (list): list of matches of hierarchy information with PROD characteristics. provider (str): provider of the asset as returned by Xpanse. Returns: dict: dictionary to be added to gridfield. """ final_dict: dict[str, Any] = {} if (len(external_indicators) == 1 or len(dev_tags + dev_hierarchy) > 0) and len(prod_tags + prod_hierarchy) == 0: final_dict["result"] = True final_dict["confidence"] = "Likely Development" reason_final = determine_reason(external_indicators, dev_tags, dev_hierarchy, provider) final_dict["reason"] = reason_final elif (len(external_indicators) == 1 or len(dev_tags + dev_hierarchy) > 0) and len(prod_tags + prod_hierarchy) > 0: final_dict["result"] = False final_dict["confidence"] = "Conflicting Information" reason_final = determine_reason(external_indicators, dev_tags + prod_tags, dev_hierarchy + prod_hierarchy, provider) final_dict["reason"] = reason_final elif (len(external_indicators) == 0 and len(dev_tags + dev_hierarchy) == 0) and len(prod_tags + prod_hierarchy) > 0: final_dict["result"] = False final_dict["confidence"] = "Likely Production" reason_final = determine_reason(external_indicators, prod_tags, prod_hierarchy, provider) final_dict["reason"] = reason_final else: final_dict["result"] = False final_dict["confidence"] = "Not Enough Information" final_dict["reason"] = "Neither dev nor prod indicators found" # Create a more human readable table for war room. if final_dict.get("result", False): final_dict["result_readable"] = "The service is development" else: final_dict["result_readable"] = "The service is not development" return final_dict """ MAIN FUNCTION """ def main(): """ Identifies whether the service is a "development" server. Development servers have no external users and run no production workflows. These servers might be named "dev", but they might also be named "qa", "pre-production", "user acceptance testing", or use other non-production terms. This automation uses both public data visible to anyone (`active_classifications` as derived by Xpanse ASM) as well as checking internal data for AI-learned indicators of development systems (`asm_tags` as derived from integrations with non-public systems). Args: asm_tags (List[Dict[str, Any]]): list of key-value dictionaries; each dictionary within the list must contain the keys "key" and "value"; the values are arbitrary Example value for `observed_list`: [{"key": "env", "source": "AWS", "value": "dev"}, {"key": "Name", "source": "AWS", "value": "ssh-ec2-machine-name"}] active_classifications (List[str]): list of Xpanse ASM classification terms (a defined vocabulary) Example value for `classifications`: ["RdpServer", "SelfSignedCertificate"] hierarchy_info (List[str]): list of infrastructure hierarchy information to include CSPs (such GCP folder, AWS account and Azure subscription names, which can indicate the enrivonemnt is dev) Example value for `observed_list`: ["Engineering-dev","dev-env-01"] provider (str): Provider of the asset as returned by Xpanse Example value for `provider`: "AWS" Returns: No return value. Two side effects: 1. Update the "alert" context with a values for `asmdevcheckdetails` gridfield. The `result` key is set as True if there is a consistent indicator of dev-ness in the arguments,and is False otherwise. `confidence` and `reason` explain more about why the `result` key was marked as it is 2. Update the warroom with a statement of whether the service is dev """ try: args = demisto.args() internal_tags: list[dict[str, Any]] = argToList(args.get("asm_tags", [{}])) dev_kv_indicators = get_indicators_from_list(internal_tags, is_dev_indicator, "dictionary") prod_kv_indicators = get_indicators_from_list(internal_tags, is_prod_indicator, "dictionary") hierarchy_info = argToList(args.get("hierarchy_info", [])) dev_hierarchy_indicators = get_indicators_from_list(hierarchy_info, is_dev_indicator, "string") prod_hierarchy_indicators = get_indicators_from_list(hierarchy_info, is_prod_indicator, "string") external_active_classifications: list[str] = argToList(args.get("active_classifications", [])) external_indicators = get_indicators_from_external_classification(external_active_classifications) provider: str = args.get("provider", None) decision_dict = final_decision( external_indicators, dev_kv_indicators, prod_kv_indicators, dev_hierarchy_indicators, prod_hierarchy_indicators, provider, ) demisto.executeCommand("setAlert", {"asmdevcheckdetails": [decision_dict]}) output = tableToMarkdown("Dev Check Results", decision_dict, ["result_readable", "confidence", "reason"]) return_results(CommandResults(readable_output=output)) except Exception as ex: demisto.error(traceback.format_exc()) # print the traceback return_error(f"Failed to execute InferWhetherServiceIsDev. Error: {str(ex)}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()
README
Identify whether the service is a “development” server. Development servers have no external users and run no production workflows. These servers might be named “dev”, but they might also be named “qa”, “pre-production”, “user acceptance testing”, or use other non-production terms. This automation uses both public data visible to anyone (active_classifications as derived by Xpanse ASM) as well as checking internal data for AI-learned indicators of development systems (asm_tags as derived from integrations with non-public systems).
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Cortex XSOAR Version | 6.5.0 |
Used In
This script is used in the following playbooks and scripts.
- Cortex ASM - Enrichment
Inputs
| Argument Name | Description |
|---|---|
| asm_tags | Array of key-value objects. Each object within the array must contain the keys “Key” and “Value” to be considered. The values associated with those keys can be arbitrary. Example: [{“Key”: “env”, “Value”: “dev”}, {“Key”: “Name”, “Value”: “ssh-ec2-machine-name”}]. |
| active_classifications | Array of strings representing the Xpanse ASM “active classifications” for the service. Example: [“RdpServer”, “SelfSignedCertificate”]. |
| hierarchy_info | Infrastructure hierarchy information to include CSPs (such GCP folder, AWS account and Azure subscription names, which can indicate the environment is dev). |
| provider | Provider of the asset as returned by Xpanse. |
Outputs
There are no outputs for this script.