CortexListAppSecAssetCoverage
Retrieves Application Security Posture Management (ASPM) coverage information for assets, including scan status across different security scanning types (vulnerabilities, secrets, code weakness, IaC, malware, CICD). Returns coverage statistics, histograms, and detailed asset information. Filters can be provided to return a subset of the results. Statistics are generated over the filtered set.
python · Core
Source
from typing import Any import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 # Columns representing different scanner coverage categories SCANNER_COLUMNS: list[str] = [ "is_scanned_by_vulnerabilities", "is_scanned_by_code_weakness", "is_scanned_by_secrets", "is_scanned_by_iac", "is_scanned_by_malware", ] # Valid input arguments for this automation VALID_ARGS: set[str] = { "asset_id", "asset_name", "business_application_names", "status_coverage", "is_scanned_by_vulnerabilities", "is_scanned_by_code_weakness", "is_scanned_by_secrets", "is_scanned_by_iac", "is_scanned_by_malware", "is_scanned_by_cicd", "asset_type", "asset_provider", "limit", } def get_command_results(command: str, args: dict[str, Any]) -> dict[str, Any]: """Execute a Cortex XSOAR command and return the parsed result.""" demisto.debug(f"Executing command: {command} with args: {args}") results = demisto.executeCommand(command, args) demisto.debug(f"Raw results from {command}: {results}") if not results or not isinstance(results, list): demisto.debug(f"No valid results returned from {command}") return {} result = results[0] if not isinstance(result, dict): demisto.debug(f"First result from {command} is not a dict") return {} if result.get("Type") == EntryType.ERROR: error_msg = result.get("Contents", "Unknown error occurred.") demisto.error(f"Error returned from {command}: {error_msg}") raise Exception(error_msg) contents = result.get("Contents") demisto.debug(f"Parsed contents from {command}: {contents}") if isinstance(contents, dict): return contents.get("reply", {}) return {} def transform_scanner_histograms_outputs(asset_coverage_histograms: dict[str, Any]) -> tuple[dict[str, dict[str, float]], float]: """Transform scanner histogram data into a summarized structure.""" demisto.debug(f"Transforming scanner histogram data: {asset_coverage_histograms}") def get_count(data: list[dict[str, Any]], value: str) -> int: count = next((item.get("count", 0) for item in data if item.get("value") == value), 0) demisto.debug(f"Count for {value}: {count} in data: {data}") return count output: dict[str, dict[str, float]] = {} total_enabled = total_relevant = 0 for column in SCANNER_COLUMNS: data = asset_coverage_histograms.get(column, []) demisto.debug(f"Processing column {column} with data: {data}") enabled_count = get_count(data, "ENABLED") disabled_count = get_count(data, "DISABLED") relevant_count = enabled_count + disabled_count coverage_pct = (enabled_count / relevant_count) if relevant_count else 0.0 output[column] = { "enabled": enabled_count, "disabled": disabled_count, "coverage_percentage": coverage_pct, } total_enabled += enabled_count total_relevant += relevant_count overall_coverage = (total_enabled / total_relevant) if total_relevant else 0.0 demisto.debug(f"Scanner histogram transformation result: {output}, overall: {overall_coverage}") return output, overall_coverage def transform_status_coverage_histogram_output(data: dict[str, Any]) -> dict[str, dict[str, int | float]]: """Transform the status coverage histogram into a flattened dictionary.""" demisto.debug(f"Transforming status coverage histogram: {data}") mapping = { "PARTIALLY SCANNED": "partially_scanned", "FULLY SCANNED": "fully_scanned", "NOT SCANNED": "not_scanned", } output: dict[str, int | float] = {} for item in data.get("status_coverage", []): label = mapping.get(item.get("value"), str(item.get("value", "")).lower().replace(" ", "_")) output[f"{label}_count"] = item.get("count", 0) output[f"{label}_percentage"] = item.get("percentage", 0.0) demisto.debug(f"Status histogram output: {output}") return {"aspm_status_coverage": output} def main() -> None: """Main execution entry point for the Cortex XSOAR script.""" try: args = demisto.args() demisto.debug(f"Received script arguments: {args}") extra_args = set(args.keys()) - VALID_ARGS if extra_args: demisto.error(f"Unexpected args found: {extra_args}") raise ValueError(f"Unexpected args found: {extra_args}") # Fetch asset coverage details demisto.debug("Fetching asset coverage via core-get-asset-coverage") asset_coverage = get_command_results("core-get-asset-coverage", args) demisto.debug(f"Asset coverage received: {asset_coverage}") assets = asset_coverage.get("DATA", []) # Fetch coverage histogram data args["columns"] = ", ".join(SCANNER_COLUMNS + ["status_coverage"]) demisto.debug(f"Fetching histogram using args: {args}") asset_coverage_histograms = get_command_results("core-get-asset-coverage-histogram", args) demisto.debug(f"Histogram result: {asset_coverage_histograms}") scanner_histograms, coverage_percentage = transform_scanner_histograms_outputs(asset_coverage_histograms) status_histogram = transform_status_coverage_histogram_output(asset_coverage_histograms) outputs = { "total_filtered_assets": asset_coverage.get("FILTER_COUNT"), "number_returned_assets": len(assets), "coverage_percentage": coverage_percentage, "Metrics": {**scanner_histograms, **status_histogram}, "Asset": assets, } demisto.debug(f"Final output: {outputs}") return_results( CommandResults( outputs=outputs, outputs_prefix="Core.Coverage", raw_response=outputs, ) ) except Exception as e: demisto.error(f"Exception occurred: {str(e)}") return_error(f"Failed to execute script.\nError:\n{str(e)}") # Script entry point if __name__ in ("__main__", "__builtin__", "builtins"): main()