ThreatX

The ThreatX integration allows automated enforcement and intel gathering actions.

Network Security · ThreatX

Details

IDThreatX
ProviderA10 Networks
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

Use the ThreatX integration to enrich intel and automate enforcement actions on the ThreatX Next Gen WAF.

 

Use Cases

  • Add and remove CIDR ranges and IP addresses to various block lists or the allow list
  • Gather Entity metadata for intel enrichment and DBot scoring
  • Set Entity notes for SOC integration or further automation

 

Configure ThreatX on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for ThreatX.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Customer Name: Contact the ThreatX SOC for your Customer Name
    • ThreatX Server URL (e.g., https://provision.threatx.io/ )
    • API Key: Contact the ThreatX SOC for your API Key
    • DBot Score Threshold: Set the threshold ThreatX Risk score (1 to 100) which will be translated to Malicious DBot scores. Default is 70.
    • Use system proxy settings
    • Trust any certificate (not secure)
  4. Click Test to validate the URLs, token, and connection.

 

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.

 

  1. Temporarily block an IP address or CIDR: threatx-block-ip
  2. Unblock an IP address or CIDR: threatx-unblock-ip
  3. Block list an IP address or CIDR: threatx-blacklist-ip
  4. Remove an IP address or CIDR from the block list: threatx-unblacklist-ip
  5. Add an IP address or CIDR to allow list: threatx-whitelist-ip
  6. Remove an IP address or CIDR from the allow list: threatx-unwhitelist-ip
  7. Get entity information: threatx-get-entities
  8. Get entity notes: threatx-get-entity-notes
  9. Add a note to an entity: threatx-add-entity-note

 

1. Temporarily block an IP address or CIDR


 

Temporarily blocks an IP address or CIDR.

 

Base Command

 

threatx-block-ip

 

Input
Argument Name Description Required
ip IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". Required
description The description of the IP record in the block list. The default value is: "Added by ThreatX Cortex XSOAR Integration". Optional

 

Context Output 
Path Type Description
IP.Address string IP address or CIDR that was blocked.

 

Command Example 
!threatx-block-ip ip=12.12.12.12

 

Context Example
{
    "IP": [
        {
            "Address": "12.12.12.12"
        }
    ]
}

 

Human Readable Output
Result
Blocklist entry for ip 12.12.12.12 added

  

2. Unblock an IP address or CIDR


Unblocks a blocked IP address or CIDR.

Base Command

threatx-unblock-ip

Input 
Argument Name Description Required
ip IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". Required

  

Context Output
Path Type Description
IP.Address string IP address or CIDR that was unblocked.

 

Command Example 
!threatx-unblock-ip ip=12.12.12.12

 

Context Example
{
    "IP": [
        {
            "Address": "12.12.12.12"
        }
    ]
}

 

Human Readable Output
Result
Block list entry for ip 12.12.12.12 removed

 

3. Block list an IP address or CIDR 


Adds an IP address or CIDR to the block list. 

Base Command

threatx-blacklist-ip

Input
Argument Name Description Required
ip IP address or CIDR in CSV format, for example: "10.1.1.1,10.1.1.0/24". Required
description The description of the IP record in the block list. The default value is: "Added by ThreatX Cortex XSOAR Integration". Optional

 

Context Output
Path Type Description
IP.Address string IP address or CIDR that was added to the blacklist.

 

Command Example
!threatx-blacklist-ip ip=12.12.12.12

 

Context Example
{
    "IP": [
        {
            "Address": "12.12.12.12"
        }
    ]
}

 

Human Readable Output
Result
Block list entry for ip 12.12.12.12 added

 

4. Remove an IP address or CIDR from the block list


Removes an IP address or CIDR from the block list.

Base Command

threatx-unblacklist-ip

Input
Argument Name Description Required
ip IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". Required

 

Context Output 
Path Type Description
IP.Address string IP address or CIDR that was removed from the block list.

 

Command Example
!threatx-unblacklist-ip ip=12.12.12.12
Context Example
{
    "IP": [
        {
            "Address": "12.12.12.12"
        }
    ]
}

 

Human Readable Output
Result
Block list entry for ip 12.12.12.12 removed

 

5. Add an IP address or CIDR to allow list


 Adds an IP address or CIDR to the allow list.

Base Command

threatx-whitelist-ip

Input
Argument Name Description Required
ip IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". Required
description The description of the IP record in the allow list. The default value is: "Added by ThreatX Cortex XSOAR Integration". Optional

 

Context Output 
Path Type Description
IP.Address string IP address or CIDR was added to the allow list.

 

Command Example 
!threatx-whitelist-ip ip=12.12.12.12

 

Context Example
{
    "IP": [
        {
            "Address": "12.12.12.12"
        }
    ]
}

 

Human Readable Output
Result
Whitelist entry for ip 12.12.12.12 added

 

6. Remove an IP address or CIDR from the allow list


Removes an IP address or CIDR from the allow list. 

Base Command

threatx-unwhitelist-ip

Input
Argument Name Description Required
ip IP address or CIDR, for example: "10.1.1.1" or "10.1.1.0/24". Required

 

Context Output 
Path Type Description
IP.Address string IP address or CIDR that was removed from the allow list.

 

Command Example
!threatx-unwhitelist-ip ip=12.12.12.12

 

Context Example
{
    "IP": [
        {
            "Address": "12.12.12.12"
        }
    ]
}

 

Human Readable Output
Result
Allow list entry for ip 12.12.12.12 removed

 

7. Get entity information


Returns high-level entity information by Entity ID, Entity Name, or Entity IP.

Base Command

threatx-get-entities

Input
Argument Name Description Required
timeframe Look-back timeframe for the query. Options are 1-Hour, 1-Day, 1-Week, 1-Month, or 3-Months. Note: long look-back timeframes for a large number of Entities can timeout. Required
entity_name CSV list of Entity names. Optional
entity_id CSV list of Entity ID hashes. Optional
entity_ip CSV list of Entity IP addresses. Optional

 

Context Output 
Path Type Description
Threatx.Entity.ID string ID hash of the Entity
Threatx.Entity.Name string Name of the Entity
Threatx.Entity.IP string IP address of the Entity
Threatx.Entity.Risk integer Risk score of the Entity

 

Command Example 
!threatx-get-entities timeframe=1-Day entity_name=CynicalGraaf,OveconfidentRas

 

Context Example
{
    "Threatx": {
        "Entity": [
            {
                "ID": "1061035762581303669",
                "Name": "OveconfidentRas",
                "Risk": 0,
                "IP": [
                    201.93.212.87
                ]
            },
            {
                "ID": "566056709675514809",
                "Name": "CynicalGraaf",
                "Risk": 0,
                "IP": [
                    1.125.227.13
                ]
            }
        ]
    }
}

 

Human Readable Output
ThreatX Risk Score IP Addresses Name ID
0 201.93.212.87 OveconfidentRas 1061035762581303669
55 1.125.227.13 CynicalGraaf 566056709675514809

  

8. Get entity notes


Returns the notes attached to an entity by Entity ID. 

Base Command

threatx-get-entity-notes

Input
Argument Name Description Required
entity_id ID hash of the Entity. To retrieve this value, run the threatx-get-entities command. Required

 

Context Output
Path Type Description
Threatx.Entity.ID string ID hash of the Entity
Threatx.Entity.Note.content string Content of the Note
Threatx.Entity.Note.timestamp string Timestamp of the Note
Threatx.Entity.Note.username string Author of the Note

 

Command Example 
!threatx-get-entity-notes entity_id=566056709675514809

 

Context Example
{
    "Threatx": {
        "Entity": [
            {
                "ID": "566056709675514809",
                "Note": [
                    {
                        "content": "Demisto test note.",
                        "entity_id": "566056709675514809",
                        "timestamp": "2019-05-31 18:41:09",
                        "username": "user@domain.com"
                    }
                ]
            }
        ]
    }
}

 

Human Readable Output
Username Timestamp Entity Id Content
user@domain.com 2019-05-31 18:41:09 566056709675514809 Demisto test note.
user@domain.com 2019-05-30 23:36:23 566056709675514809 this is a test note
user@domain.com 2019-05-12 21:36:12 566056709675514809 Another test note
user@domain.com 2019-05-12 21:34:48 566056709675514809 test-note-from-demisto

  

9. Add a note to an entity


Adds a new note to an entity.

Base Command

threatx-add-entity-note

Input
Argument Name Description Required
entity_id ID hash of the Entity. To retrieve this value, run the threatx-get-entities command. Required
message Contents of the note. Required

 

Context Output

There is no context output for this command. 

Command Example
!threatx-add-entity-note entity_id=566056709675514809 message="test note."

 

Human Readable Output
Result
Note for Entity<id=566056709675514809> created

Configuration parameters

  • url — ThreatX Server URL (e.g., https://provision.threatx.io) (required)
  • customer_name — Customer Name
  • api_key — API Key
  • credentials — Customer Name
  • dbot_threshold — IP Threshold. Minimum risk score from ThreatX to consider the IP malicious. (required)
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)

Commands (9)

  • threatx-add-entity-note

    Add a new note to the entity. For example: !threatx-add-entity-note entity_id=566056709695514809 message="test note"

  • threatx-blacklist-ip

    Adds an IP address or CIDR to the block list.

  • threatx-block-ip

    Temporarily blocks an IP address or CIDR. Default is 30 minutes.

  • threatx-get-entities

    Get high-level Entity information using the Entity ID, Entity Name, or Entity IP. For example: !threatx-get-entities timeframe=1-Day entity_name=CynicalGraaf,MJ12Bot entity_id= 566056709675514809 entity_ip=12.12.12.12,14.14.14.14. Note: long look-back timeframes for a large number of Entities can timeout.

  • threatx-get-entity-notes

    Returns the notes attached to an Entity, by Entity ID.

  • threatx-unblacklist-ip

    Removes an IP or CIDR from the block list.

  • threatx-unblock-ip

    Unblocks a blocked IP address or CIDR.

  • threatx-unwhitelist-ip

    Removes an IP address or CIDR from the allow list.

  • threatx-whitelist-ip

    Adds an IP address or CIDR to the allow list.

import demistomock as demisto
from CommonServerPython import *

from CommonServerUserPython import *

""" IMPORTS """
import socket
import struct
import time
from operator import itemgetter

import requests
import urllib3

# disable insecure warnings
urllib3.disable_warnings()

""" GLOBAL VARS """
CUSTOMER_NAME = ""
API_KEY = ""
BASE_URL = ""
DBOT_THRESHOLD = 70
USE_SSL = True

""" HELPER FUNCTIONS """


def http_request(url_suffix, commands=None):
    state = demisto.getIntegrationContext()

    session_token = state.get("session_token")

    if url_suffix != "/login":
        demisto.info(f"running request with url={BASE_URL + url_suffix} with commands={commands}")
        data = {"token": session_token, "customer_name": CUSTOMER_NAME}
    else:
        demisto.info(f"running request with url={BASE_URL + url_suffix}")
        data = {}

    if commands is not None:
        data.update(commands)

    res = requests.post(BASE_URL + url_suffix, verify=USE_SSL, json=data)

    if res.status_code != requests.codes.ok:
        if url_suffix == "/login":
            demisto.setIntegrationContext({"session_token": None, "token_expires": None})
            demisto.info(f"{res.status_code} from server during login. Clearing session token cache.")

        raise DemistoException(f"HTTP {res.status_code} Error in API call to ThreatX service - {res.text}")

    resp_json = {}  # type:dict
    try:
        resp_json = res.json()
    except ValueError:
        raise DemistoException(f"Could not parse the response from ThreatX: {res.text}")

    if "Ok" not in resp_json:
        if url_suffix == "/login":
            demisto.setIntegrationContext({"session_token": None, "token_expires": None})
            raise DemistoException(f"Login response error - {res.text}.")

        raise DemistoException(res.text)

    if url_suffix == "/login" and "status" in resp_json["Ok"] and resp_json["Ok"]["status"] is not True:
        demisto.setIntegrationContext({"session_token": None, "token_expires": None})
        raise DemistoException("Invalid credentials.")

    return resp_json["Ok"]


@logger
def initialize():
    endpoint = "/login"
    commands = {"command": "login", "api_token": API_KEY}

    state = demisto.getIntegrationContext()

    if not state.get("session_token"):
        session_token = None
        token_expires = None
    else:
        session_token = state.get("session_token")
        token_expires = state.get("token_expires")

    demisto.info("Initializing request...")

    if session_token is None or (token_expires is not None and token_expires < int(time.time())):
        if session_token is None:
            demisto.info("Session token missing - getting new session token...")
        elif token_expires is not None and token_expires < int(time.time()):
            demisto.info("Session token expired - getting new session token...")

        r = http_request(endpoint, commands)
        demisto.setIntegrationContext({"session_token": r["token"], "token_expires": int(time.time() + (10 * 60))})
        return

    demisto.info("Cached session token not expired.")
    return


def pretty_ip(decimal_ip):
    """Convert decimal ip to dotted quad format"""
    packed_ip = struct.pack("!I", decimal_ip)
    return socket.inet_ntoa(packed_ip)


def pretty_time(input_time):
    """Convert unix epoch time to human readable format"""
    return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(input_time))


def set_dbot_score(threatx_score):
    """Set the DBot Score based on the ThreatX risk score"""
    if threatx_score >= DBOT_THRESHOLD:
        return 3
    elif threatx_score > 10:
        return 2
    else:
        return 0


""" FUNCTIONS """


@logger
def block_ip(ip, description):
    commands = {"command": "new_blocklist", "entry": {"ip": ip, "description": description, "created": int(time.time())}}

    return http_request("/lists", commands)


@logger
def block_ip_command(args):
    ips = args.get("ip", [])
    description = args.get("description", "Added by ThreatX Demisto Integration")
    results = []
    errors = []
    for ip in argToList(ips):
        try:
            ip_result = block_ip(ip, description)
        except Exception as error:
            demisto.error(f"failed block ip: {ip}\n{traceback.format_exc()}")
            errors.append(f"Failed to block ip: {ip} error: {error}")
        else:
            results.append(ip_result)
    if results:
        readable_outputs = tableToMarkdown("Block IP", results, ["Result"], removeNull=True)

        return_results(
            CommandResults(
                outputs=results, readable_output=readable_outputs, outputs_prefix="IP(val.Address === obj.Address).Address"
            )
        )
    if errors:
        return_error("\n".join(errors))


@logger
def unblock_ip(ip):
    commands = {"command": "delete_blocklist", "ip": ip}

    return http_request("/lists", commands)


@logger
def unblock_ip_command(args):
    ip = args.get("ip", None)
    results = unblock_ip(ip)

    md = tableToMarkdown("Unblock IP", results, ["Result"], removeNull=True)

    ec = {"IP(val.Address === obj.Address)": {"Address": ip}}

    return_outputs(md, ec, results)


@logger
def blacklist_ip(ip, description):
    commands = {"command": "new_blacklist", "entry": {"ip": ip, "description": description, "created": int(time.time())}}

    return http_request("/lists", commands)


@logger
def blacklist_ip_command(args):
    ips = args.get("ip", None)
    description = args.get("description", "Added by ThreatX Demisto Integration")
    results = []
    errors = []
    for ip in argToList(ips):
        try:
            ip_result = blacklist_ip(ip, description)
        except Exception as error:
            demisto.error(f"failed adding ip: {ip} to balcklist\n{traceback.format_exc()}")
            errors.append(f"Failed to add ip: {ip} to blacklist error: {error}")
        else:
            results.append(ip_result)

    if results:
        readable_outputs = tableToMarkdown("Blacklist IP", results, ["Result"], removeNull=True)

        return_results(
            CommandResults(
                outputs=results, readable_output=readable_outputs, outputs_prefix="IP(val.Address === obj.Address).Address"
            )
        )
    if errors:
        return_error("\n".join(errors))


@logger
def unblacklist_ip(ip):
    commands = {"command": "delete_blacklist", "ip": ip}

    return http_request("/lists", commands)


@logger
def unblacklist_ip_command(args):
    ip = args.get("ip", None)
    results = unblacklist_ip(ip)

    md = tableToMarkdown("Unblacklist IP", results, ["Result"], removeNull=True)

    ec = {"IP(val.Address === obj.Address)": {"Address": ip}}

    return_outputs(md, ec, results)


@logger
def whitelist_ip(ip, description):
    commands = {"command": "new_whitelist", "entry": {"ip": ip, "description": description, "created": int(time.time())}}

    return http_request("/lists", commands)


@logger
def whitelist_ip_command(args):
    ip = args.get("ip", None)
    description = args.get("description", "Added by ThreatX Demisto Integration")
    results = whitelist_ip(ip, description)

    md = tableToMarkdown("Whitelist IP", results, ["Result"], removeNull=True)

    ec = {"IP(val.Address === obj.Address)": {"Address": ip}}

    return_outputs(md, ec, results)


@logger
def unwhitelist_ip(ip):
    commands = {"command": "delete_whitelist", "ip": ip}

    return http_request("/lists", commands)


@logger
def unwhitelist_ip_command(args):
    ip = args.get("ip", None)
    results = unwhitelist_ip(ip)

    md = tableToMarkdown("Unwhitelist IP", results, ["Result"], removeNull=True)

    ec = {"IP(val.Address === obj.Address)": {"Address": ip}}

    return_outputs(md, ec, results)


@logger
def get_entities(entity_name, entity_id, entity_ip, timeframe):
    commands = {"command": "list", "query": {}}  # type: dict

    if entity_name is not None:
        entity_names = entity_name.split(",")
        my_entity_name = {"codenames": entity_names}
        commands["query"].update(my_entity_name)

    if entity_id is not None:
        entity_ids = entity_id.split(",")
        my_entity_id = {"entity_ids": entity_ids}
        commands["query"].update(my_entity_id)

    if entity_ip is not None:
        entity_ips = entity_ip.split(",")
        my_entity_ip = {"ip_addresses": entity_ips}
        commands["query"].update(my_entity_ip)

    first_seen = None

    if timeframe is not None:
        if timeframe == "1-Hour":
            first_seen = int(time.time() - (60 * 60))
        elif timeframe == "1-Day":
            first_seen = int(time.time() - (24 * 60 * 60))
        elif timeframe == "1-Week":
            first_seen = int(time.time() - (7 * 24 * 60 * 60))
        elif timeframe == "1-Month":
            first_seen = int(time.time() - (31 * 24 * 60 * 60))

    if first_seen:
        my_timeframe = {"first_seen": first_seen}
        commands["query"].update(my_timeframe)

    return http_request("/entities", commands)


@logger
def get_entity_risk(entity_id):
    commands = {"command": "risk_changes", "id": entity_id}

    return http_request("/entities", commands)


@logger
def get_entities_command(args):
    entity_name = args.get("entity_name", None)
    entity_id = args.get("entity_id", None)
    entity_ip = args.get("entity_ip", None)
    timeframe = args.get("timeframe", None)
    results = get_entities(entity_name, entity_id, entity_ip, timeframe)

    dbot_scores = []
    ip_enrich = []
    human_readable = []
    entities_context = []
    for entity in results:
        risk_score = 0
        e_risk = None
        # Grab the entity risk so we can set the Dbot score for the Actor IPs
        e_id = entity.get("id")

        if e_id:
            e_risk = get_entity_risk(e_id)

        if isinstance(e_risk, list) and e_risk and isinstance(e_risk[-1], dict) and "risk" in e_risk[-1]:
            risk_score = e_risk[-1]["risk"]

        entity["risk"] = risk_score

        iplist = []

        for actor in entity.get("actors", []):
            if "ip_address" in actor:
                ipdot = pretty_ip(actor["ip_address"])
                iplist.append(ipdot)
                actor["ip_address"] = ipdot

            if "interval_time_start" in actor:
                actor["interval_time_start"] = pretty_time(actor["interval_time_start"])

            if "interval_time_stop" in actor:
                actor["interval_time_stop"] = pretty_time(actor["interval_time_stop"])

            if "fingerprint" in actor and actor.get("fingerprint") is not None and "last_seen" in actor.get("fingerprint", {}):
                actor["fingerprint"]["last_seen"] = pretty_time(actor["fingerprint"]["last_seen"])

            dbscore = set_dbot_score(risk_score)

            dbot_scores.append({"Vendor": "ThreatX", "Indicator": ipdot, "Type": "ip", "Score": dbscore})

            if dbscore == 3:
                ip_enrich.append(
                    {
                        "Address": ipdot,
                        "Malicious": {"Vendor": "ThreatX", "Description": "ThreatX risk score is " + str(risk_score)},
                    }
                )
            else:
                ip_enrich.append({"Address": ipdot})
        entities_context.append({"ID": e_id, "Name": entity["codename"], "IP": iplist, "Risk": risk_score})

        human_readable.append(
            {"Name": entity["codename"], "ID": e_id, "IP Addresses": ", ".join(iplist), "ThreatX Risk Score": risk_score}
        )

    ec = {
        "Threatx.Entity(val.ID && val.ID === obj.ID)": entities_context,
        "DBotScore": dbot_scores,
        "IP(val.Address === obj.Address)": ip_enrich,
    }

    return_outputs(tableToMarkdown("Entities", human_readable), ec, results)


@logger
def get_entity_notes(entity_id):
    commands = {"command": "notes", "id": entity_id}

    return http_request("/entities", commands)


@logger
def get_entity_notes_command(args):
    entity_id = args.get("entity_id", None)
    results = get_entity_notes(entity_id)

    # Reverse sort the list by timestamp
    sorted_results = sorted(results, key=itemgetter("timestamp"), reverse=True)

    # Replace dates with pretty format
    for note in sorted_results:
        if "timestamp" in note:
            note["timestamp"] = pretty_time(note["timestamp"])

    md = tableToMarkdown("Entity Notes", sorted_results, headerTransform=string_to_table_header)

    ec = {"Threatx.Entity(val.ID && val.ID === obj.ID)": {"ID": entity_id, "Note": sorted_results}}

    return_outputs(md, ec, sorted_results)


@logger
def add_entity_note(entity_id, message):
    commands = {"command": "new_note", "note": {"entity_id": entity_id, "content": message}}

    return http_request("/entities", commands)


@logger
def add_entity_note_command(args):
    entity_id = args.get("entity_id", None)
    message = args.get("message", None)
    results = add_entity_note(entity_id, message)

    md = tableToMarkdown("New Entity Note", results, ["Result"], removeNull=True)

    return_outputs(md, {}, results)


@logger
def command_test_module():
    results = http_request("/users", {"command": "list"})

    if not isinstance(results, list):
        raise DemistoException("Unrecognized response from ThreatX.")

    if not results:
        raise DemistoException("Empty response from ThreatX.")

    if "username" not in results[0]:
        raise DemistoException("Unexpected response from ThreatX.")

    demisto.results("ok")


""" EXECUTION CODE """


def main():
    global CUSTOMER_NAME, API_KEY, BASE_URL, DBOT_THRESHOLD, USE_SSL
    params = demisto.params()

    url = params.get("url", "").strip("/")
    BASE_URL = url + "/tx_api/v1"

    CUSTOMER_NAME = params.get("credentials", {}).get("identifier") or params.get("customer_name")
    API_KEY = params.get("credentials", {}).get("password") or params.get("api_key")
    DBOT_THRESHOLD = int(params.get("dbot_threshold", 70))
    USE_SSL = not params.get("insecure")

    command = demisto.command()
    demisto.info(f"command is {command}")
    args = demisto.args()
    try:
        handle_proxy()
        initialize()
        if command == "test-module":
            command_test_module()
        elif command == "threatx-block-ip":
            block_ip_command(args)
        elif command == "threatx-unblock-ip":
            unblock_ip_command(args)
        elif command == "threatx-blacklist-ip":
            blacklist_ip_command(args)
        elif command == "threatx-unblacklist-ip":
            unblacklist_ip_command(args)
        elif command == "threatx-whitelist-ip":
            whitelist_ip_command(args)
        elif command == "threatx-unwhitelist-ip":
            unwhitelist_ip_command(args)
        elif command == "threatx-get-entities":
            get_entities_command(args)
        elif command == "threatx-get-entity-notes":
            get_entity_notes_command(args)
        elif command == "threatx-add-entity-note":
            add_entity_note_command(args)

    except Exception as e:
        return_error(str(e), error=traceback.format_exc())


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