Tessian

Tessian is an email security platform that allows organizations to protect their users from inbound phishing threats, outbound data loss (both malicious and accidental) and account takeovers.

Data Enrichment & Threat Intelligence · Tessian

Details

IDTessian
ProviderProofpoint
CategoryData Enrichment & Threat Intelligence
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Tessian is an email security platform that allows organizations to protect their users from inbound phishing threats, outbound data loss (both malicious and accidental) and account takeovers.

Configure Tessian in Cortex

Parameter Description Required
Portal URL The URL that you use to access the Tessian Portal. Please include the extension, e.g. “example.tessian-platform.com” or “example.tessian-app.com” True
API Key The API Key to use to connect to the Tessian API. This can be found under “Security Integrations” in your Tessian Portal (/0/admin/integrations/api/tokens) True
Trust any certificate (not secure)   False
Use system proxy settings   False

Commands

You can execute these commands from the 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.

tessian-list-events


This command allows you to pull Tessian event data into your XSOAR instance.

Base Command

tessian-list-events

Input

Argument Name Description Required
limit The maximum number of events you would like Tessian to return per call. The maximum value is 100. The minimum value is 2. Optional
after_checkpoint If provided, this parameter must be set to the checkpoint returned by a previous request to this endpoint. When provided, events from the previous request will not be included in the response from this request. If the new checkpoint returned by this request is used in yet another call to this endpoint events from both previous requests will not be included in the response (and so on). By making a number of consecutive requests to this endpoint where the checkpoint from the previous request is provided, clients can get all events from the Tessian platform, even when there are many more than can be returned in a single request. This process is often referred to as pagination. If an event is updated, it will no longer be excluded from subsequent requests. Optional
created_after Only include events that were created after this time. For example, 2020-02-02T19:00:00Z. Optional

Context Output

Path Type Description
Tessian.EventsOutput.checkpoint String This value can be provided to a subsequent request via the after_checkpoint query parameter to ensure that events from this request are not returned in future responses. This allows clients to paginate through results.
Tessian.EventsOutput.additional_results Boolean True if there may be more events that can be immediately retrieved.
Tessian.EventsOutput.results Unknown The events returned by this request.

Command example

!tessian-list-events limit=2

Context Example

{
    "Tessian": {
        "EventsOutput": {
            {
                "checkpoint": "string",
                "additional_results": true,
                "results": [
                    {
                        "id": "string",
                        "type": "string",
                        "created_at": "2019-08-24T14:15:22Z",
                        "updated_at": "2019-08-24T14:15:22Z",
                        "portal_link": "string",
                        "outbound_email_details": {
                            "send_time": "2019-08-24T14:15:22Z",
                            "tessian_action": "WARN",
                            "message_id": "string",
                            "tessian_id": "string",
                            "from": null,
                            "transmitter": null,
                            "reply_to": [
                                null
                            ],
                            "recipients": {
                                "to": [
                                    null
                                ],
                                "cc": [
                                    null
                                ],
                                "bcc": [
                                    null
                                ],
                                "all": [
                                    null
                                ],
                                "count": 0
                            },
                            "subject": "string",
                            "attachments": {
                                "names": [
                                    "string"
                                ],
                                "count": 0,
                                "bytes": 0
                            }
                        },
                        "guardian_details": {
                            "triggered_filter_ids": [
                                "string"
                            ],
                            "type": "MISDIRECTED_EMAIL",
                            "triggered_filter_names": [
                            "string"
                            ],
                            "breach_prevented": true,
                            "anomalous_recipients": [
                                null
                            ],
                            "suggested_recipients": [
                                null
                            ],
                            "anomalous_attachments": [
                                "string"
                            ],
                            "final_outcome": null,
                            "user_responses": [
                                "SEND"
                            ],
                            "admin_action": "SAFE",
                            "justifications": [
                                "string"
                            ],
                            "user_shown_message": true
                        }
                    },
                    {
                        "id": "string",
                        "type": "string",
                        "created_at": "2019-08-24T14:15:22Z",
                        "updated_at": "2019-08-24T14:15:22Z",
                        "portal_link": "string",
                        "inbound_email_details": {
                            "received_time": "2019-08-24T14:15:22Z",
                            "urls": ["strings"],
                            "attachment_urls": ["strings"],
                            "message_id": "string",
                            "tessian_id": "string",
                            "from": null,
                            "transmitter": null,
                            "reply_to": [null],
                            "recipients": {
                                "to": [
                                    null
                                ],
                                "cc": [
                                    null
                                ],
                                "bcc": [
                                    null
                                ],
                                "all": [
                                    null
                                ],
                                "count": 0
                            },
                            "subject": "string",
                            "attachments": {
                                "names": [
                                    "string"
                                ],
                                "count": 0,
                                "bytes": 0
                            }
                        },
                        "defender_details": {
                            "burst_attack_id": "string",
                            "intent_types": ["INVOICE"],
                            "threat_signal_types": ["string"],
                            "threat_types": ["MATCHED_DENYLIST"],
                            "spf_result": null,
                            "dkim_result": null,
                            "dmarc_result": null,
                            "sender_location": null,
                            "users_responded": {
                                "malicious": 1,
                                "safe" 0,
                                "unsure": 0,
                                "deleted": 0,
                            },
                            "admin_label": null,
                            "quarantine_status_count": {
                                "admin_quarantine_status_count": {
                                    "not_quarantined": 0,
                                    "quarantined": 1,
                                    "released": 0,
                                    "deleted": 0,
                                    "pending_release": 0,
                                },
                                "user_quarantine_status_count": {
                                    "not_quarantined": 0,
                                    "quarantined": 1,
                                    "released": 0,
                                    "deleted": 0,
                                    "pending_release": 0,
                                },
                            },
                            "deletion_status_count": {
                                "deleted": 1,
                                "deletion_pending": 0,
                                "not_deleted": 0,
                            },
                            "number_protected_users": 1,
                            "confidence": HIGH,
                            "impersonation_type": null,
                            "impersonated_domain": null,
                            "impersonated_address": null,
                        }
                    }
                ]
            }
        }
    }
}

Human Readable Output

Tessian Events

Checkpoint: eyJzb3J0X3ZhbHVlcyI6IFsxNjkxNTkyNTc4Mjg4LCAiaW5ib3VuZC1lNWI1MmQyYWQ3ZGQ4MTdhMGRhYmVhZjgzMDhhYWMwMDhkZDY3ZDg1ZTQ3MTk1NDE4NTZmMzRkN2JlY2Y4ZTNlIl0sICJyZXZlcnNlIjogZmFsc2V9

Additional Results: True

Number of events returned: 2

Results

Event ID Event Type Event Created At Event Updated At Portal Link
string string 2019-08-24T14:15:22Z 2019-08-24T14:15:22Z string
string string 2019-08-24T14:15:22Z 2019-08-24T14:15:22Z string

tessian-release-from-quarantine


This command allows you to release a quarantined emails associated with an event from Tessian.

Base Command

tessian-release-from-quarantine

Input

Argument Name Description Required
event_id The ID of the event you would like to release from quarantine. Required

Context Output

Path Type Description
Tessian.ReleaseFromQuarantineOutput.number_of_actions_attempted String The number of users that release from quarantine actions were attempted for.
Tessian.ReleaseFromQuarantineOutput.number_of_actions_succeeded String The number of users that the release from quarantine action was successful for.
Tessian.ReleaseFromQuarantineOutput.results Unknown The results of the release action. This is an array of objects mapping the email address of users to the result of the release action.
Tessian.ReleaseFromQuarantineOutput.event_id String The event ID that was submitted for release.

Command example

!tessian-release-from-quarantine event_id="string"

Context Example

{
    "Tessian": {
        "EventsOutput": {
            "number_of_actions_attempted": 1,
            "number_of_actions_succeeded": 1,
            "results": [
                {
                    "user_address": "test_user@example.com",
                    "error": null,
                },
                {
                    "user_address": "test_user2@example.com",
                    "error": "EMAIL_ALREADY_REMEDIATED",
                },
            ]
        }
    }
}

Human Readable Output

Release from Quarantine Action

Event ID: string

Number of Release Actions Successfully Initiated: 1

Number of Release Actions Failed: 1

Errors

Recipient Error
test_user2@example.com EMAIL_ALREADY_REMEDIATED

tessian-delete-from-quarantine


This command allows you to delete quarantined emails associated with an event from Tessian.

Base Command

tessian-delete-from-quarantine

Input

Argument Name Description Required
event_id The ID of the event you would like to delete from quarantine. Required

Context Output

Path Type Description
Tessian.DeleteFromQuarantineOutput.number_of_actions_attempted String The number of users that delete from quarantine actions were attempted for.
Tessian.DeleteFromQuarantineOutput.number_of_actions_succeeded String The number of users that the delete from quarantine action was successful for.
Tessian.DeleteFromQuarantineOutput.results Unknown The results of the delete action. This is an array of objects mapping the email address of users to the result of the delete action.
Tessian.DeleteFromQuarantineOutput.event_id String The event ID that was submitted for deletion.

Command example

!tessian-delete-from-quarantine event_id="string"

Context Example

{
    "Tessian": {
        "EventsOutput": {
            "number_of_actions_attempted": 1,
            "number_of_actions_succeeded": 1,
            "results": [
                {
                    "user_address": "test_user@example.com",
                    "error": null,
                },
                {
                    "user_address": "test_user2@example.com",
                    "error": "EMAIL_ALREADY_REMEDIATED",
                },
            ]
        }
    }
}

Human Readable Output

Delete from Quarantine Action

Event ID: string

Number of Delete Actions Successfully Initiated: 1

Number of Delete Actions Failed: 1

Errors

Recipient Error
test_user2@example.com EMAIL_ALREADY_REMEDIATED

tessian-delete-from-inbox


This command allows you to delete emails associated with a Tessian event from your inbox.

Base Command

tessian-delete-from-inbox

Input

Argument Name Description Required
event_id The ID of the event you would like to delete from inbox. Required

Context Output

Path Type Description
Tessian.DeleteFromQuarantineOutput.number_of_actions_attempted String The number of users that delete from inbox actions were attempted for.
Tessian.DeleteFromQuarantineOutput.number_of_actions_succeeded String The number of users that the delete from inbox action was successful for.
Tessian.DeleteFromQuarantineOutput.results Unknown The results of the delete action. This is an array of objects mapping the email address of users to the result of the delete action.
Tessian.DeleteFromQuarantineOutput.event_id String The event ID that was submitted for deletion.

Command example

!tessian-delete-from-inbox event_id="string"

Context Example

{
    "Tessian": {
        "EventsOutput": {
            "number_of_actions_attempted": 1,
            "number_of_actions_succeeded": 1,
            "results": [
                {
                    "user_address": "test_user@example.com",
                    "error": null,
                },
                {
                    "user_address": "test_user2@example.com",
                    "error": "ALREADY_DELETED",
                },
            ]
        }
    }
}

Human Readable Output

Delete from Inbox Action

Event ID: string

Number of Delete Actions Successfully Initiated: 1

Number of Delete Actions Failed: 1

Errors

Recipient Error
test_user2@example.com ALREADY_DELETED

Configuration parameters

  • url — Portal URL (required)
  • api_key — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (4)

  • tessian-delete-from-inbox

    This command allows you to delete emails associated with a Tessian event from your inbox.

  • tessian-delete-from-quarantine

    This command allows you to delete quarantined emails associated with an event from Tessian.

  • tessian-list-events

    This command allows you to pull Tessian event data into your XSOAR instance.

  • tessian-release-from-quarantine

    This command allows you to release a quarantined emails associated with an event from Tessian.

from typing import Any

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

from CommonServerUserPython import *  # noqa

# Disable insecure warnings
urllib3.disable_warnings()


""" CONSTANTS """

DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"  # ISO8601 format with UTC, default in XSOAR & Tessian

""" CLIENT CLASS """


class Client(BaseClient):
    """Client class to interact with the service API

    This Client implements API calls, and does not contain any XSOAR logic.
    Should only do requests and return data.
    It inherits from BaseClient defined in CommonServer Python.
    Most calls use _http_request() that handles proxy, SSL verification, etc.
    For this  implementation, no special attributes defined
    """

    def list_events(self, limit: int | None, after_checkpoint: str | None, created_after: str | None) -> dict[str, Any]:
        params = assign_params(limit=limit, after_checkpoint=after_checkpoint, created_after=created_after)

        return self._http_request(method="GET", url_suffix="/api/v1/events", params=params, resp_type="json", ok_codes=(200,))

    def release_from_quarantine(self, event_id: str) -> dict[str, Any]:
        return self._http_request(
            method="POST",
            url_suffix="/api/v1/remediation/release_from_quarantine",
            json_data={"event_id": event_id},
            resp_type="json",
            ok_codes=(200,),
        )

    def delete_from_quarantine(self, event_id: str) -> dict[str, Any]:
        return self._http_request(
            method="POST",
            url_suffix="/api/v1/remediation/delete_from_quarantine",
            json_data={"event_id": event_id},
            resp_type="json",
            ok_codes=(200,),
        )

    def delete_from_inbox(self, event_id: str) -> dict[str, Any]:
        return self._http_request(
            method="POST",
            url_suffix="/api/v1/remediation/delete_from_inbox",
            json_data={"event_id": event_id},
            resp_type="json",
            ok_codes=(200,),
        )


""" HELPER FUNCTIONS """


def format_url(url: str) -> str:
    """
    This function strips the url to make sure it's in the expected format.
    We want to be working with a url that looks like this: https://domain.tessian.com
    """

    #  Remove leading http/https, we do this so that we can add the https:// prefix in the return
    if url.startswith("http://"):
        # We should never have insecure portals, but just in case the customer enters their url
        # with http:// for whatever reason, we'll strip it to add a secure prefix
        url = url[7:]
    elif url.startswith("https://"):
        #  Just strip this so that we can ensure it's not there for the rest of the logic.
        url = url[8:]

    # Remove trailing slashes
    if "/" in url:
        #  We will disregard everything after the trailing slash to obtain the portal URL.
        # This should cover customers who enter their api url by mistake.
        url = url.split("/")[0]

    # Add the https:// prefix in the return
    return f"https://{url}"


""" COMMAND FUNCTIONS """


def list_events_command(client: Client, args: dict[str, Any]) -> CommandResults:
    limit = int(args.get("limit", 100))
    after_checkpoint = args.get("after_checkpoint", None)
    created_after = args.get("created_after", None)

    results = client.list_events(limit, after_checkpoint, created_after)

    summary_results = [
        {
            "Event ID": event.get("id"),
            "Event Type": event.get("type"),
            "Event Created At": event.get("created_at"),
            "Event Updated At": event.get("updated_at"),
            "Portal Link": event.get("portal_link"),
        }
        for event in results.get("results", [])
    ]

    markdown = "# Tessian Events\n"
    markdown += f'## Checkpoint: {results.get("checkpoint")}\n'
    markdown += f'## Additional Results: {results.get("additional_results")}\n'
    markdown += f'### Number of events returned: {len(results.get("results", []))}\n'
    markdown += tableToMarkdown(
        name="Results",
        t=summary_results,
        headers=["Event ID", "Event Type", "Event Created At", "Event Update At", "Portal Link"],
    )

    return CommandResults(
        outputs_prefix="Tessian.EventsOutput",
        outputs_key_field="checkpoint",
        outputs=results,
        raw_response=results,
        readable_output=markdown,
    )


def release_from_quarantine_command(client: Client, args: dict[str, Any]) -> CommandResults:
    event_id = args.get("event_id", None)

    if event_id is None:
        raise ValueError("Event ID is required")

    results = client.release_from_quarantine(event_id)
    results["event_id"] = event_id

    markdown = "# Release from Quarantine Action\n"
    markdown += f"## Event ID: {event_id}\n"
    markdown += f'## Number of Release Actions Successfully Initiated: {results.get("number_of_actions_succeeded")}\n'
    failure_count = results.get("number_of_actions_attempted", 0) - results.get("number_of_actions_succeeded", 0)
    markdown += f"## Number of Release Actions Failed: {failure_count}\n"

    if results.get("number_of_actions_attempted") != results.get("number_of_actions_succeeded"):
        failures = [
            {"Recipient": failure.get("user_address"), "Error": failure.get("error")}
            for failure in results.get("results", [])
            if failure.get("error") is not None
        ]
        markdown += tableToMarkdown(
            name="Errors",
            t=failures,
            headers=["Recipient", "Error"],
        )

    return CommandResults(
        outputs_prefix="Tessian.ReleaseFromQuarantineOutput",
        outputs_key_field="event_id",
        outputs=results,
        raw_response=results,
        readable_output=markdown,
    )


def delete_from_quarantine_command(client: Client, args: dict[str, Any]) -> CommandResults:
    event_id = args.get("event_id", None)

    if event_id is None:
        raise ValueError("Event ID is required")

    results = client.delete_from_quarantine(event_id)
    results["event_id"] = event_id

    markdown = "# Delete from Quarantine Action\n"
    markdown += f"## Event ID: {event_id}\n"
    markdown += f'## Number of Delete Actions Successfully Initiated: {results.get("number_of_actions_succeeded")}\n'
    failure_count = results.get("number_of_actions_attempted", 0) - results.get("number_of_actions_succeeded", 0)
    markdown += f"## Number of Delete Actions Failed: {failure_count}\n"

    if results.get("number_of_actions_attempted") != results.get("number_of_actions_succeeded"):
        failures = [
            {"Recipient": failure.get("user_address"), "Error": failure.get("error")}
            for failure in results.get("results", [])
            if failure.get("error") is not None
        ]
        markdown += tableToMarkdown(
            name="Errors",
            t=failures,
            headers=["Recipient", "Error"],
        )

    return CommandResults(
        outputs_prefix="Tessian.DeleteFromQuarantineOutput",
        outputs_key_field="event_id",
        outputs=results,
        raw_response=results,
        readable_output=markdown,
    )


def delete_from_inbox_command(client: Client, args: dict[str, Any]) -> CommandResults:
    event_id = args.get("event_id", None)

    if event_id is None:
        raise ValueError("Event ID is required")

    results = client.delete_from_inbox(event_id)
    results["event_id"] = event_id

    markdown = "# Delete from Inbox Action\n"
    markdown += f"## Event ID: {event_id}\n"
    markdown += f'## Number of Delete Actions Successfully Initiated: {results.get("number_of_actions_succeeded")}\n'
    failure_count = results.get("number_of_actions_attempted", 0) - results.get("number_of_actions_succeeded", 0)
    markdown += f"## Number of Delete Actions Failed: {failure_count}\n"

    if results.get("number_of_actions_attempted") != results.get("number_of_actions_succeeded"):
        failures = [
            {"Recipient": failure.get("user_address"), "Error": failure.get("error")}
            for failure in results.get("results", [])
            if failure.get("error") is not None
        ]
        markdown += tableToMarkdown(
            name="Errors",
            t=failures,
            headers=["Recipient", "Error"],
        )

    return CommandResults(
        outputs_prefix="Tessian.DeleteFromInboxOutput",
        outputs_key_field="event_id",
        outputs=results,
        raw_response=results,
        readable_output=markdown,
    )


def test_module(client: Client) -> str:  #  pragma: no cover
    """
    Tests API connectivity and authentication'
    Returning 'ok' indicates that connection to the service is successful.
    Raises exceptions if something goes wrong.
    """

    try:
        response = client.list_events(2, None, None)

        success = demisto.get(response, "checkpoint")
        if success is None:
            return f"Unexpected result from the service: expected checkpoint to be a string, response={response!s}"

        return "ok"
    except Exception as e:
        exception_text = str(e).lower()
        if "forbidden" in exception_text or "authorization" in exception_text:
            return "Authorization Error: make sure API Key is correctly set"
        else:
            raise e


""" MAIN FUNCTION """


def main() -> None:  #  pragma: no cover
    """main function, parses params and runs command functions

    :return:
    :rtype:
    """

    # get the service API url
    params = demisto.params()
    args = demisto.args()
    base_url = format_url(params.get("url"))
    api_key = params.get("api_key", {}).get("password")

    # if your Client class inherits from BaseClient, SSL verification is
    # handled out of the box by it, just pass ``verify_certificate`` to
    # the Client constructor
    verify_certificate = not demisto.params().get("insecure", False)

    # if your Client class inherits from BaseClient, system proxy is handled
    # out of the box by it, just pass ``proxy`` to the Client constructor
    proxy = demisto.params().get("proxy", False)

    demisto.debug(f"Command being called is {demisto.command()}")
    try:
        headers: dict = {}
        headers["Authorization"] = f"API-Token {api_key}"

        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)
            return_results(result)

        elif demisto.command() == "tessian-list-events":
            return_results(list_events_command(client, args))

        elif demisto.command() == "tessian-release-from-quarantine":
            return_results(release_from_quarantine_command(client, args))

        elif demisto.command() == "tessian-delete-from-quarantine":
            return_results(delete_from_quarantine_command(client, args))

        elif demisto.command() == "tessian-delete-from-inbox":
            return_results(delete_from_inbox_command(client, args))
        else:
            raise NotImplementedError(f"Either the command, {demisto.command}, is not supported yet or it does not exist.")

    # Log exceptions and return errors
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


""" ENTRY POINT """


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