CreateIndicatorRelationship
This automation creates a relationship between indicator objects.
- Type
- python
- Pack
- 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.