CounterCraft Deception Director

CounterCraft Deception Solution detects advanced adversaries. Automate counterintelligence campaigns to discover targeted attacks with real-time active response.

Deception & Breach Simulation · CounterCraft Deception Director

Details

IDCounterCraft Deception Director
ProviderCounterCraft
CategoryDeception & Breach Simulation
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Overview


CounterCraft Deception Solution detects advanced adversaries. Automate counterintelligence campaigns to discover targeted attacks with real-time active response.
This integration was integrated and tested with version 2.5.13 of CounterCraft Deception Director

CounterCraft Deception Director Playbook


Use Cases


  • Query IOCs (objects) in your Deception Director
  • Retrieve events from your deception campaigns
  • Retrieve configuration from your Deception Director
  • Retrieve alerts (notifications) from your Deception Director
  • Create new deception campaigns
  • Create new deception hosts
  • Operate your campaigns, hosts, services and breadcrumbs

Prerequisites


You need to obtain the following Deception Director information.

  • Server URL
  • API Key
  • Secret Key

In order to obtain the API Key and the Secret Key you need to go to the user settings in the Deception Director
and copy both or generate a new pair if they are not already generated.

Configure CounterCraft Deception Director on Cortex XSOAR


  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for CounterCraft Deception Director.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Deception Director Domain or IP Address: for example, https://192.168.1.1
    • Fetch incidents: if you select this option, your notifications in the Deception Director will be created as Cortex XSOAR incidents.
    • Incident type
    • API Key for Deception Director connection: paste your API Key.
    • Secret Key for Deception Director connection: paste your Secret Key.
    • Ignore SSL Warnings: in case the SSL certificate is self-signed.
    • Use system proxy settings: in case you need to connect through a proxy.
  4. Click Test to validate the URLs, token, and connection.

Fetched Incidents Data


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. countercraft-list-campaigns
  2. countercraft-list-hosts
  3. countercraft-list-services
  4. countercraft-list-breadcrumbs
  5. countercraft-get-object
  6. countercraft-get-events
  7. countercraft-create-campaign
  8. countercraft-list-dsns
  9. countercraft-list-providers
  10. countercraft-create-host-machine
  11. countercraft-list-incidents
  12. countercraft-manage-campaign
  13. countercraft-manage-host
  14. countercraft-manage-service
  15. countercraft-manage-breadcrumb

1. countercraft-list-campaigns


List all deception campaigns

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the campaigns you have access to.

Base Command

countercraft-list-campaigns

Input
Argument Name Description Required
name Campaign Name Optional
Context Output
Path Type Description
CounterCraft.Campaign.ID number Campaign ID
CounterCraft.Campaign.Name string Campaign Name
CounterCraft.Campaign.Description string Campaign Description
CounterCraft.Campaign.StatusCode string Campaign Status
Command Example

!countercraft-list-campaigns

Human Readable Output
ID Name Description StatusCode
1 AntiPhishing Gather intelligence from phishers ACTIVE
2 External recoinassance Collect pre-attack evidence ACTIVE
3 Internal lateral movement Detect lateral movement ACTIVE
4 DMZ DMZ activity ACTIVE
5 VIP VIP mobile protection ACTIVE

2. countercraft-list-hosts


Lists all deception hosts

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the hosts you have access to.

Base Command

countercraft-list-hosts

Input
Argument Name Description Required
campaign_id Campaign ID Optional
Context Output
Path Type Description
CounterCraft.Host.ID number Host Id
CounterCraft.Host.Name string Host Name
CounterCraft.Host.Description string Host Description
CounterCraft.Host.StatusCode string Host Status
CounterCraft.Host.TypeCode string Host Type
Command Example

!countercraft-list-hosts campaign_id=2

Human Readable Output
ID Name Description StatusCode TypeCode
1 Ubuntu Web Wordpress ACTIVE MACHINE
2 Azure Windows 2019 RDP with breadcrumbs ACTIVE MACHINE
3 Office365 tenant Office365 with domain name ACTIVE CLOUD_ENTITY
4 Apache Struts Vulnerable Apache Struts ACTIVE MACHINE
5 CFO CFO persona ACTIVE IDENTITY

3. countercraft-list-services


List services currently deployed on deception hosts

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the services you have access to.

Base Command

countercraft-list-services

Input
Argument Name Description Required
host_id Host Id Optional
Context Output
Path Type Description
CounterCraft.Service.ID number Service ID
CounterCraft.Service.Name string Service Name
CounterCraft.Service.Description string Service.Description
CounterCraft.Service.StatusCode string Service Status
CounterCraft.Service.TypeCode string Service Type
Command Example

!countercraft-list-services host_id=1

Human Readable Output
ID Name Description StatusCode TypeCode
1 Operating system User events ACTIVE SYSTEM
2 WebApp Web application ACTIVE WEB_SERVER
8 Tailored Service Anonymous FTP ACTIVE FTP_SERVER
9 Phishing Sinkhole Sinkhole ACTIVE SMTP_SERVER

4. countercraft-list-breadcrumbs


List breadcrumbs in a campaign

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the breadcrumbs you have access to.

Base Command

countercraft-list-breadcrumbs

Input
Argument Name Description Required
campaign_id Campaign ID Optional
Context Output
Path Type Description
CounterCraft.Breadcrumb.ID number Breadcrumb ID
CounterCraft.Breadcrumb.Name string Breadcrumb Name
CounterCraft.Breadcrumb.Description string Breadcrumb Description
CounterCraft.Breadcrumb.StatusCode string Breadcrumb Status
CounterCraft.Breadcrumb.TypeCode string Breadcrumb Type
Command Example

!countercraft-list-breadcrumbs campaign_id=1

Human Readable Output
ID Name Description StatusCode TypeCode
1 Fake document   ACTIVE DOCUMENT
2 Mobile App   ACTIVE MOBILE_APP
3 SSL Certificate   ACTIVE SSL_CERTIFICATE
4 LinkedIn_persona   ACTIVE HONEYTOKEN

5. countercraft-get-object


Get information about an object (IoC)

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the objects you have access to.

Base Command

countercraft-get-object

Input
Argument Name Description Required
value Object value Required
Context Output
Path Type Description
CounterCraft.Object.ID number Object ID
CounterCraft.Object.Value string Object value
CounterCraft.Object.Hits number Object hits
CounterCraft.Object.Score number Object score
CounterCraft.Object.TypeCode string Object type
CounterCraft.Object.FirstSeen date Object first seen
CounterCraft.Object.LastSeen date Object last seen
CounterCraft.Object.EventsCount number Object events count
CounterCraft.Object.Tags string Object tags
Command Example

!countercraft-get-object value=root

Human Readable Output
   
Id 852
Value root
EventsCount 7
TypeCode CC_USERNAME
Score 0
FirstSeen Wed Jan 29 12:33:34 2020
LastSeen Wed Jan 29 12:53:19 2020
Tags  

6. countercraft-get-events


Get full list of Events

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the objects you have access to.

Base Command

countercraft-get-events

Input
Argument Name Description Required
criteria Search criteria Required
max_results Maximum number of results Required
Context Output
Path Type Description
CounterCraft.Event.ID number Event id
CounterCraft.Event.CampaignName string Campaign name
CounterCraft.Event.CategoryCode string Category Code
CounterCraft.Event.EventDate date Event date
CounterCraft.Event.HostName string Host name
CounterCraft.Event.ServiceName string Service name
CounterCraft.Event.TypeCode string Type
CounterCraft.Event.Score number Score
CounterCraft.Event.Tags string Tags
CounterCraft.Events.Data unknown Data
Command Example

!countercraft-get-events criteria="type_code:ValidAuth" max_results="1"

Human Readable Output
     
Id 45  
Campaignname External recoinassance  
Hostname Azure Windows
Servicename OS Logs (Azure Windows)
Eventdate Thu Jan 30 08:11:01 2020  
Score 100  
Typecode ValidAuth  
Data event: ValidAuth subject: A session was reconnected to a Window Station event_id: 4778 …  
Tags attack.T1078  

7. countercraft-create-campaign


Create a new deception campaign

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You can only create campaigns if you have the role ARCHITECT.

Base Command

countercraft-create-campaign

Input
Argument Name Description Required
name Campaign name Required
description Campaign description Required
Context Output
Path Type Description
CounterCraft.Campaign.ID number Campaign ID
CounterCraft.Campaign.Name string Name
CounterCraft.Campaign.Description string Description
CounterCraft.Campaign.StatusCode string Status Code
Command Example

!countercraft-create-campaign name="TestCampaign" description="Test Description"

Human Readable Output
   
Id 5
Name TestCampaign
Description Test Description
StatusCode DESIGN

8. countercraft-list-dsns


List Deception Support Nodes (DSNs)

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You can only create campaigns if you have the role ARCHITECT.

Base Command

countercraft-list-dsns

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output
Path Type Description
CounterCraft.DSN.ID number ID
CounterCraft.DSN.Name string Name
CounterCraft.DSN.Description string Description
CounterCraft.DSN.Hostname string Hostname
CounterCraft.DSN.Port number Port
Command Example

!countercraft-list-dsns

Human Readable Output
   
Id 1
Name Local DSN
Description Local DSN in the intranet
Hostname 192.168.1.2
Port  4567

9. countercraft-list-providers


List providers (providers for hosts or services i.e. AWS or Office365)

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the providers you have access to.

Base Command

countercraft-list-providers

Input

| Argument Name | Description | Required |
| — | — | — |

Context Output
Path Type Description
ID number ID
CounterCraft.Provider.Name string Name
CounterCraft.Provider.Description string Description
CounterCraft.Provider.TypeCode string Type
CounterCraft.Provider.StatusCode string Status
Command Example

!countercraft-list-providers

Human Readable Output
ID Name Description StatusCode TypeCode
1 Splunk Internal Splunk HEALTHY SPLUNK_PROVIDER
3 Signal Signal notifications HEALTHY SIGNAL_PROVIDER
4 Office365 Office365 Tenant HEALTHY OFFICE365_PROVIDER
5 AWS AWS EC2 HEALTHY AWS_PROVIDER

10. countercraft-create-host-machine


Deploy a new deception host

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to create a host if you are MANAGER in a campaign.

Base Command

countercraft-create-host-machine

Input
Argument Name Description Required
name Name Required
description Description Optional
provider_id Provider Required
deception_support_node_id Deception Support Node ID Required
campaign_id Campaign Required
os_family Operating System Required
ip_address IP Address Required
port Port Required
username Username Required
password Password Required
Context Output
Path Type Description
CounterCraft.Host.Id number Host ID
Command Example

!countercraft-create-host-machine campaign_id=2 deception_support_node_id=1 os_family=linux ip_address=192.168.1.2 port=22 name="Test host" description="Test Description" username="ubuntu" password="ubuntu provider_id=1"

Human Readable Output
   
Id 8
Name Test Host
Description Test Description
StatusCode DESIGN
TypeCode MACHINE

11. countercraft-list-incidents


List all incidents currently active

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to list only the incidents you have access to.

Base Command

countercraft-list-incidents

Input
Argument Name Description Required
campaign_id Campaign ID Required
Context Output
Path Type Description
CounterCraft.Incident.ID number Incident ID
CounterCraft.Incident.Name string Name
CounterCraft.Incident.Description string Description
CounterCraft.Incident.StatusCode string Status
CounterCraft.Incident.TLPCode string TLP code
Command Example

!countercraft-list-incidents campaign_id=1

Human Readable Output
ID Name Description StatusCode TLPCode Tags
1 APT incident State-sponsored OPEN AMBER  
2 Internal Fraud SWIFT apps CLOSED AMBER  

12. countercraft-manage-campaign


Manage Campaign parameters

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to manage only the campaigns you have access to.

Base Command

countercraft-manage-campaign

Input
Argument Name Description Required
campaign_id Campaign ID Required
operation Operation Required
Context Output
Path Type Description
CounterCraft.Campaign.Message string Result message
CounterCraft.Campaign.ID number Campaign ID
Command Example

!countercraft-manage-campaign campaign_id=5 operation=activate

Human Readable Output
   
Id 5
Message Campaign is currently in state: PAUSED. Action activate discarded

13. countercraft-manage-host


Manage a deception host

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to manage only the hosts you have access to.

Base Command

countercraft-manage-host

Input
Argument Name Description Required
host_id Host ID Required
operation Operation Required
Context Output
Path Type Description
CounterCraft.Host.Message string Result message
CounterCraft.Host.ID number Host ID
Command Example

!countercraft-manage-campaign host_id=5 operation=activate

Human Readable Output
   
Id 5
Message Host is currently in state: PAUSED. Action activate discarded

14. countercraft-manage-service


Manage a deception service

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to manage only the services you have access to.

Base Command

countercraft-manage-service

Input
Argument Name Description Required
service_id Service ID Required
operation Operation Required
Context Output
Path Type Description
CounterCraft.Service.Message string Result message
CounterCraft.Service.ID number Service ID
Command Example

!countercraft-manage-campaign service_id=5 operation=activate

Human Readable Output
   
Id 5
Message Service is currently in state: PAUSED. Action activate discarded

15. countercraft-manage-breadcrumb


Manage breadcrumb

Required Permissions

Any interaction will be based on your permissions on the Deception Director. Please consult
your Deception Director administrator in you have any questions.

You will be able to manage only the breadcrumbs you have access to.

Base Command

countercraft-manage-breadcrumb

Input
Argument Name Description Required
breadcrumb_id Breadcrumb ID Required
operation Operation Required
Context Output
Path Type Description
CounterCraft.Breadcrumb.Message string Result message
CounterCraft.Breadcrumb.ID number Breadcrumb ID
Command Example

!countercraft-manage-campaign breadcrumb_id=5 operation=activate

Human Readable Output
   
Id 5
Message Breadcrumb is currently in state: PAUSED. Action activate discarded

Additional Information


Please check the Deception Director user manual for more guidance on how to use and deploy campaigns.

Configuration parameters

  • server — Deception Director Domain or IP Address (required)
  • api_key — API Key for Deception Director connection (required)
  • secret_key — Secret Key for Deception Director connection (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval

Commands (15)

  • countercraft-create-campaign

    Create a new deception campaign.

  • countercraft-create-host-machine

    Deploy a new deception host.

  • countercraft-get-events

    Get full list of Events.

  • countercraft-get-object

    Get information about an object (IoC).

  • countercraft-list-breadcrumbs

    List breadcrumbs in a campaign.

  • countercraft-list-campaigns

    List all deception campaigns.

  • countercraft-list-dsns

    List Deception Support Nodes (DSNs).

  • countercraft-list-hosts

    Lists all deception hosts.

  • countercraft-list-incidents

    List all incidents currently active.

  • countercraft-list-providers

    List providers (providers for hosts or services i.e. AWS or Office365).

  • countercraft-list-services

    List services currently deployed on deception hosts.

  • countercraft-manage-breadcrumb

    Manage breadcrumb.

  • countercraft-manage-campaign

    Manage Campaign parameters.

  • countercraft-manage-host

    Manage a deception host.

  • countercraft-manage-service

    Manage a deception service.

import demistomock as demisto
from CommonServerPython import *

""" IMPORTS """
import binascii
import hmac
import json
import time
import urllib.parse
import uuid
from datetime import datetime
from hashlib import sha1

import requests
import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

""" GLOBALS"""

X_API_KEY_AUTHENTICATION_HEADER_PREFIX = "X-API-Key-Auth-"
SERVER = demisto.params().get("server").rstrip("/") + "/api"
API_KEY = demisto.params().get("api_key")
SECRET_KEY = demisto.params().get("secret_key")
VERIFY_CERTIFICATE = not demisto.params().get("insecure", False)
PROXY = demisto.params().get("proxy", False)
FETCH_DELTA = "24 hours"

DEF_HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}

ERR_DICT = {
    400: "Bad request. Please check your arguments and Deception Director API manual",
    401: "User does not have the right permission",
    404: "Entity not found. Please make sure the entity does exist",
    500: "Bad request. Please check your arguments and Deception Director API manual",
}

CAMPAIGN_FIELDS = {
    "id": "ID",
    "name": "Name",
    "description": "Description",
    "status_code": "StatusCode",
}

DSN_FIELDS = {
    "id": "ID",
    "name": "Name",
    "description": "Description",
    "hostname": "Hostname",
    "port": "Port",
}

HOST_FIELDS = {
    "id": "ID",
    "name": "Name",
    "description": "Description",
    "type_code": "TypeCode",
    "status_code": "StatusCode",
}

SERVICE_FIELDS = {
    "id": "ID",
    "name": "Name",
    "description": "Description",
    "type_code": "TypeCode",
    "status_code": "StatusCode",
}

BREADCRUMB_FIELDS = {
    "id": "ID",
    "name": "Name",
    "description": "Description",
    "type_code": "TypeCode",
    "status_code": "StatusCode",
}

PROVIDER_FIELDS = {
    "id": "ID",
    "name": "Name",
    "description": "Description",
    "type_code": "TypeCode",
    "status_code": "StatusCode",
}

INCIDENT_FIELDS = {
    "id": "ID",
    "name": "Name",
    "description": "Description",
    "status_code": "StatusCode",
    "tlp_code": "TLPCode",
}

OBJECT_FIELDS = {
    "id": "ID",
    "value": "Value",
    "hits": "Hits",
    "score": "Score",
    "type_code": "TypeCode",
    "first_seen": "FirstSeen",
    "last_seen": "LastSeen",
    "events_count": "EventsCount",
    "tags": "Tags",
}

EVENT_FIELDS = {
    "id": "ID",
    "campaign_name": "CampaignName",
    "category_code": "CategoryCode",
    "host_name": "HostName",
    "service_name": "ServiceName",
    "event_date": "EventDate",
    "score": "Score",
    "type_code": "TypeCode",
    "data": "Data",
    "tags": "Tags",
}

""" HELPERS """


def sign(secret_key, data):
    """
    @param $secret_key the secret key to use for the HMAC-SHA digesting
    @param $data the string to sign
    @return string base64 encoding of the HMAC-SHA1 hash of the data parameter using {@code secret_key} as cipher key.
    """

    sha1_hash = hmac.new(secret_key.encode(), data.encode(), sha1)

    return binascii.b2a_base64(sha1_hash.digest())[:-1]  # strip \n from base64 string result


def get_signature(request_method, request_headers, path, query_string, private_key):
    """
    Calculate the authentication headers to be sent with a request to the API
    @param $request_method the HTTP method (GET, POST, etc.)
    @param $path the urlencoded string including the path (from the first forward slash) and the parameters
    @param $x_headers HTTP headers specific to CounterCraft API
    @return array a map with the Authorization and Date headers needed to sign a Latch API request
    """

    x_headers = {k: v for k, v in request_headers.items() if k.lower().startswith(X_API_KEY_AUTHENTICATION_HEADER_PREFIX.lower())}

    string_to_sign = (
        request_method.upper().strip()
        + "\n"
        + get_serialized_headers(x_headers)
        + "\n"
        + (path if query_string.strip() == b"" else f"{path}?{query_string.strip().decode('utf - 8')}")
    )

    return sign(private_key, string_to_sign).decode("utf-8")


def get_serialized_headers(x_headers):
    """
    Generate a string ready to be signed based on HTTP headers received
    @param $x_headers a non neccesarily ordered map (array without duplicates) of the HTTP headers to be ordered.
    @return string The serialized headers, an empty string if no headers are passed
    """

    res = {}  # type: Dict[str, List]

    for k, v in x_headers.items():
        if k.strip().lower() not in res:
            res[k.lower()] = []

        res[k.strip().lower()].append(v.strip())

    return "\n".join(f"{k}:{v}" for k, v in sorted({k: ",".join(v) for k, v in res.items()}.items()))


def http_request(request_method, path, data={}, params=""):
    """
    Send an HTTP request
    @param $request_method the request method GET, POST, etc.
    @param $path the HTTP path
    @param $data the data included in the POST request
    @param $params the URL params to be included
    @return string A dict containing the response in JSON format or an error
    """

    headers = {X_API_KEY_AUTHENTICATION_HEADER_PREFIX + "Date": time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime())}
    headers.update(DEF_HEADERS)
    signature = get_signature(
        request_method,
        headers,
        path,
        urllib.parse.urlencode(params).encode("utf-8"),
        SECRET_KEY,
    )
    headers["Authorization"] = f"APIKey {API_KEY}:{signature}"
    url = SERVER + path

    proxies = None
    if PROXY:
        proxies = handle_proxy()

    res = requests.request(
        request_method, url, data=json.dumps(data), params=params, headers=headers, verify=VERIFY_CERTIFICATE, proxies=proxies
    )

    if res.status_code not in [200, 201, 204]:
        demisto.debug(f"Error doing the HTTP query. We got a {res.status_code}: {res.text}")
        return_error(ERR_DICT[res.status_code])

    try:
        res_json = res.json()
        return res_json
    except Exception as ex:
        demisto.debug(str(ex))
        return_error(str(ex))


def return_host_standard_context(host):
    host_standard_context = {}

    if host["type_code"] == "MACHINE":
        host_standard_context["ID"] = host["uuid"]
        host_standard_context["IP"] = host["data"]["ip_address"]

        if "ansible_facts" in host["data"]:
            host_standard_context["Domain"] = host["data"]["ansible_facts"]["ansible_domain"]
            host_standard_context["Hostname"] = host["data"]["ansible_facts"]["ansible_hostname"]
            host_standard_context["BIOSVersion"] = host["data"]["ansible_facts"]["ansible_bios_version"]
            host_standard_context["Memory"] = host["data"]["ansible_facts"]["ansible_memtotal_mb"]
            host_standard_context["Model"] = host["data"]["ansible_facts"]["ansible_product_name"]
            host_standard_context["OS"] = host["data"]["ansible_facts"]["ansible_os_family"]
            host_standard_context["OSVersion"] = host["data"]["ansible_facts"]["ansible_distribution_version"]
            host_standard_context["Processor"] = " ".join(host["data"]["ansible_facts"]["ansible_processor"])
            host_standard_context["Processors"] = len(host["data"]["ansible_facts"]["ansible_processor"])

    return host_standard_context


def return_entry_results(title, content, human_readable, context, headers):
    """
    Generic function that receives a result json, and turns it into an entryObject
    @param $title the table title
    @param $content the object contents in JSON format
    @param $human_readable human readable data for the table
    @param $context the entry context
    @param $headers table headers
    @return dict the entryObject
    """

    if len(content) == 0:
        return_outputs(
            readable_output="There is no output result",
            outputs=context,
            raw_response=content,
        )
        return

    if headers:
        if isinstance(headers, str):
            headers = headers.split(",")

    else:
        if isinstance(content, dict):
            headers = list(set(headers).intersection(set(content.keys())))

    readable_output = tableToMarkdown(
        title,
        human_readable,
        headers,
        lambda h: h.title().replace("_", " ").replace(".", ":"),
    )

    return_outputs(readable_output=readable_output, outputs=context, raw_response=content)


""" COMMANDS """


def test_module_command():
    http_request("GET", "/campaigns")
    demisto.results("ok")


def list_dsns_command():
    """
    Retrieve all deception support nodes
    """

    res = http_request("GET", "/deception_support_nodes")

    new_dsns = []
    for o in res["data"]:
        new_dsns.append({new_key: o.get(old_key, None) for old_key, new_key in DSN_FIELDS.items()})
    context = createContext(new_dsns, removeNull=True)

    return_entry_results(
        "Deception Support Node",
        res["data"],
        new_dsns,
        {"CounterCraft.DSN(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "Hostname", "Port"],
    )


def list_providers_command():
    """
    Retrieve all providers
    """

    res = http_request("GET", "/providers")

    new_providers = []
    for o in res["data"]:
        new_providers.append({new_key: o.get(old_key, None) for old_key, new_key in PROVIDER_FIELDS.items()})
    context = createContext(new_providers, removeNull=True)

    return_entry_results(
        "Providers",
        res["data"],
        new_providers,
        {"CounterCraft.Provider(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "TypeCode", "StatusCode"],
    )


def list_campaigns_command():
    """
    Retrieve all campaigns
    """

    name = demisto.args().get("name")

    if name is not None:
        criteria = {"criteria": f'name:"{name}"'}
    else:
        criteria = {}

    res = http_request("GET", "/campaigns", params=criteria)

    new_campaigns = []
    for o in res["data"]:
        new_campaigns.append({new_key: o.get(old_key, None) for old_key, new_key in CAMPAIGN_FIELDS.items()})
    context = createContext(new_campaigns, removeNull=True)

    return_entry_results(
        "Campaigns",
        res["data"],
        new_campaigns,
        {"CounterCraft.Campaign(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "StatusCode"],
    )


def list_hosts_command():
    """
    Retrieve all hosts
    """

    campaign_id = demisto.args().get("campaign_id")

    if campaign_id is not None:
        criteria = {"criteria": "campaigns.id:" + campaign_id}
    else:
        criteria = {}

    res = http_request("GET", "/hosts", params=criteria)

    new_hosts = []
    for o in res["data"]:
        new_hosts.append({new_key: o.get(old_key, None) for old_key, new_key in HOST_FIELDS.items()})

    context = createContext(new_hosts, removeNull=True)
    contextHosts = createContext([return_host_standard_context(x) for x in res["data"]], removeNull=True)

    return_entry_results(
        "Hosts",
        res["data"],
        new_hosts,
        {"CounterCraft.Host(val.ID && val.ID === obj.ID)": context, "Host(val.IP && val.IP === obj.IP)": contextHosts},
        headers=["ID", "Name", "Description", "StatusCode", "TypeCode"],
    )


def list_services_command():
    """
    Retrieve all services
    """

    host_id = demisto.args().get("host_id")

    if host_id is not None:
        criteria = {"criteria": "hosts.id:" + host_id}
    else:
        criteria = {}

    res = http_request("GET", "/services", params=criteria)

    new_services = []
    for o in res["data"]:
        new_services.append({new_key: o.get(old_key, None) for old_key, new_key in SERVICE_FIELDS.items()})
    context = createContext(new_services, removeNull=True)

    return_entry_results(
        "Services",
        res["data"],
        new_services,
        {"CounterCraft.Service(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "StatusCode", "TypeCode"],
    )


def list_breadcrumbs_command():
    """
    Retrieve all breadcrumbs
    """

    campaign_id = demisto.args().get("campaign_id")

    if campaign_id is not None:
        criteria = {"criteria": "campaigns.id:" + campaign_id}
    else:
        criteria = {}

    res = http_request("GET", "/breadcrumbs", params=criteria)

    new_breadcrumbs = []
    for o in res["data"]:
        new_breadcrumbs.append({new_key: o.get(old_key, None) for old_key, new_key in BREADCRUMB_FIELDS.items()})
    context = createContext(new_breadcrumbs, removeNull=True)

    return_entry_results(
        "Breadcrumbs",
        res["data"],
        new_breadcrumbs,
        {"CounterCraft.Breadcrumb(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "StatusCode", "TypeCode"],
    )


def list_incidents_command():
    """
    Retrieve all incidents
    """

    campaign_id = demisto.args().get("campaign_id")

    if campaign_id is not None:
        criteria = {"criteria": "campaigns.id:" + campaign_id}
    else:
        criteria = {}

    res = http_request("GET", "/incidents", params=criteria)

    new_incidents = []
    for o in res["data"]:
        new_incidents.append({new_key: o.get(old_key, None) for old_key, new_key in INCIDENT_FIELDS.items()})
    context = createContext(new_incidents, removeNull=True)

    return_entry_results(
        "Incidents",
        res["data"],
        new_incidents,
        {"CounterCraft.Incident(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "StatusCode", "TLPCode", "Tags"],
    )


def get_object_command():
    """
    Retrieve all objects
    """

    value = demisto.args().get("value")

    if value is not None:
        criteria = {"criteria": "objects.value:" + value}
    else:
        criteria = {}

    res = http_request("GET", "/objects", params=criteria)

    for entry in res["data"]:
        entry["first_seen"] = formatEpochDate(entry["first_seen"])
        entry["last_seen"] = formatEpochDate(entry["last_seen"])

    new_objects = []
    for o in res["data"]:
        new_objects.append({new_key: o.get(old_key, None) for old_key, new_key in OBJECT_FIELDS.items()})
    context = createContext(new_objects, removeNull=True)

    return_entry_results(
        "Objects",
        res["data"],
        new_objects,
        {"CounterCraft.Object(val.ID && val.ID === obj.ID)": context},
        headers=[
            "ID",
            "Value",
            "Hits",
            "EventsCount",
            "TypeCode",
            "Score",
            "FirstSeen",
            "LastSeen",
            "Tags",
        ],
    )


def get_events_command():
    """
    Retrieve all events
    """

    sfilter = demisto.args().get("criteria")
    per_page = demisto.args().get("max_results")

    criteria = {
        "criteria": sfilter,
        "order": "-event_date",
        "page": 1,
        "per_page": per_page,
    }

    res = http_request("GET", "/events", params=criteria)

    for entry in res["data"]:
        entry["event_date"] = formatEpochDate(entry["event_date"])

    new_events = []
    for o in res["data"]:
        new_events.append({new_key: o.get(old_key, None) for old_key, new_key in EVENT_FIELDS.items()})
    context = createContext(new_events, removeNull=True)

    return_entry_results(
        "Events",
        res["data"],
        new_events,
        {"CounterCraft.Event(val.ID && val.ID === obj.ID)": context},
        headers=[
            "ID",
            "CampaignName",
            "CategoryCode",
            "HostName",
            "ServiceName",
            "EventDate",
            "Score",
            "TypeCode",
            "Data",
            "Tags",
        ],
    )


def list_notifications(last_fetched):
    """
    Retrieve all Notifications
    """

    criteria = {
        "criteria": f'plugin_code:CONSOLE AND notifications.ctime:["{last_fetched}" TO *]',
        "order": "-ctime",
        "with_stats": True,
    }

    res = http_request("GET", "/notifications", params=criteria)

    return res["data"]


def create_campaign_command():
    """
    Create a campaign
    """
    name = demisto.args().get("name")
    description = demisto.args().get("description")

    data = {"name": name, "description": description}

    res = http_request("POST", "/campaigns", data=data)

    campaign = {new_key: res.get(old_key, None) for old_key, new_key in CAMPAIGN_FIELDS.items()}

    context = createContext(campaign, removeNull=True)

    return_entry_results(
        "Campaign",
        res,
        campaign,
        {"CounterCraft.Campaign(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "StatusCode"],
    )


def manage_campaign_command():
    """
    Operate a campaign
    """
    campaign_id = demisto.args().get("campaign_id")
    operation = demisto.args().get("operation")

    data = {"action": operation}

    res = http_request("PATCH", "/campaigns/" + campaign_id, data=data)

    message = [{"ID": campaign_id, "Message": res["message"]}]

    context = createContext(message, removeNull=True)

    return_entry_results(
        "Campaign Management",
        res,
        message,
        {"CounterCraft.Campaign(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Message"],
    )


def create_host_machine_command():
    """
    Create a host of type MACHINE
    """

    name = demisto.args().get("name")
    description = demisto.args().get("description")
    provider_id = demisto.args().get("provider_id")
    type_code = "MACHINE"
    deception_support_node_id = demisto.args().get("deception_support_node_id")
    campaign_id = demisto.args().get("campaign_id")

    ip_address = demisto.args().get("ip_address")
    port = demisto.args().get("port")
    username = demisto.args().get("username")
    password = demisto.args().get("password")

    host_data = {
        "ip_address": ip_address,
        "port": port,
        "username": username,
        "password": password,
        "os_family_usr": "linux",
    }

    _uuid = str(uuid.uuid4())

    data = {
        "name": name,
        "description": description,
        "provider_id": provider_id,
        "deception_support_node_id": deception_support_node_id,
        "campaign_id": campaign_id,
        "type_code": type_code,
        "uuid": _uuid,
        "data": host_data,
    }

    res = http_request("POST", "/hosts", data=data)

    host = {new_key: res.get(old_key, None) for old_key, new_key in HOST_FIELDS.items()}

    context = createContext(host, removeNull=True)

    return_entry_results(
        "Hosts",
        res,
        host,
        {"CounterCraft.Host(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Name", "Description", "StatusCode", "TypeCode"],
    )


def manage_host_command():
    """
    Operate a host
    """
    host_id = demisto.args().get("host_id")
    operation = demisto.args().get("operation")

    data = {"action": operation}

    res = http_request("PATCH", "/hosts/" + host_id, data=data)

    message = {"ID": host_id, "Message": res["message"]}

    context = createContext(message, removeNull=True)

    return_entry_results(
        "Host Management",
        res,
        message,
        {"CounterCraft.Host(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Message"],
    )


def manage_service_command():
    """
    Operate a service
    """
    service_id = demisto.args().get("service_id")
    operation = demisto.args().get("operation")

    data = {"action": operation}

    res = http_request("PATCH", "/services/" + service_id, data=data)

    message = {"ID": service_id, "Message": res["message"]}

    context = createContext(message, removeNull=True)

    return_entry_results(
        "Service Management",
        res,
        message,
        {"CounterCraft.Service(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Message"],
    )


def manage_breadcrumb_command():
    """
    Operate a breadcrumb
    """
    breadcrumb_id = demisto.args().get("breadcrumb_id")
    operation = demisto.args().get("operation")

    data = {"action": operation}

    res = http_request("PATCH", "/breadcrumbs/" + breadcrumb_id, data=data)

    message = {"ID": breadcrumb_id, "Message": res["message"]}

    context = createContext(message, removeNull=True)

    return_entry_results(
        "Breadcrumb Management",
        res,
        message,
        {"CounterCraft.Breadcrumb(val.ID && val.ID === obj.ID)": context},
        headers=["ID", "Message"],
    )


def fetch_incidents_command():
    """
    Fetch incidents (user notifications)
    """

    last_run = demisto.getLastRun()
    if not last_run:
        last_run = {}
    if "time" not in last_run:
        # get timestamp in seconds
        timestamp, _ = parse_date_range(FETCH_DELTA, to_timestamp=True)
        timestamp /= 1000
    else:
        timestamp = last_run["time"]

    max_timestamp = timestamp

    # All alerts retrieved from get_alerts are newer than last_fetch and are in a chronological order
    notifications = list_notifications(timestamp)

    incidents = []

    for notification in notifications:
        if int(notification["ctime"]) > timestamp:
            incidents.append(
                {
                    "name": notification["data"]["subject"],
                    "occurred": datetime.utcfromtimestamp(int(notification["ctime"])).strftime("%Y-%m-%dT%H:%M:%SZ"),
                    "details": notification["data"]["html"],
                    "rawJSON": json.dumps(notification),
                }
            )
            if int(notification["ctime"]) > max_timestamp:
                max_timestamp = int(notification["ctime"])

    demisto.incidents(incidents)
    demisto.setLastRun({"time": max_timestamp})


def main():
    try:
        if demisto.command() == "test-module":
            test_module_command()
        elif demisto.command() == "countercraft-list-providers":
            list_providers_command()
        elif demisto.command() == "countercraft-list-dsns":
            list_dsns_command()
        elif demisto.command() == "countercraft-list-campaigns":
            list_campaigns_command()
        elif demisto.command() == "countercraft-list-hosts":
            list_hosts_command()
        elif demisto.command() == "countercraft-list-services":
            list_services_command()
        elif demisto.command() == "countercraft-list-breadcrumbs":
            list_breadcrumbs_command()
        elif demisto.command() == "countercraft-list-incidents":
            list_incidents_command()
        elif demisto.command() == "countercraft-get-object":
            get_object_command()
        elif demisto.command() == "countercraft-get-events":
            get_events_command()
        elif demisto.command() == "countercraft-create-campaign":
            create_campaign_command()
        elif demisto.command() == "countercraft-create-host-machine":
            create_host_machine_command()
        elif demisto.command() == "countercraft-manage-campaign":
            manage_campaign_command()
        elif demisto.command() == "countercraft-manage-host":
            manage_host_command()
        elif demisto.command() == "countercraft-manage-service":
            manage_service_command()
        elif demisto.command() == "countercraft-manage-breadcrumb":
            manage_breadcrumb_command()
        elif demisto.command() == "fetch-incidents":
            fetch_incidents_command()
    except Exception as e:
        return_error(f"Unable to perform command : {demisto.command()}, Reason: {e}")


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