GCPOffendingFirewallRule

Determine potential offending firewall rules in GCP based on port, protocol and possibly target tags (network tags). Considerations: - At this time this automation only find potential offending rules and not necessarily the rule that is matching traffic.

python · GCP Enrichment and Remediation

Source

import traceback
from typing import Any

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

""" STANDALONE FUNCTION """


def is_port_in_range(port_range: str, port: str) -> bool:
    """
    Breaks a string port range (i.e. '20-25') into integers for comparison
    Args:
        port_range (str): string based port range from the GCP firewall rule.
        port (str): port supplied in script args.

    Returns:
        bool: whether there was a match between the supplied port in args and the supplied range.
    """
    start, end = port_range.split("-")
    return int(start) <= int(port) <= int(end)


def is_there_traffic_match(port: str, protocol: str, rule: dict, network_tags: list) -> bool:
    """
    Determines if there's a match between the supplied port, protocol, and possible target tag combination
    and the GCP firewall rule.
    The function checks:
    if the rule is an ingress rule,
    if the source is from the internet (0.0.0.0/0),
    if the rule is enabled, and if it's an allow rule.
    It also checks if the target tags are relevant (if they show up in keys or tag match), and if the protocol and ports match
    the supplied protocol and port.

    Args:
        port (str): The port supplied in script args.
        protocol (str): The protocol supplied in script args. This should be a string representing a
                        network protocol (e.g., 'tcp', 'udp', 'icmp').
        rule (dict): A dictionary representing a GCP firewall rule. This is pulled from the integration command.
        network_tags (list): A list of network tags. This can be empty.

    Returns:
        bool: True if there's a match between the supplied port, protocol, and possible target tag combination
              and the GCP firewall rule. False otherwise.
    """
    # Match rule needs to be direction ingress, source from internet (0.0.0.0/0), enabled and an allow rule.
    if (
        rule.get("direction") == "INGRESS"
        and "0.0.0.0/0" in rule.get("sourceRanges", [])
        and rule.get("disabled") is False
        and "allowed" in rule
    ):
        # Test if targetTags are relevant or not (if show up in keys or tag match)
        target_tags_verdict = "targetTags" not in rule or len(set(rule.get("targetTags", [])) & set(network_tags)) > 0
        for entry in rule["allowed"]:
            # Match is all protocol AND either no target tags OR target tags match
            if entry.get("IPProtocol") == "all" and target_tags_verdict:
                return True
            # Complicated because just {'IPProtocol': 'udp'} means all udp ports
            # therefore if protocol match but no ports, this is a match
            elif entry.get("IPProtocol") == protocol.lower() and "ports" not in entry:
                return True
            # Else need to go through all ports to see if range or not
            elif entry.get("IPProtocol") == protocol.lower() and "ports" in entry:
                for port_entry in entry.get("ports", []):
                    if "-" in port_entry:
                        res = is_port_in_range(port_entry, port)
                        if res and target_tags_verdict:
                            return True
                    else:
                        if port == port_entry and target_tags_verdict:
                            return True
    return False


""" COMMAND FUNCTION """


def gcp_offending_firewall_rule(args: dict[str, Any]) -> CommandResults:
    """
    Determine potential offending firewall rules in GCP based on port, protocol and possibly target tags (network tags).
    Args:
        args (dict): Command arguments from XSOAR.

    Returns:
        CommandResults: outputs, readable outputs and raw response for XSOAR.

    """

    project_id = args["project_id"]
    network_url = args["network_url"]
    port = args["port"]
    protocol = args["protocol"]
    network_tags = args.get("network_tags", [])

    # Using `demisto.executeCommand` instead of `execute_command` because for
    # multiple integration instances we can expect one too error out.
    network_url_filter = f'network="{network_url}"'
    fw_rules = demisto.executeCommand("gcp-compute-list-firewall", {"project_id": project_id, "filters": network_url_filter})
    fw_rules_returned = [instance for instance in fw_rules if (not isError(instance) and instance.get("Contents", {}).get("id"))]
    if not fw_rules_returned:
        return CommandResults(readable_output="Could not find specified firewall info.")
    final_match_list = []
    for rule in fw_rules_returned[0].get("Contents", {}).get("items", []):
        if is_there_traffic_match(port, protocol, rule, network_tags):
            final_match_list.append(rule["name"])
    if not final_match_list:
        return CommandResults(readable_output="Could not find any potential offending firewall rules.")

    return CommandResults(
        outputs={"GCPOffendingFirewallRule": final_match_list},
        raw_response={"GCPOffendingFirewallRule": final_match_list},
        readable_output=f"Potential Offending GCP Firewall Rule(s) Found: {final_match_list}",
    )


""" MAIN FUNCTION """


def main():
    try:
        return_results(gcp_offending_firewall_rule(demisto.args()))
    except Exception as ex:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute GCPOffendingFirewallRule. Error: {ex!s}")


""" ENTRY POINT """


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()

README

Determine potential offending firewall rules in GCP based on port, protocol and possibly target tags (network tags).

Considerations:

  • At this time this automation only find potential offending rules and not necessarily the rule that is matching traffic.

Script Data


Name Description
Script Type python3
Cortex XSOAR Version 6.8.0

Used In


This script is used in the following playbooks and scripts.

  • GCP - Enrichment - EXPANDR-3608
  • GCP - Enrichment

Inputs


Argument Name Description
project_id The project to look up firewall rules in. The project ID instead of the project number. No need to supply `projects/` before the ID (i.e., use `project-name` instead of `projects/project-name` or `projects/111111111111`).
network_url The url of the network objects to lookup firewall rules in. This will be the url of the network and not just the name (i.e. https://www.googleapis.com/compute/v1/projects/<project_name>/global/networks/<network_name>).
port Port to match traffic on for firewall rules.
protocol Protocol to match traffic on for firewall rules.
network_tags Network tags on GCP VM instance to match rules based on target tag (optional).

Outputs


Path Description Type
GCPOffendingFirewallRule One or more potential offending firewall rules in GCP based on port, protocol and possibly target tags (network tags). Unknown