CreateIndicatorRelationship

This automation creates a relationship between indicator objects.

python · Base

Source

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

from CommonServerUserPython import *

BRAND = "XSOAR"
PAGE_SIZE = 2000
MAX_BATCH_SIZE = 100

# --------------------------------------------------- Helper functions---------------------------------------------


def find_indicators_by_query(query: str) -> List[dict]:
    """
    Search indicators in the system using a query.

    :type query: ``str``
    :param query: A query for the searchIndicators command.

    :return: A list of indicators that exist in the system.
    :rtype: ``list``
    """
    indicators: List[dict] = []
    search_indicators = IndicatorsSearcher(query=query, size=PAGE_SIZE)
    for ioc_res in search_indicators:
        fetched_indicators = ioc_res.get("iocs") or []
        indicators.extend(
            {"entity_b": ioc.get("value"), "entity_b_type": ioc.get("indicator_type")} for ioc in fetched_indicators
        )

    return indicators


def remove_existing_entity_b_indicators(entity_b_list: list, entity_b_query: str = "") -> list:
    """
    We would like to send to the createNewIndicator command only unexisting indicators, as a result this function
    checks for existing indicator in the system and removed from entity_b the existing indicators.

    :type entity_b_list: ``list``
    :param entity_b_list: A list of entity_b's arguments to the command.

    :type entity_b_query: ``str``
    :param entity_b_query: A query that has been given to the command.

    :return: A list of entity_b's that do not exist in the system that we will need to add.
    :rtype: ``list``
    """
    if entity_b_query:
        return []

    # Split entity_b_list into batches to prevent query length issues
    entity_b_list_to_remove = set(entity_b_list)
    existing_indicators: set[str] = set()

    for i in range(0, len(entity_b_list), MAX_BATCH_SIZE):
        batch = entity_b_list[i : i + MAX_BATCH_SIZE]
        query = " or ".join(f'value:"{entity}"' for entity in batch)
        batch_results = find_indicators_by_query(query)
        existing_indicators.update(indicator.get("entity_b", "") for indicator in batch_results)

    entity_b_list_to_remove.difference_update(existing_indicators)
    return list(entity_b_list_to_remove)


def create_relation_command_using_query(args: dict) -> List[EntityRelationship]:
    relationships = []
    indicators = find_indicators_by_query(args.get("entity_b_query", ""))
    for indicator in indicators:
        relationships.append(
            create_relationship(
                name=args.get("relationship", ""),
                entity_a=args.get("entity_a", ""),
                entity_a_type=args.get("entity_a_type", ""),
                entity_b=indicator.get("entity_b", ""),
                entity_b_type=indicator.get("entity_b_type", ""),
                source_reliability=args.get("source_reliability", ""),
                reverse_name=args.get("reverse_relationship", ""),
                first_seen=args.get("first_seen", ""),
                description=args.get("description", ""),
            )
        )
    return relationships


def create_relationships_with_args(args: dict) -> List[EntityRelationship]:
    relationships = []
    entity_b_list = argToList(args.get("entity_b"))
    for entity_b in entity_b_list:
        relationships.append(
            create_relationship(
                name=args.get("relationship", ""),
                entity_a=args.get("entity_a", ""),
                entity_a_type=args.get("entity_a_type", ""),
                entity_b=entity_b,
                entity_b_type=args.get("entity_b_type", ""),
                source_reliability=args.get("source_reliability", ""),
                reverse_name=args.get("reverse_relationship", ""),
                first_seen=args.get("first_seen", ""),
                description=args.get("description", ""),
            )
        )
    return relationships


def create_relationship(
    name: str,
    entity_a: str,
    entity_a_type: str,
    entity_b: str,
    entity_b_type: str,
    source_reliability: str = "",
    reverse_name: str = "",
    first_seen: str = "",
    description: str = "",
) -> EntityRelationship:
    return EntityRelationship(
        name=name,
        reverse_name=reverse_name,
        entity_a=entity_a,
        entity_a_type=entity_a_type,
        entity_b=entity_b,
        entity_b_type=entity_b_type,
        source_reliability=source_reliability,
        brand=BRAND,
        fields={
            "firstSeenBySource": first_seen or datetime.now().isoformat("T"),
            "lastSeenBySource": datetime.now().isoformat("T"),
            "description": description,
        },
    )


# --------------------------------------------------- Main functions---------------------------------------------


def validate_arguments(args: dict) -> Dict[str, str]:
    """Get the args of the command and validate the arguments.

    :return: raise an error if one of the validations fail.
    :rtype: ``None``
    """
    if len(argToList(args.get("entity_a"))) > 1:
        raise Exception("entity_a is a list, Please insert a single entity_a to create the relationship")
    if len(argToList(args.get("entity_b_type"))) > 1:
        raise Exception("entity_b_type is a list, Please insert a single type to create the relationship")
    if len(argToList(args.get("entity_a_type"))) > 1:
        raise Exception("entity_a_type is a list, Please insert a single type to create the relationship")
    if args.get("entity_b") and args.get("entity_b_type") and args.get("entity_b_query"):
        raise Exception("entity_b_query can not be used with entity_b and/or entity_b_type")
    if not args.get("entity_b_query") and not args.get("entity_b"):
        raise Exception("Missing entity_b in the create relationships")
    if args.get("entity_b") and not args.get("entity_b_type"):
        raise Exception("Missing entity_b_type in the create relationships")

    args["entity_a_type"] = FeedIndicatorType.indicator_type_by_server_version(args.get("entity_a_type"))
    if entity_b_type := args.get("entity_b_type"):
        args["entity_b_type"] = FeedIndicatorType.indicator_type_by_server_version(entity_b_type)
    return args


def create_indicators(args: dict):
    """
    When the create_indicator argument is set to True, create the new indicator first.

    :type args: ``dict``
    :param args: dict of arguments of the command.

    :return: return a list of errors, empty if no errors.
    :rtype: ``None``
    """
    entity_b_to_create = remove_existing_entity_b_indicators(argToList(args.get("entity_b")), args.get("entity_b_query", ""))
    indicators = []
    for entity_b in entity_b_to_create:
        indicator = {
            "value": entity_b,
            "type": args.get("entity_b_type"),
        }
        indicators.append(indicator)
    errors = []
    for indicator in indicators:
        res = demisto.executeCommand("createNewIndicator", indicator)
        if is_error(res[0]):
            errors.append("Error creating indicator - {}".format(res[0]["Contents"]))
    demisto.debug(
        f"Create Indicators From CreateIndicatorRelationship automation: {len(indicators) - len(errors)}"
        f" indicators were created with values {indicators!s}"
    )
    if errors:
        demisto.debug(
            f"Errors were found while creating indicators from CreateIndicatorRelationships automation:"
            f" {json.dumps(errors, indent=4)}"
        )


def create_relationships(args: dict) -> tuple[List[EntityRelationship], str]:
    """
    Create relationships from given arguments.

    :type args: ``dict``
    :param args: dict of arguments of the command.

    :return: relationships object with human readable output.
    :rtype: ``tuple``
    """
    if args.get("entity_b_query"):
        relationships = create_relation_command_using_query(args)
    else:
        relationships = create_relationships_with_args(args)

    if len(relationships) == 1:
        human_readable = f"Relationship for {args.get('entity_a')} was created successfully."
    elif len(relationships) > 1:
        human_readable = f"{len(relationships)!s} Relationships for {args.get('entity_a')} were created successfully."
    else:
        human_readable = f"Relationships were not created for {args.get('entity_a')}."
    return relationships, human_readable


""" MAIN FUNCTION """


def main():
    try:
        args = validate_arguments(demisto.args())
        if argToBoolean(args.get("create_indicator")):
            create_indicators(args)
        relationships, human_readable = create_relationships(args)
        return_results(CommandResults(readable_output=human_readable, relationships=relationships))
    except Exception as e:
        return_error(f"Failed to execute CreateIndicatorRelationships automation. Error: {e!s}")


""" ENTRY POINT """

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

README

This automation creates a relationship between indicator objects.

Script Data


Name Description
Script Type python3
Tags basescript
Cortex XSOAR Version 6.2.0

Used In


This script is used in the following playbooks and scripts.

  • ACTI Create Report-Indicator Associations

Inputs


Argument Name Description
entity_a The source of the relationship, for example 1.1.1.1. Only a single value is acceptable.
entity_a_type The source type of the relationship, for example IP. The value must be an accepted indicator type. Only a single value is acceptable.
entity_b A comma-separated list of destinations or second entity values, for example 3.3.3.3,2.2.2.2. This argument must be used with the entity_b_type argument and cannot be used in conjunction with the entity_b_query argument.
entity_b_type The destination type of the relationship, for example IP. Only a single value is acceptable. This argument must be used with the entity_b argument and cannot be used in conjunction with the entity_b_query argument.
entity_b_query The indicator query for all the entity_b results. The indicators that are the results of the query will be used as the destination of the relationship. For example type:ip AND tags:mytag. For more query examples, see Cortex XSOAR 6.13 or Cortex XSOAR 8 Cloud or Cortex XSOAR 8.7 On-prem This argument cannot be used in conjunction with the entity_b argument or the entity_b_type argument.
relationship The name of relationship to be created.
reverse_relationship The reverse name of relationship to be created. If the argument isn’t provided by the user, the default reverse relation will be created.
source_reliability Reliability of the source providing the intelligence data.
description Free text description to add to the relationship.
first_seen The time the relationship was seen. If left empty, the default value will be the time the relationship was created. Format (YYYY-MM-DDTHH:MM:SSZ). For example: 2020-02-02T19:00:00Z
create_indicator True, if the non-existing indicators will be created according to the specified entities and their types. Default is false.

Outputs


There are no outputs for this script.

filter (Cortex XSOAR 6.13) or filter (Cortex XSOAR 8 Cloud) or Cortex XSOAR 8.7 On-prem.