IllusiveNetworks

The Illusive Attack Management API allows customers to retrieve detected incidents with a forensics timeline, attack surface insights, collect forensics on-demand, and manage a variety of operations with regard to deceptive entities, deception policies, and more.

Deception & Breach Simulation · Illusive Networks

Details

IDIllusiveNetworks
ProviderProofpoint Inc
CategoryDeception & Breach Simulation
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM EDR Cortex Cloud Cloud Runtime Security

README

Overview


The Illusive Attack Management API allows customers to retrieve detected incidents with a forensics timeline, attack surface insights, collect forensics on-demand, and manage a variety of operations with regard to deceptive entities, deception policies, and more.
This integration was integrated and tested with version 130 of IllusiveNetworks

Configure IllusiveNetworks on Cortex XSOAR

Use Cases

  • Automatically collect data and forensics from new incidents detected by Illusive
  • Enrich SOC data by retrieving a rich set of incident and forensics information, including: 1) host details and forensics from a potentially compromised host, 2) a forensics timeline, 3) forensics analysis, 4) additional data
  • Auto-analyze collected data and calculate incident severity to speed up SOC response times
  • Collect forensics from any compromised host and retrieve a forensics timeline
  • Retrieve detailed lists of approved and suggested deceptive servers and users
  • Approve, delete, and query deceptive entities
  • Manage deception policy assignments per host
  • Retrieve attack surface insights for Crown Jewels and specific hosts

Configure IllusiveNetworks on Cortex XSOAR


Illusive Console

  1. Open the Illusive Management console, navigate to Settings > General, and locate the API KEYS section. Generate a new API key with all permissions and copy the token at the end of the process.

Cortex XSOAR Console

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for IllusiveNetworks.
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required
url Server URL (e.g. https://example.net) True
api_token API Token True
isFetch Fetch incidents False
incidentType Incident type False
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False
fetch_time The initial time to fetch from False
has_forensics Fetch only incidents with forensics True
  1. Click Test to validate the URLs, token, and connection.

Fetched Incidents Data


{
“sourceIp”: “10.90.10.25”,
“sourceOperatingSystem”: null,
“policyName”: null,
“incidentTypes”: [“DECEPTION”],
“riskInsights”: {“stepsToDomainAdmin”: null, “stepsToCrownJewel”: null},
“deceptionFamilies”: [“FAMILY_TYPE_BROWSERS”],
“lastSeenUser”: null,
“closed”: false,
“unread”: true,
“flagged”: false,
“hasForensics”: false,
“incidentId”: 32,
“incidentTimeUTC”: “2020-05-04T11:37:10.231Z”,
“sourceHostname”: null,
“userNotes”: null
}

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. illusive-get-forensics-timeline
  2. illusive-get-asm-host-insight
  3. illusive-get-asm-cj-insight
  4. illusive-get-deceptive-users
  5. illusive-get-deceptive-servers
  6. illusive-is-deceptive-user
  7. illusive-is-deceptive-server
  8. illusive-add-deceptive-users
  9. illusive-add-deceptive-servers
  10. illusive-delete-deceptive-users
  11. illusive-delete-deceptive-servers
  12. illusive-assign-host-to-policy
  13. illusive-remove-host-from-policy
  14. illusive-run-forensics-on-demand
  15. illusive-get-incidents
  16. illusive-get-event-incident-id
  17. illusive-get-incident-events
  18. illusive-get-forensics-analyzers
  19. illusive-get-forensics-triggering-process-info
  20. illusive-get-forensics-artifacts

illusive-get-forensics-timeline


Retrieve forensics timeline for a specific incident

Base Command

illusive-get-forensics-timeline

Input

Argument Name Description Required
incident_id The desired incident ID Required
start_date The starting date of the forensics timeline. Optional
end_date The last date of the forensics timeline. Optional

Context Output

Path Type Description
Illusive.Forensics.Evidence.details String The forensics evidence details
Illusive.Forensics.Evidence.eventId String The event ID
Illusive.Forensics.Evidence.id String The forensics evidence ID
Illusive.Forensics.Evidence.source String The Evidence source
Illusive.Forensics.Evidence.starred Boolean Whether the forensics evidence has been starred
Illusive.Forensics.Evidence.time Date Date and time of the forensics evidence
Illusive.Forensics.Evidence.title String The forensics evidence description
Illusive.Forensics.IncidentId String The Incident Id
Illusive.Forensics.Status String The process progress ( Done, InProgress)
Illusive.Forensics.Evidence.date Date The forensics evidence date

Command Example

illusive-get-forensics-timeline incident_id=80 start_date=”10 days” end_date=”3 hours”

Human Readable Output

date details eventId id source starred time title type
2020-06-29 09:16:17.480 id: 4
date: 2020-06-29 09:16:17.480
type: LOGIN
sourceIP: 172.27.102.12
trap: hr
injectedUserName: dgffg
injectedPassword: ****
destinationIpAddress: 172.27.102.6
serviceType: WEB
data: Is Successful Login: false,
Accept-language: en-US,en;q=0.9,
Web Protocol: HTTP,
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9,
Web Host: 172.27.102.6,
Web Url: /,
Authorization: Digest username=”dgffg”, realm=”Domain Name”, nonce=”1593422172658:ad3f7cc0c86b52747fba1d68583c3827”, uri=”/”, response=”9f5d4174395c7d09460fc88e454713aa”, opaque=”9AC5ADA4A6317F9FB4F2B1211D8A7EFE”, qop=auth, nc=00000002, cnonce=”b000143183c56904”,
Web Body: ,
Web Method: GET,
Upgrade-insecure-requests: 1,
Web User Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36,
Connection: keep-alive,
Cache-control: max-age=0,
Accept-encoding: gzip, deflate
hasForensics: No
title: WEB login attempt with the username dgffg to the deceptive URL 172.27.102.6/
4 d59f0b0a-4a66-40d0-9565-563adc7534f1 MANAGEMENT false 1593422177480 WEB login attempt with the username dgffg to the deceptive URL 172.27.102.6/ EVENT

illusive-get-asm-host-insight


Retrieve the specified host insights from Attack Surface Manager

Base Command

illusive-get-asm-host-insight

Input

Argument Name Description Required
hostnameOrIp The hostname or IP address of the desired host Required

Context Output

Path Type Description
Illusive.AttackSurfaceInsightsHost.DomainName String The host domain
Illusive.AttackSurfaceInsightsHost.HostName String The host hostname
Illusive.AttackSurfaceInsightsHost.HostType String The host type (Server, Workstation, Other)
Illusive.AttackSurfaceInsightsHost.IpAddresses String The host IP address
Illusive.AttackSurfaceInsightsHost.OperatingSystemName String The host operating system name
Illusive.AttackSurfaceInsightsHost.OperatingSystemVersion String The host operating system version
Illusive.AttackSurfaceInsightsHost.OrganizationalUnit String The host Active Directory Organizational Unit
Illusive.AttackSurfaceInsightsHost.SourceConnectivityExposure Number The host Source Connectivity Exposure to crown jewels and domain user credentials
Command Example

illusive-get-asm-host-insight hostnameOrIp=172.27.139.12

Human Readable Output

Illusive ASM Host Insights

domainName hostName hostType ipAddresses operatingSystemName operatingSystemVersion organizationalUnit sourceConnectivityExposure
illusive.com win5.illusive.com Workstation 172.27.139.12,::1,fe80::ffff:ffff:fffe,fe80::2d2d:5763:8c1a:7b9 Windows 10   clients 0.0

illusive-get-asm-cj-insight


Retrieve Crown-Jewels insights from Attack Surface Manager

Base Command

illusive-get-asm-cj-insight

Input

There are no input arguments for this command.

Context Output

Path Type Description
Illusive.AttackSurfaceInsightsCrownJewel.data Unknown The number of connections to this Crown Jewel per service type
Illusive.AttackSurfaceInsightsCrownJewel.hostname String The crown jewel hostname
Illusive.AttackSurfaceInsightsCrownJewel.machineTagAndSubTags.tag String The List of a crown jewel category and subcategory couplings
Illusive.AttackSurfaceInsightsCrownJewel.MachineTagAndSubTags.subTag String The List of a crown jewel category and subcategory couplings
Illusive.AttackSurfaceInsightsCrownJewel.targetExposureRank Number The crown jewel target exposure
Command Example

illusive-get-asm-cj-insight

Human Readable Output

Illusive ASM Crown Jewels Insights

data hostname machineTagAndSubTags targetExposureRank
{‘key’: ‘RDP’, ‘value’: 1} 172.27.139.12 {‘tag’: ‘Mainframe’, ‘subTag’: ‘MAINFRAME’} 0.0

illusive-get-deceptive-users


Retrieve a list of all deceptive users

Base Command

illusive-get-deceptive-users

Input

Argument Name Description Required
type The status of the desired deceptive users (APPROVED, SUGGESTED, ALL) Optional

Context Output

Path Type Description
Illusive.DeceptiveUser.userName String The deceptive user name
Illusive.DeceptiveUser.domainName String The deceptive user domain
Illusive.DeceptiveUser.policyNames Unknown The deception policies the deceptive user is assigned to
Illusive.DeceptiveUser.password String The deceptive user password
Illusive.DeceptiveUser.deceptiveState String The deceptive user state (APPROVED, SUGGESTED, ALL)
Illusive.DeceptiveUser.adUser Boolean Whether the deceptive user is a genuine user in Active Directory
Illusive.DeceptiveUser.activeUser Boolean In case the deceptive user is a real AD user, indicates whether he is active
Command Example

!illusive-get-deceptive-users type=APPROVED

Human Readable Output

Illusive Deceptive Users

activeUser adUser deceptiveState domainName password policyNames username
false false APPROVED illusive.com Password Full Protection user1
false false APPROVED illusive.com Password Full Protection user2

illusive-get-deceptive-servers


Retrieve a list of all deceptive servers

Base Command

illusive-get-deceptive-servers

Input

Argument Name Description Required
type The status of the desired deceptive servers (APPROVED, SUGGESTED, ALL) Optional

Context Output

Path Type Description
Illusive.DeceptiveServer.host String The deceptive server hostname
Illusive.DeceptiveServer.policyNames String The deception policies the deceptive server is assigned to
Illusive.DeceptiveServer.adHost Boolean Whether the deceptive server is a genuine machine in Active Directory
Illusive.DeceptiveServer.deceptiveState String The deceptive server state (APPROVED, SUGGESTED, ALL)
Illusive.DeceptiveServer.serviceTypes String The deception services the deceptive server is assigned to
Command Example

!illusive-get-deceptive-servers type=APPROVED

Human Readable Output

Illusive Deceptive Servers

adHost deceptiveState host policyNames serviceTypes
false APPROVED server1.illusive.com adiPo,
Full Protection
SHARE,
DB
false APPROVED server2.illusive.com Full Protection WEB,
DB
false APPROVED server3.illusive.com adiPo,
Full Protection
FTP,
SHARE,
DB

illusive-is-deceptive-user


Retrieve whether a specified user is deceptive

Base Command

illusive-is-deceptive-user

Input

Argument Name Description Required
username The username to be verified Required

Context Output

Path Type Description
Illusive.IsDeceptive.Username String The checked username
Illusive.IsDeceptive.IsDeceptiveUser Boolean Is the specified user conducted as a deceptive user
Command Example

!illusive-is-deceptive-user username=user1

Human Readable Output

Illusive Is Deceptive

IsDeceptiveUser Username
true user1

illusive-is-deceptive-server


Retrieve whether a specified server is deceptive

Base Command

illusive-is-deceptive-server

Input

Argument Name Description Required
hostname The server hostname to be verified Required

Context Output

Path Type Description
Illusive.IsDeceptive.IsDeceptiveServer Boolean Is the specified server conducted as a deceptive server
Illusive.IsDeceptive.Hostname String The checked server hostname
Command Example

!illusive-is-deceptive-server hostname=server5.illusive.com

Human Readable Output

Illusive Is Deceptive

Hostname IsDeceptiveServer
server5.illusive.com false

illusive-add-deceptive-users


Add or approve deceptive users

Base Command

illusive-add-deceptive-users

Input

Argument Name Description Required
domain_name The deceptive user domain Required
password The deceptive user password Required
policy_names The deception policies to be assigned to the new deceptive user Optional
username The deceptive user name Required

Context Output

There is no context output for this command.

Command Example

!illusive-add-deceptive-users domain_name=illusive.com password=pass username=user3

Human Readable Output

Illusive Add Deceptive User Succeeded

domainName password policyNames userName
illusive.com pass All Policies user3

illusive-add-deceptive-servers


Add or approve deceptive servers

Base Command

illusive-add-deceptive-servers

Input

Argument Name Description Required
host The deceptive server hostname Required
policy_names The deception policies to be assigned to the new deceptive server Optional
service_types The deception services to be assigned to the new deceptive server Required

Context Output

There is no context output for this command.

Command Example

!Set key=”serviceTypes” value=”FTP”

!Set key=”serviceTypes” value=”SSH” append=true

!illusive-add-deceptive-servers host=server4.illusive.com service_types=${serviceTypes}

Human Readable Output

Illusive Add Deceptive Server Succeeded

host policyNames serviceTypes
server4.illusive.com All Policies FTP,
SSH

illusive-delete-deceptive-users


Delete deceptive users

Base Command

illusive-delete-deceptive-users

Input

Argument Name Description Required
deceptive_users The list of deceptive users to delete Required

Context Output

There is no context output for this command.

Command Example

!illusive-delete-deceptive-users deceptive_users=user3

Human Readable Output

Deceptive User [‘user3’] was successfully Deleted

illusive-delete-deceptive-servers


Delete deceptive servers

Base Command

illusive-delete-deceptive-servers

Input

Argument Name Description Required
deceptive_hosts The list of deceptive servers to delete Required

Context Output

There is no context output for this command.

Command Example

!Set key=”servers” value=”server5.illusive.com”

!Set key=”servers” value=”server1.illusive.com” append=true

!illusive-delete-deceptive-servers deceptive_hosts=${servers}

Human Readable Output

Deceptive Servers [‘server5.illusive.com’, ‘server1.illusive.com’] were successfully Deleted

illusive-assign-host-to-policy


Assign a deception policy to domain hosts

Base Command

illusive-assign-host-to-policy

Input

Argument Name Description Required
policy_name Policy name to assign Required
hosts List of hosts to assign, in the following format: machine@domain.
Maximum number of hosts is 1000.
Required

Context Output

There is no context output for this command.

Command Example

!illusive-assign-host-to-policy hosts=WIN7@illusive.com policy_name=”Full Protection”

Human Readable Output

Illusive Assign Machines to Policy Succeeded

hosts isAssigned policy_name
WIN7@illusive.com true Full Protection

illusive-remove-host-from-policy


Remove deception policy assignment from domain hosts

Base Command

illusive-remove-host-from-policy

Input

Argument Name Description Required
hosts List of hosts to remove policy assignment from, in the following format: machine@domain.
Maximum number of hosts is 1000
Required

Context Output

There is no context output for this command.

Command Example

!illusive-remove-host-from-policy hosts=WIN7@illusive.com

Human Readable Output

Illusive Remove Machines from All Policies Succeeded

hosts isAssigned policy_name
WIN7@illusive.com false  

illusive-run-forensics-on-demand


Collect forensics on a specified host and retrieve the forensics timeline

Base Command

illusive-run-forensics-on-demand

Input

Argument Name Description Required
fqdn_or_ip The host fqdn or IP address on which to collect forensics Required

Context Output

Path Type Description
Illusive.Event.eventId Number The created event ID of the operation
Command Example

!illusive-run-forensics-on-demand fqdn_or_ip=172.27.139.12

Human Readable Output

Illusive Run Forensics On Demand

eventId
123

illusive-get-incidents


Retrieve incidents

Base Command

illusive-get-incidents

Input

Argument Name Description Required
incident_id The desired incident ID to retrieve.
If specified - other arguments are ignored and only a single incident can be retrieved
Optional
hostnames The list of hostnames to retrieve incidents Optional
has_forensics Whether to retrieve incidents with forensics only Optional
limit Use offset and limit for pagination.
The maximum limit is 100.
Optional
offset Use offset and limit for pagination. Optional
start_date start date Optional

Context Output

| Path | Type | Description |
| — | — | — |
| Illusive.Incident.closed | Boolean | Whether the incident has been closed |
| Illusive.Incident.deceptionFamilies | String | The deception families of the
deceptions used to trigger
the incident |
| Illusive.Incident.flagged | Boolean | Whether the incident has been flagged |
| Illusive.Incident.hasForensics | Boolean | Whether incident has forensics |
| Illusive.Incident.incidentId | Number | The Incident ID |
| Illusive.Incident.incidentTimeUTC | Date | Date and time of the incident |
| Illusive.Incident.incidentTypes | Unknown | Type of events detected |
| Illusive.Incident.lastSeenUser | String | The user who last reviewed the incident |
| Illusive.Incident.policyName | String | The compromised host’s policy |
| Illusive.Incident.riskInsights.stepsToCrownJewel | Number | The compromised host’s lateral distance from Crown Jewels |
| Illusive.Incident.riskInsights.stepsToDomainAdmin | Number | The compromised host’s lateral distance from domain admin accounts |
| Illusive.Incident.sourceHostname | String | The compromised host’s name |
| Illusive.Incident.sourceIp | String | The compromised host’s IP address |
| Illusive.Incident.sourceOperatingSystem | String | The compromised host’s operating system |
| Illusive.Incident.unread | Boolean | Whether the incident has been read |
| Illusive.Incident.userNotes | String | The analyst’s comments |

Command Example

!illusive-get-incidents incident_id=28

Human Readable Output

Illusive Incidents

closed deceptionFamilies flagged hasForensics incidentId incidentTimeUTC incidentTypes lastSeenUser policyName riskInsights sourceHostname sourceIp sourceOperatingSystem unread userNotes
false FAMILY_TYPE_BROWSERS false false 28 2020-04-20T06:44:33.207Z DECEPTION     stepsToDomainAdmin: null
stepsToCrownJewel: null
  172.27.139.14   false  

illusive-get-event-incident-id


Retrieve the incident ID of an event

Base Command

illusive-get-event-incident-id

Input

Argument Name Description Required
event_id The Event id Required

Context Output

Path Type Description
Illusive.Event.incidentId Number The Incident ID
Illusive.Event.eventId Number The given event ID
Illusive.Event.status String The status command ( Done, InProgress)
Command Example

!illusive-get-event-incident-id event_id=80

Human Readable Output

Illusive Get Incident

eventId incidentId status
80 72 Done

illusive-get-incident-events


Retrieve all the events that are associated with an incident

Base Command

illusive-get-incident-events

Input

Argument Name Description Required
incident_id The incident ID on which to retrieve the associated events Required
limit The maximum number of events to retrieve Optional
offset Use offset and limit for pagination Optional

Context Output

Path Type Description
Illusive.Incident.incidentId Number The corresponding incident ID
Illusive.Incident.eventsNumber Number The number of associated events
Illusive.Incident.Event.eventId Number The event ID
Illusive.Incident.Event.eventTimeUTC Date The event time
Illusive.Incident.Event.hasForensics Boolean Whether the event has forensics

Command Example

!illusive-get-incident-events incident_id=2

Human Readable Output

Illusive get incident’s events

eventId eventTimeUTC hasForensics
4 2020-06-29T09:16:17.480Z false
3 2020-06-29T09:16:17.464Z false
2 2020-06-29T09:16:12.673Z false

illusive-get-forensics-analyzers


Retrieve Illusive’s forensics analyzers on a certain event

Base Command

illusive-get-forensics-analyzers

Input

Argument Name Description Required
event_id The event ID to retrieve the forensics analyzers from Required

Context Output

Path Type Description
Illusive.Event.incidentId Number The corresponding incident ID
Illusive.Event.eventId Number The corresponding event ID
Illusive.Event.ForensicsAnalyzers.analyzerName String The forensics analyzer name
Illusive.Event.ForensicsAnalyzers.analyzerValue String The forensics analyzer description

Command Example

!illusive-get-forensics-analyzers event_id=2

Human Readable Output

Illusive Forensics Analyzers

analyzerName analyzerValue
Summary An attempt to browse to the deceptive URL hr (172.27.102.6) was detected from 172.27.102.12
Event Source And Destination Event ID: 2
Event time: 2020-06-29T09:16:12.673Z
Event originated from 172.27.102.12
Event targeted hr (172.27.102.6)
Web A GET request was made to the url http://172.27.102.6/
Forensics Failure Reason No resolving for host 172.27.102.12

illusive-get-forensics-triggering-process-info


Retrieve the triggering process information from Illusive’s forensics

Base Command

illusive-get-forensics-triggering-process-info

Input

Argument Name Description Required
event_id The event ID to retrieve the triggering process information from Required

Context Output

Path Type Description
Illusive.Event.incidentId Number The corresponding incident ID
Illusive.Event.eventId Number The corresponding event ID
Illusive.Event.ForensicsTriggeringProcess.commandLine String The triggering process command line
Illusive.Event.ForensicsTriggeringProcess.connectionsNum Number The triggering process active connections
Illusive.Event.ForensicsTriggeringProcess.md5 String The triggering process md5
Illusive.Event.ForensicsTriggeringProcess.sha256 String The triggering process sha256
Illusive.Event.ForensicsTriggeringProcess.name String The triggering process name
Illusive.Event.ForensicsTriggeringProcess.parent String The parent process of the triggering process
Illusive.Event.ForensicsTriggeringProcess.path String The triggering process path
Illusive.Event.ForensicsTriggeringProcess.startTime Date The triggering process start time

Command Example

!illusive-get-forensics-triggering-process-info event_id=5

Human Readable Output

Illusive Triggering Processes Info

commandLine connectionsNum md5 name parent path sha256 startTime
“C:\Program Files (x86)\Google\Chrome\Application\chrome.exe” –type=utility –field-trial-handle=1472,8764100348922490764,5250110531070070503,131072 –lang=en-US –service-sandbox-type=network –enable-audio-service-sandbox –mojo-platform-channel-handle=1900 /prefetch:8 15 9E46D108805A743D2D88D04019370A05 chrome.exe chrome.exe(7848) C:\Program Files (x86)\Google\Chrome\Application\chrome.exe D7771E5F5090EF37BE554D5DD9E1C24C8CD83EBF284C48CC5D1EF45D02C0E77F 2020-06-29T09:16:47.245+00:00

illusive-get-forensics-artifacts


Retrieve forensics artifacts from Illusive’s forensics

Base Command

illusive-get-forensics-artifacts

Input

Argument Name Description Required
event_id The event ID to retrieve the forensics artifacts from Required
artifact_type The type of forensics artifact to retrieve Required

Context Output

There is no context output for this command.

Command Example

!illusive-get-forensics-artifacts event_id=2

Human Readable Output

event id 2 has no artifacts

Configuration parameters

  • url — Server URL (e.g. https://example.net) (required)
  • api_token — API Token (required)
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • fetch_time — The initial time to fetch from
  • has_forensics — Fetch only incidents with forensics

Commands (20)

  • illusive-add-deceptive-servers

    Add or approve deceptive servers.

  • illusive-add-deceptive-users

    Add or approve deceptive users.

  • illusive-assign-host-to-policy

    Assign a deception policy to domain hosts.

  • illusive-delete-deceptive-servers

    Delete deceptive servers.

  • illusive-delete-deceptive-users

    Delete deceptive users.

  • illusive-get-asm-cj-insight

    Retrieve Crown-Jewels insights from Attack Surface Manager.

  • illusive-get-asm-host-insight

    Retrieve the specified host insights from Attack Surface Manager.

  • illusive-get-deceptive-servers

    Retrieve a list of all deceptive servers.

  • illusive-get-deceptive-users

    Retrieve a list of all deceptive users.

  • illusive-get-event-incident-id

    Retrieve the incident ID of an event.

  • illusive-get-forensics-analyzers

    Retrieve Illusive's forensics analyzers on a certain event.

  • illusive-get-forensics-artifacts

    Retrieve forensics artifacts from Illusive's forensics.

  • illusive-get-forensics-timeline

    Retrieve forensics timeline for a specific incident.

  • illusive-get-forensics-triggering-process-info

    Retrieve the triggering process information from Illusive's forensics.

  • illusive-get-incident-events

    Retrieve all the events that are associated with an incident.

  • illusive-get-incidents

    Retrieve incidents.

  • illusive-is-deceptive-server

    Retrieve whether a specified server is deceptive.

  • illusive-is-deceptive-user

    Retrieve whether a specified user is deceptive.

  • illusive-remove-host-from-policy

    Remove deception policy assignment from domain hosts.

  • illusive-run-forensics-on-demand

    Collect forensics on a specified host and retrieve the forensics timeline.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
import urllib3
from CommonServerUserPython import *  # noqa: E402 lgtm [py/polluting-import]
# IMPORTS

from typing import Any
import json
import dateparser
import zipfile
import io

# Disable insecure warnings

urllib3.disable_warnings()

# CONSTANTS
DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.000Z"
DEFAULT_HEADERS_POST_REQUEST = {"accept": "application/json;charset=UTF-8", "Content-Type": "application/json;charset=UTF-8"}


class Client(BaseClient):
    """
    Client will implement the service API, and should not contain any Demisto logic.
    Should only do requests and return data.
    """

    def get_deceptive_users(self, user_type):
        url_suffix = f"/api/v1/deceptive-entities/users?deceptive_user_type={user_type}"
        return self._http_request("GET", url_suffix=url_suffix)

    def get_deceptive_serves(self, server_type):
        url_suffix = f"/api/v1/deceptive-entities/servers?deceptive_server_type={server_type}"
        return self._http_request("GET", url_suffix=url_suffix)

    def add_deceptive_users(self, body):
        url_suffix = "/api/v1/deceptive-entities/users"
        return self._http_request("POST", url_suffix=url_suffix, data=json.dumps(body), ok_codes=(200,))

    def add_deceptive_servers(self, body):
        url_suffix = "/api/v1/deceptive-entities/servers"
        return self._http_request("POST", url_suffix=url_suffix, data=json.dumps(body), ok_codes=(200,))

    def assign_host_to_policy(self, policy_name, body):
        url_suffix = f"/api/v1/policy/domain_hosts/assign?policy_name={policy_name}"
        return self._http_request("POST", url_suffix=url_suffix, data=json.dumps(body), ok_codes=(200,))

    def remove_host_from_policy(self, body):
        url_suffix = "/api/v1/policy/domain_hosts/remove_assignment"
        return self._http_request("POST", url_suffix=url_suffix, data=json.dumps(body), ok_codes=(200,))

    def get_forensics_timeline(self, incident_id, start_date, end_date):
        url_suffix = f"/api/v1/forensics/timeline?incident_id={incident_id}"
        if end_date:
            url_suffix += f"&end_date={end_date}"
        if start_date:
            url_suffix += f"&start_date={start_date}"
        return self._http_request("GET", url_suffix=url_suffix, ok_codes=(200,))

    def get_asm_host_insight(self, hostname_or_ip):
        url_suffix = f"/api/v1/attack-surface/machine-insights?hostNameOrIp={hostname_or_ip}"
        return self._http_request("GET", url_suffix=url_suffix)

    def get_asm_cj_insight(self):
        url_suffix = "/api/v1/crownjewels/insights"
        return self._http_request("GET", url_suffix=url_suffix)

    def run_forensics_on_demand(self, hostname_or_ip):
        url_suffix = f"/api/v1/event/create-external-event?hostNameOrIp={hostname_or_ip}"
        return self._http_request("POST", url_suffix=url_suffix)

    def is_deceptive_user(self, username):
        url_suffix = f"/api/v1/deceptive-entities/user?userName={username}"
        return self._http_request("GET", url_suffix=url_suffix, resp_type="text")

    def is_deceptive_server(self, hostname):
        url_suffix = f"/api/v1/deceptive-entities/server?hostName={hostname}"
        return self._http_request("GET", url_suffix=url_suffix, resp_type="text")

    def delete_deceptive_users(self, deceptive_users):
        url_suffix = "/api/v1/deceptive-entities/users"
        url_suffix += "?deceptive_users=" + "&deceptive_users=".join(deceptive_users)
        return self._http_request("DELETE", url_suffix=url_suffix, resp_type="text")

    def delete_deceptive_servers(self, deceptive_servers):
        url_suffix = "/api/v1/deceptive-entities/servers"
        url_suffix += "?deceptive_hosts=" + "&deceptive_hosts=".join(deceptive_servers)
        return self._http_request("DELETE", url_suffix=url_suffix, resp_type="text")

    def get_incident(self, incident_id):
        url_suffix = f"/api/v2/incidents/incident?incident_id={incident_id}"
        return self._http_request("GET", url_suffix=url_suffix)

    def get_event_incident_id(self, event_id):
        url_suffix = f"/api/v1/incidents/id?event_id={event_id}"
        return self._http_request("GET", url_suffix=url_suffix, ok_codes=(200,))

    def list_all_incidents(self, has_forensics, host_names, limit, offset, start_date):
        url_suffix = f"/api/v1/incidents?limit={limit}&offset={offset}"
        if has_forensics is not None:
            url_suffix += f"&has_forensics={has_forensics}"
        if start_date:
            url_suffix += f"&start_date={start_date}"
        if host_names:
            url_suffix += "&host_names=" + "&host_names=".join(host_names)
        return self._http_request("GET", url_suffix=url_suffix)

    def test_configuration(self):
        url_suffix = "/api/v1/incidents?limit=10&offset=0"
        return self._http_request("GET", url_suffix=url_suffix, ok_codes=(200,))

    def get_incident_events(self, incident_id, limit, offset):
        url_suffix = f"/api/v1/incidents/events?incident_id={incident_id}&limit={limit}&offset={offset}"
        return self._http_request("GET", url_suffix=url_suffix)

    def get_forensics_artifacts(self, event_id, artifact_type):
        url_suffix = f"/api/v1/forensics/artifacts?event_id={event_id}&artifacts_type={artifact_type}"
        return self._http_request("GET", url_suffix=url_suffix, resp_type="content")

    def get_forensics_analyzers(self, event_id):
        url_suffix = f"/api/v1/forensics/analyzers?event_id={event_id}"
        return self._http_request("GET", url_suffix=url_suffix)

    def get_forensics_triggering_process_info(self, event_id):
        url_suffix = f"/api/v1/forensics/triggering_process_info?event_id={event_id}"
        return self._http_request("GET", url_suffix=url_suffix, ok_codes=(200,))


def test_module(client):
    """
    Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful.

    Args:
        client: Illusive Networks client

    Returns:
        'ok' if test passed, anything else will fail the test.
    """
    try:
        client.test_configuration()
        return "ok"
    except DemistoException as e:
        if "401" in e.args[0]:
            return f"Test failed, potential reasons might be that the API KEY parameter is incorrect: {e.args[0]}"
        else:
            return f"Test failed: {e.args[0]}"


def fetch_incidents(client, last_run, first_fetch_time, has_forensics):
    """
    This function will execute each interval (default is 1 minute).
    """
    # Get the last fetch time, if exists
    last_fetch = last_run.get("last_run")

    # Handle first time fetch
    if last_fetch is None:
        last_fetch, _ = parse_date_range(first_fetch_time, date_format=DATE_FORMAT, utc=True)

    latest_created_time = last_fetch
    incidents = []
    items = client.list_all_incidents(has_forensics, None, limit=10, offset=0, start_date=latest_created_time)
    for item in items:
        incident_created_time = item["incidentTimeUTC"]
        incident_type = "None"
        if len(item["incidentTypes"]) > 0:
            incident_type = str(item["incidentTypes"][0])
        incident_create_time_date = dateparser.parse(incident_created_time)
        assert incident_create_time_date is not None
        incident = {
            "name": "Illusive Attack Management detected an incident of type " + incident_type,
            "occurred": incident_create_time_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
            "rawJSON": json.dumps(item),
        }
        incidents.append(incident)

        # Update last run and add incident if the incident is newer than last fetch
        if incident_created_time > latest_created_time:
            latest_created_time = incident_created_time

    next_run = {"last_run": latest_created_time}
    return next_run, incidents


def get_deceptive_users_command(client: Client, args: dict) -> tuple:
    user_type = args.get("type", "ALL")
    try:
        result = client.get_deceptive_users(user_type)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    readable_output = tableToMarkdown("Illusive Deceptive Users", result)
    outputs = {"Illusive.DeceptiveUser(val.userName == obj.userName)": result}
    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def get_deceptive_servers_command(client: Client, args: dict) -> tuple:
    server_type = args.get("type", "ALL")
    try:
        result = client.get_deceptive_serves(server_type)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")
    readable_output = tableToMarkdown("Illusive Deceptive Servers", result)
    outputs = {"Illusive.DeceptiveServer(val.host == obj.host)": result}
    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def add_deceptive_users_command(client: Client, args: dict) -> tuple:
    user_name = args.get("username", None)
    domain_name = args.get("domain_name", None)
    password = args.get("password", None)
    policy_names = argToList(args.get("policy_names"))

    request_body = [{"domainName": domain_name, "password": password, "policyNames": policy_names, "username": user_name}]
    try:
        client.add_deceptive_users(request_body)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    result = {
        "userName": user_name,
        "domainName": domain_name,
        "policyNames": "All Policies" if policy_names == [] else policy_names,
        "password": password,
    }
    readable_output = tableToMarkdown("Illusive Add Deceptive User Succeeded", result)
    outputs = {"Illusive.DeceptiveUser(val.userName == obj.userName)": result}
    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def add_deceptive_servers_command(client: Client, args: dict) -> tuple:
    host_name = args.get("host", "")  # must be <host>.<domain>
    service_types = argToList(args.get("service_types"))
    policy_names = argToList(args.get("policy_names"), "All Policies")

    if len(host_name.split(".")) < 2:
        raise DemistoException("host name must have the following pattern: <host>.<domain>")

    request_body = [{"host": host_name, "serviceTypes": service_types, "policyNames": policy_names}]
    try:
        client.add_deceptive_servers(request_body)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    result = {
        "host": host_name,
        "serviceTypes": service_types,
        "policyNames": "All Policies" if policy_names == [] else policy_names,
    }
    readable_output = tableToMarkdown("Illusive Add Deceptive Server Succeeded", result)
    outputs = {"Illusive.DeceptiveServer(val.host == obj.host)": result}
    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def assign_host_to_policy_command(client: Client, args: dict) -> tuple:
    policy_name = args.get("policy_name", None)
    host_names = argToList(args.get("hosts"))
    host_names = host_names[:1000]
    request_body = []
    for host_name in host_names:
        host_name_split = host_name.split("@")
        if len(host_name_split) != 2:
            raise Exception(f"bad hostname format: {host_name}. Should be  <machineName>@<domainName> ")
        request_body.append({"machineName": host_name_split[0], "domainName": host_name_split[1]})
    try:
        client.assign_host_to_policy(policy_name, request_body)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")
    result = []
    for host in host_names:
        result.append({"isAssigned": True, "hosts": host, "policy_name": policy_name})

    readable_output = tableToMarkdown("Illusive Assign Machines to Policy Succeeded", result)
    outputs = {"Illusive.DeceptionPolicy.isAssigned(val.hosts == obj.hosts)": result}
    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def remove_host_from_policy_command(client: Client, args: dict) -> tuple:
    host_names = argToList(args.get("hosts"))
    host_names = host_names[:1000]
    request_body = []
    for host_name in host_names:
        host_name_split = host_name.split("@")
        if len(host_name_split) != 2:
            raise Exception(f"bad hostname format: {host_name}. Should be  <machineName>@<domainName> ")
        request_body.append({"machineName": host_name_split[0], "domainName": host_name_split[1]})
    try:
        client.remove_host_from_policy(request_body)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    result = []
    for host in host_names:
        result.append({"isAssigned": False, "hosts": host, "policy_name": ""})

    readable_output = tableToMarkdown("Illusive Remove Machines from All Policies Succeeded", result)
    outputs = {"Illusive.DeceptionPolicy.isAssigned(val.hosts == obj.hosts)": result}
    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def get_forensics_timeline_command(client: Client, args: dict) -> tuple:
    incident_id = args.get("incident_id")
    start_date = args.get("start_date", None)
    end_date = args.get("end_date", None)
    if start_date:
        start_date, _ = parse_date_range(start_date, date_format=DATE_FORMAT, utc=True)
    if end_date:
        end_date, _ = parse_date_range(end_date, date_format=DATE_FORMAT, utc=True)

    try:
        result = client.get_forensics_timeline(incident_id, start_date, end_date)
        for evidence in result:
            evidence["date"] = evidence.get("details").get("date")
        readable_output = tableToMarkdown("Illusive Forensics Timeline", result)
        outputs = {
            "Illusive.Forensics(val.IncidentId == obj.IncidentId)": {
                "IncidentId": incident_id,
                "Status": "Done",
                "Evidence": result,
            }
        }
    except DemistoException as e:
        if "404" in e.args[0]:
            raise DemistoException(f"Incident id {incident_id} doesn't not exist")
        elif "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        elif "202" in e.args[0]:
            readable_output = f"Incident id {incident_id} hasn't been closed yet"
            outputs = {
                "Illusive.Forensics(val.IncidentId == obj.IncidentId)": {
                    "IncidentId": incident_id,
                    "Status": "InProgress",
                    "Evidence": [],
                }
            }
            result = []
        else:
            raise DemistoException(f"{e.args[0]}")

    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def get_asm_host_insight_command(client: Client, args: dict) -> tuple:
    hostname_or_ip = args.get("hostnameOrIp", None)
    try:
        result = client.get_asm_host_insight(hostname_or_ip)
    except DemistoException as e:
        if "404" in e.args[0]:
            result = []
        elif "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")
    readable_output = tableToMarkdown("Illusive ASM Host Insights", result)
    outputs = {"Illusive.AttackSurfaceInsightsHost(val.ipAddresses == obj.ipAddresses)": result}

    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def get_asm_cj_insight_command(client: Client, args: dict) -> tuple:
    try:
        result = client.get_asm_cj_insight()
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")
    readable_output = tableToMarkdown("Illusive ASM Crown Jewels Insights", result)
    outputs = {"Illusive.AttackSurfaceInsightsCrownJewel(val.hostname == obj.hostname)": result}

    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def run_forensics_on_demand_command(client: Client, args: dict) -> tuple:
    fqdn_or_ip = args.get("fqdn_or_ip", None)
    try:
        result = client.run_forensics_on_demand(fqdn_or_ip)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")
    readable_output = tableToMarkdown("Illusive Run Forensics On Demand", result)
    outputs = {"Illusive.Event(val.eventId == obj.eventId)": result}
    return (
        readable_output,
        outputs,
        result,  # raw response - the original response
    )


def is_deceptive_user_command(client: Client, args: dict) -> tuple:
    username = args.get("username", None)
    is_deceptive_user = False
    is_deceptive_user = True if client.is_deceptive_user(username) else is_deceptive_user
    result = {"Username": username, "IsDeceptiveUser": is_deceptive_user}
    readable_output = tableToMarkdown("Illusive Is Deceptive", result)
    outputs = {"Illusive.IsDeceptive(val.Username == obj.Username)": result}
    return (
        readable_output,
        outputs,
        None,  # raw response - the original response
    )


def is_deceptive_server_command(client: Client, args: dict) -> tuple:
    hostname = args.get("hostname", None)
    is_deceptive_server = False
    is_deceptive_server = True if client.is_deceptive_server(hostname) else is_deceptive_server
    result = {"Hostname": hostname, "IsDeceptiveServer": is_deceptive_server}
    readable_output = tableToMarkdown("Illusive Is Deceptive", result)
    outputs = {"Illusive.IsDeceptive(val.Hostname == obj.Hostname)": result}
    return (
        readable_output,
        outputs,
        None,  # raw response - the original response
    )


def delete_deceptive_users_command(client: Client, args: dict) -> tuple:
    deceptive_users = argToList(args.get("deceptive_users"))
    try:
        client.delete_deceptive_users(deceptive_users)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")
    if len(deceptive_users) > 1:
        a, b = "s", "were"
    else:
        a, b = "", "was"
    result = f' {f"Deceptive User{a} {deceptive_users} {b} successfully Deleted"}'
    readable_output = f"## {result}"

    outputs: dict[str, Any] = {}
    return (
        readable_output,
        outputs,
        None,  # raw response - the original response
    )


def delete_deceptive_servers_command(client: Client, args: dict) -> tuple:
    deceptive_servers = argToList(args.get("deceptive_hosts"))
    try:
        client.delete_deceptive_servers(deceptive_servers)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    if len(deceptive_servers) > 1:
        a, b = "s", "were"
    else:
        a, b = "", "was"
    result = f' {f"Deceptive Server{a} {deceptive_servers} {b} successfully Deleted"}'
    readable_output = f"## {result}"

    outputs: dict[str, Any] = {}
    return (
        readable_output,
        outputs,
        None,  # raw response - the original response
    )


def get_incidents_command(client: Client, args: dict) -> tuple:
    incident_id = args.get("incident_id")
    has_forensics = args.get("has_forensics", None)
    host_names = argToList(args.get("hostnames"))
    limit = args.get("limit", 10)
    offset = args.get("offset", 0)
    start_date = args.get("start_date", None)
    if start_date:
        start_date, _ = parse_date_range(start_date, date_format=DATE_FORMAT, utc=True)
    try:
        if incident_id:
            incident = client.get_incident(incident_id)
        else:
            limit = "100" if int(limit) > 100 else limit
            incident = client.list_all_incidents(has_forensics, host_names, limit, offset, start_date)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    readable_output = tableToMarkdown("Illusive Incidents", incident)
    outputs = {"Illusive.Incident(val.incidentId == obj.incidentId)": incident}
    return (
        readable_output,
        outputs,
        incident,  # raw response - the original response
    )


def get_event_incident_id_command(client: Client, args: dict) -> tuple:
    event_id = int(args.get("event_id", None))
    status = "Done"
    try:
        incident = client.get_event_incident_id(event_id)
    except DemistoException as e:
        if "404" in e.args[0]:
            raise DemistoException(f"Event id {event_id} doesn't not exist")
        elif "202" in e.args[0]:
            incident = "-"
            status = "InProgress"
        elif "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")
    result = [{"eventId": event_id, "incidentId": incident, "status": status}]
    outputs = {"Illusive.Event(val.eventId == obj.eventId)": result}
    readable_output = tableToMarkdown("Illusive Get Incident", result)

    return (
        readable_output,
        outputs,
        None,  # raw response - the original response
    )


def get_incident_events_command(client: Client, args: dict) -> tuple:
    incident_id = args.get("incident_id", 0)
    limit = args.get("limit", 100)
    limit = "1000" if int(limit) > 1000 else limit
    offset = args.get("offset", 0)
    try:
        events = client.get_incident_events(incident_id, limit, offset)
    except DemistoException as e:
        if "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    readable_output = tableToMarkdown("Illusive get incident's events", events, metadata=f"Number of events {len(events)}")

    outputs = {
        "Illusive.Incident(val.incidentId == obj.incidentId)": {
            "eventsNumber": len(events),
            "incidentId": int(incident_id),
            "Event": events,
        }
    }
    return (
        readable_output,
        outputs,
        events,  # raw response - the original response
    )


def get_forensics_analyzers_command(client: Client, args: dict) -> tuple:
    event_id = args.get("event_id", 0)
    try:
        analyzers = client.get_forensics_analyzers(event_id)
        incident = client.get_event_incident_id(event_id)
    except DemistoException as e:
        if "404" in e.args[0]:
            raise DemistoException(f"Event id {event_id} doesn't not exist")
        elif "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    outputs = {
        "Illusive.Event(val.eventId == obj.eventId)": {
            "eventId": int(event_id),
            "incidentId": int(incident),
            "ForensicsAnalyzers": analyzers,
        }
    }

    readable_output = tableToMarkdown("Illusive Forensics Analyzers", analyzers)

    return (
        readable_output,
        outputs,
        incident,  # raw response - the original response
    )


def get_forensics_triggering_process_info_command(client: Client, args: dict) -> tuple:
    event_id = args.get("event_id")
    try:
        processes = client.get_forensics_triggering_process_info(event_id)
    except DemistoException as e:
        if "404" in e.args[0]:
            raise DemistoException(f"failed to get forensics for Event id {event_id}")
        elif "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    readable_output = tableToMarkdown("Illusive Triggering Processes Info", processes.get("processes"))
    outputs = {
        "Illusive.Event(val.eventId == obj.eventId)": {
            "eventId": event_id,
            "ForensicsTriggeringProcess": processes.get("processes"),
        }
    }

    return (
        readable_output,
        outputs,
        readable_output,  # raw response - the original response
    )


def get_forensics_artifacts_command(client: Client, args: dict) -> tuple:
    event_id = args.get("event_id")
    artifact_type = args.get("artifact_type", "DESKTOP_SCREENSHOT")
    try:
        client.get_event_incident_id(event_id)  # this request is for checking the event exists
        artifact = client.get_forensics_artifacts(event_id, artifact_type)
    except DemistoException as e:
        if "404" in e.args[0]:
            raise DemistoException(f"failed to get forensics for Event id {event_id}")
        elif "429" in e.args[0]:
            raise DemistoException(
                "The allowed amount of API calls per minute in Illusive Attack Management has exceeded. In case this"
                " message repeats, please contact Illusive Networks support"
            )
        else:
            raise DemistoException(f"{e.args[0]}")

    if len(artifact) == 0:
        return [], []

    zip_file = zipfile.ZipFile(io.BytesIO(artifact))
    file_results = []
    file_names = []
    i = 0
    for info in zip_file.infolist():
        i = i + 1
        file_results.append(
            fileResult(
                filename=f"eventId_{event_id}_{artifact_type}_{i}.jpg",
                data=zip_file.read(info.filename),
                file_type=entryTypes["image"],
            )
        )
        file_names.append(f"eventId_{event_id}_{artifact_type}_{i}.jpg")
    return file_results, file_names


def link_forensics_artifacts_name_command(file_names, client: Client, args: dict) -> CommandResults:
    event_id = args.get("event_id", 0)

    if len(file_names) > 0:
        outputs = {"eventId": int(event_id), "Artifacts": file_names}

        return CommandResults(outputs_prefix="Illusive.Event", outputs_key_field="eventId", outputs=outputs)
    else:
        readable_output = f"### event id {event_id} has no artifacts"

        return CommandResults(
            readable_output=readable_output,
            outputs_prefix="Illusive.Event",
            outputs_key_field="eventId",
            outputs={"eventId": int(event_id)},
        )


def main():
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """
    # get the service API token
    api_token = demisto.params().get("api_token")
    has_forensics = demisto.params().get("has_forensics")
    has_forensics = None if has_forensics == "ALL" else has_forensics

    # get the service API url
    base_url = demisto.params()["url"]

    verify_certificate = not demisto.params().get("insecure", False)

    # How much time before the first fetch to retrieve incidents
    first_fetch_time = demisto.params().get("fetch_time", "7 days").strip().lower()

    proxy = demisto.params().get("proxy", False)

    LOG(f"Command being called is {demisto.command()}")
    try:
        headers = DEFAULT_HEADERS_POST_REQUEST
        headers["Authorization"] = api_token
        client = Client(base_url=base_url, verify=verify_certificate, headers=headers, proxy=proxy)

        if demisto.command() == "test-module":
            # This is the call made when pressing the integration Test button.
            result = test_module(client)
            demisto.results(result)
        elif demisto.command() == "fetch-incidents":
            # Set and define the fetch incidents command to run after activated via integration settings.
            next_run, incidents = fetch_incidents(
                client=client, last_run=demisto.getLastRun(), first_fetch_time=first_fetch_time, has_forensics=has_forensics
            )

            demisto.setLastRun(next_run)
            demisto.incidents(incidents)

        elif demisto.command() == "illusive-get-forensics-timeline":
            return_outputs(*get_forensics_timeline_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-asm-host-insight":
            return_outputs(*get_asm_host_insight_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-asm-cj-insight":
            return_outputs(*get_asm_cj_insight_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-deceptive-users":
            return_outputs(*get_deceptive_users_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-deceptive-servers":
            return_outputs(*get_deceptive_servers_command(client, demisto.args()))

        elif demisto.command() == "illusive-is-deceptive-user":
            return_outputs(*is_deceptive_user_command(client, demisto.args()))

        elif demisto.command() == "illusive-is-deceptive-server":
            return_outputs(*is_deceptive_server_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-incidents":
            return_outputs(*get_incidents_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-event-incident-id":
            return_outputs(*get_event_incident_id_command(client, demisto.args()))

        elif demisto.command() == "illusive-add-deceptive-users":
            return_outputs(*add_deceptive_users_command(client, demisto.args()))

        elif demisto.command() == "illusive-add-deceptive-servers":
            return_outputs(*add_deceptive_servers_command(client, demisto.args()))

        elif demisto.command() == "illusive-delete-deceptive-users":
            return_outputs(*delete_deceptive_users_command(client, demisto.args()))

        elif demisto.command() == "illusive-delete-deceptive-servers":
            return_outputs(*delete_deceptive_servers_command(client, demisto.args()))

        elif demisto.command() == "illusive-assign-host-to-policy":
            return_outputs(*assign_host_to_policy_command(client, demisto.args()))

        elif demisto.command() == "illusive-remove-host-from-policy":
            return_outputs(*remove_host_from_policy_command(client, demisto.args()))

        elif demisto.command() == "illusive-run-forensics-on-demand":
            return_outputs(*run_forensics_on_demand_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-forensics-artifacts":
            try:
                file_results, file_names = get_forensics_artifacts_command(client, demisto.args())
                return_results(file_results)
                return_results(link_forensics_artifacts_name_command(file_names, client, demisto.args()))
            except ValueError:
                return_results(link_forensics_artifacts_name_command([], client, demisto.args()))

        elif demisto.command() == "illusive-get-forensics-triggering-process-info":
            return_outputs(*get_forensics_triggering_process_info_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-forensics-analyzers":
            return_outputs(*get_forensics_analyzers_command(client, demisto.args()))

        elif demisto.command() == "illusive-get-incident-events":
            return_outputs(*get_incident_events_command(client, demisto.args()))

    # Log exceptions
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}")


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