Coralogix

Fetch incidents, search for supporting data and tag interesting datapoints in/from your Coralogix account.

Data Enrichment & Threat Intelligence · Coralogix

Details

IDCoralogix
ProviderCoralogix
CategoryData Enrichment & Threat Intelligence
From Version5.0.0
Docker Imagedemisto/python3:3.12.8.3296088
Supported ModulesAgentix XSIAM

README

Overview


Use this integration to pull incidents and supporting information from your Coralogix account and tag interesting points in time from Cortex XSOAR.

Use Cases


  1. Configure your Coralogix account as a full fledged SIEM solution by using any of its available integrations and tools and streamline the process of security incident handling by using Cortex XSOAR’s playbooks to automatically pull the incidents from Coralogix and handle them by any of the other Cortex XSOAR integrations.
  2. Use supporting data from Coralogix while you prepare the security incident report directly from the war room in Cortex XSOAR.
  3. Automatically tag the timestamps in Coralogix at which point a security incident was detected by any of the other Cortex XSOAR integrations.

Configure Coralogix on Cortex XSOAR


  1. Navigate to Marketplace.
  2. Search for Coralogix.
  3. Click on Install on the top right corner and then on Install at the bottom right corner.
  4. Once it is installed, click on Settings > Integrations and then on Add instance on the right-hand side and fill in the following parameters:
Parameter Name Description Required Default Value
Fetches incidents Whether or not to fetch incidents via this integration No Do not fetch
Coralogix WebAPI Endpoint URL The Coralogix WebAPI URL Yes (Don’t change it unless instructed to do so by Coralogix personnel) https://webapi.coralogix.com
Private Key Your Coralogix account private key Yes N/A
Application Name (for tags) The Coralogix application name that will be assigned to the tags created by this instance Yes Cortex XSOAR
Subsystem Name (for tags) The Coralogix subsystem name that will be assigned to the tags created by this instance Yes Cortex XSOAR
Coralogix ES-API Endpoint URL The Coralogix ES-API URL Yes https://coralogix-esapi.coralogix.com:9443
Basic incidents query The Lucene query for fetching incidents. If not specified, will return Coralogix alerts that were sent to the Demisto webhook No N/A
Incidents Application Name Limits the incidents query to only return incidents of a specific application name No N/A
Incidents Severity Limits the incidents query to only return incidents of a specific severity No N/A
incidents Name Field The Coralogix field value that should be used as the incident’s name. If not specified, the integration will use the “alert_name” field No N/A
Incidents first fetch days The number of days to look back for incidents No 3
Maximum number of incidents to fetch at a single call Maximum number of incidents to retrieve at each call to Coralogix No 50

Commands


You can execute the following 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. coralogix-search
  2. coralogix-tag

1. coralogix-search


Returns logs from your Coralogix account according to the specified Lucene query

Base Command

coralogix-search

Input
Argument Name Description Required Default
query The Lucene query to run Yes N/A
app_name A Coralogix application name to filter results by No empty
subsystem_name A Coralogix subsystem name to filte results by No empty
severity A Coralogix severity name to filter results by No empty
since_timestamp The timestamp in the format of YYYY-MM-DD (e.g 1978-03-31T23:59:59) from which you would like to start the search No empty
to_timestamp The timestamp in the format of YYYY-MM-DD (e.g 1978-03-31T23:59:59) that will be the upper boundary of the search timespan No ‘now’
max_items_to_retrieve Maximum number of log entries to retrieve from Coralogix No 50
Command Examples

!coralogix-search query="security.rcode_name:\"NXDOMAIN\"" using="Coralogix_instance_1"
!coralogix-search query="security.rcode_name:\"NXDOMAIN\"" max_items_to_retrieve="100" since_timestamp="2020-12-31T23:59:59" using="Coralogix_instance_1"

Output
coralogix.timestamp coralogix.metadata.applicationName coralogix.metadata.subsystemName security.source_ip security.destination_ip security.event_type security.source_port security.destination_port security.protocol security.query security.query_type_name
2020-08-27T02:39:35.886Z test-sta test-sta 172.31.7.153 172.31.0.2 bro_dns 33161 53 udp upload.wikimedia.org.ncsa.uiuc.edu A
2020-08-27T02:52:35.699Z test-sta test-sta 172.31.7.153 172.31.0.2 bro_dns 44618 53 udp www.googgle.com AAAA

2. coralogix-tag


Allows you to tag an interesting point in time in Coralogix from Cortex XSOAR

Base Command

coralogix-tag

Input
Argument Name Description Required Default
name The name of the tag that will be created in Coralogix Yes N/A
timestamp The timestamp at which the tag will be created in Coralogix No Defaults to the current timestamp
icon_url A URL to an icon file (JPG or PNG) that will be displayed as the tag at Coralogix. Can be up to 50KB in size No Defaults to a lightning icon
Command Example

!coralogix-tag name="Data leak started"

Output
Tag added successfully

Configuration parameters

  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • webapi_url — Coralogix WebAPI Endpoint URL (required)
  • privatekey — Private Key (required)
  • app_name — Application Name (for tags) (required)
  • subsystem_name — Subsystem Name (for tags) (required)
  • esapi_url — Coralogix ES-API Endpoint URL (required)
  • incidents_query — Basic incidents query
  • incidents_application_name — Incidents Application Name
  • incidents_severity — Incidents Severity
  • incident_description_field — Incidents Name Field
  • incidents_subsystem_name — Incidents Subsystem Name
  • incidents_first_fetch_range — First fetch timestamp in days
  • incidents_max_fetch — Maximum number of incidents to fetch at a single call

Commands (2)

  • coralogix-search

    Query Coralogix for information.

  • coralogix-tag

    Tag timestamp in Coralogix.

# TODO: Add description to the integration in <root>/Packs/Coralogix/Integrations/Coralogix/Coralogix_description.md
from datetime import UTC, datetime

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

from CommonServerUserPython import *

""" IMPORTS """

import json

import dateutil.parser
import requests
import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """

""" SUPPLEMENTAL FUNCTIONS """


def strip_or_empty(o):
    try:
        if o is not None:
            return o.strip()
        else:
            o = ""
    except AttributeError:
        o = ""

    return o


def flatten_json(y):
    """
    This supplemental method flattens a JSON by renaming subfields using dots as delimiters between levels

    Args:
        y - the JSON to flatten

    Returns:
        A flattened JSON string
    """
    out = {}

    def flatten(x, name=""):
        # If the Nested key-value
        # pair is of dict type
        if type(x) is dict:
            for a in x:
                flatten(x[a], name + a + ".")

            # If the Nested key-value
        # pair is of list type
        elif type(x) is list:
            i = 0

            for a in x:
                flatten(a, name + str(i) + ".")
                i += 1
        else:
            out[name[:-1]] = x

    flatten(y)
    return out


def elastic_output_as_table(raw_elastic_json_obj):
    """
    This supplemental method converts an Elasticsearch output JSON object to a Demisto table

    Args:
         raw_elastic_json_obj - The raw Elasticsearch output as received from the Elasticsearch API
         filter_out_columns - A strings array which contains the list of columns to remove from the output
    Returns:
         An object that represents a Demisto table with the data from the Elasticsearch output
    """

    # fill in the table and the raw contents objects
    table = []
    for raw_row in raw_elastic_json_obj["hits"]["hits"]:
        raw_row_flattened = flatten_json(raw_row["_source"])
        table.append(raw_row_flattened)

    return table


""" COMMANDS """


def coralogix_search_command(
    cgx_private_key,
    cgx_endpoint_url,
    query,
    columns_to_include_in_human_readable,
    application_name,
    subsystem_name,
    severity,
    since_timestamp=None,
    to_timestamp="now",
    max_items_to_retrieve=-1,
):
    """
    This method handles the cgx_search command which allows the user to search for data on a Coralogix account

    Args:
        cgx_private_key - String, Coralogix Private Key (From the integration's settings page)
        cgx_endpoint_url - Coralogix ES-API base URL (From the integration's settings page)
        query - The Lucene query to send to Coralogix
        columns_to_include_in_human_readable - Strings Array, Columns to include in the human readable output
        application_name - String, The Coralogix application name to look for (optional)
        subsystem_name - String, The Coralogix subsystem name to look for (optional)
        severity - String, The Coralogix severity to look for (optional)
    Returns:
        A demisto CommandResults object
    """
    http = urllib3.PoolManager()
    pre_query = ""
    if len(strip_or_empty(application_name)) > 0:
        pre_query = 'coralogix.metadata.applicationName:"' + application_name + '"'
    if len(strip_or_empty(pre_query)) > 0:
        pre_query = pre_query + " AND "
    if len(strip_or_empty(subsystem_name)) > 0:
        pre_query = pre_query + 'coralogix.metadata.subsystemName:"' + subsystem_name + '"'
    if len(strip_or_empty(pre_query)) > 0:
        pre_query = pre_query + " AND "
    if len(strip_or_empty(severity)) > 0:
        pre_query = pre_query + 'coralogix.severity_str:"' + severity + '"'
    if len(strip_or_empty(pre_query)) > 0:
        query = "(" + pre_query + ") AND (" + query + ")"

    request_data = {"query": {"bool": {"must": [{"query_string": {"query": query}}]}}}
    if since_timestamp is not None:
        request_data["query"]["bool"]["must"].append(
            {"range": {"coralogix.timestamp": {"gt": since_timestamp, "lt": to_timestamp}}}
        )
    if since_timestamp is None and to_timestamp != "now":
        raise ValueError("to_timestamp can only be set together with since_timestamp")

    if max_items_to_retrieve > 0:
        request_data["size"] = max_items_to_retrieve
    encoded_data = json.dumps(request_data).encode("utf-8")
    demisto.info("Calling `" + cgx_endpoint_url + "` with these args " + json.dumps(request_data) + " ...")
    r = http.request(
        "POST", cgx_endpoint_url, body=encoded_data, headers={"token": cgx_private_key, "Content-type": "application/json"}
    )
    results_raw = json.loads(r.data.decode("utf-8"))
    if "hits" in results_raw and "hits" in results_raw["hits"]:
        formatted_data = elastic_output_as_table(raw_elastic_json_obj=results_raw)
        return CommandResults(
            raw_response=results_raw,
            readable_output=tableToMarkdown(
                "Coralogix Search Results", formatted_data, columns_to_include_in_human_readable, removeNull=True
            ),
            outputs_key_field="_id",
            outputs_prefix="Coralogix.SearchResults",
            outputs=results_raw["hits"]["hits"],
        )
    else:
        return CommandResults(
            raw_response=results_raw,
            readable_output=tableToMarkdown(
                "Coralogix Search Results", [], columns_to_include_in_human_readable, removeNull=True
            ),
            outputs_key_field="_id",
            outputs_prefix="Coralogix.SearchResults",
            outputs=[],
        )


def coralogix_tag_command(
    private_key, coralogix_url, application_name, subsystem_name, tag_name, tag_timestamp="", tag_icon_url=""
):
    """
    This method handles the cgx_tag command which allows the user to tag a certain timestamp on Coralogix (for example to mark
        the time at which an incident has occurred)

    Args:
        private_key - String, Coralogix Private Key (From the integration's settings page)
        coralogix_url - String, Coralogix Web-API base URL (From the integration's settings page)
        application_name - String, The application name that will be associated with the tag (Would probably be
                       something like 'Demisto')
        subsystem_name - String, The subsystem name that will be associated with the tag (Would probably be
                       something like 'Demisto')
        tag_name - String, The name of the tag that will be created
        tag_timestamp - String, The date at which to put the tag in Coralogix. (Optional, if not set,
                       the current time will be used by Coralogix)
        tag_icon_url - String, A URL for an image that will be used in the tag. Cannot exceed 50KB. (Optional, if not set,
                       Coralogix will automatically choose an image)
    Returns:
        The raw response from Coralogix WebAPI. If it is JSON parsable it will return it as an object, otherwise - as a string.
    """
    query_string_params = {"key": private_key, "application": application_name, "subsystem": subsystem_name, "name": tag_name}
    if len(tag_timestamp) > 0:
        query_string_params["timestamp"] = tag_timestamp
    if len(tag_icon_url) > 0:
        query_string_params["iconUrl"] = tag_icon_url

    demisto.info("Calling `" + coralogix_url + "` using GET with these args " + json.dumps(query_string_params) + " ...")
    response = requests.post(coralogix_url, params=query_string_params)
    try:
        results_obj = json.loads(response.text)
        if "tag_status" in results_obj and results_obj["tag_status"] == "SUCCESSFUL":
            return CommandResults(
                raw_response=results_obj,
                outputs_prefix="Coralogix.TagResults",
                readable_output="Tag added successfully",
                outputs=["Tag was successfully created at " + tag_timestamp + " under the name " + tag_name],
            )
        else:
            return CommandResults(
                raw_response=results_obj,
                outputs_prefix="Coralogix.TagResults",
                readable_output="Failed to add the requested tag",
                outputs=["Failed to tag the following timestamp " + tag_timestamp + " under the name " + tag_name],
            )

    except json.JSONDecodeError:
        raise ValueError(
            "Failed to tag the following timestamp "
            + tag_timestamp
            + " under the name "
            + tag_name
            + ". This is the raw response:\n"
            + response.text
        )


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

    Args:
        cgx_private_key: Coralogix Private Key
        cgx_endpoint_url: Coralogix Cluster Base URL for the Coralogix ES-API

    Returns:
        'ok' if test passed, anything else will fail the test.
    """

    request_data = {"query": {"bool": {"must": [{"range": {"coralogix.timestamp": {"gte": "now-15m", "lt": "now"}}}]}}}

    try:
        encoded_data = json.dumps(request_data).encode("utf-8")
        demisto.info("Calling `" + cgx_endpoint_url + "` with these args " + json.dumps(request_data) + " ...")
        http = urllib3.PoolManager()
        r = http.request(
            "GET", cgx_endpoint_url, body=encoded_data, headers={"token": cgx_private_key, "Content-type": "application/json"}
        )
        results_raw = json.loads(r.data.decode("utf-8"))
    except Exception as ex:
        return "Test failed (" + type(ex).__name__ + ")"

    if results_raw is not None and "hits" in results_raw:
        return "ok"
    else:
        return (
            "Test failed (No result was received from Coralogix or the response was unexpected `" + json.dumps(results_raw) + "`)"
        )


def fetch_incidents(
    cgx_private_key,
    cgx_endpoint_url,
    incidents_base_query,
    application_name,
    subsystem_name,
    severity,
    incidents_name_field,
    incidents_first_fetch_range,
    incidents_max_fetch,
    columns_to_include_in_human_readable,
):
    """
    This method handles querying Coralogix for incidents by using the configured query and parameters

    Args:
        cgx_private_key - String, Coralogix Private Key
        cgx_endpoint_url - String, Coralogix Cluster Base URL for the Coralogix ES-API
        application_name - String, The
        subsystem_name - String,
        severity - String

    Returns:
        Returns the incidents found in Coralogix
    """

    last_run_timestamp = "now-" + str(incidents_first_fetch_range) + "d"
    if "last_run_timestamp" in demisto.getLastRun():
        last_run_timestamp = demisto.getLastRun().get("last_run_timestamp", last_run_timestamp)
    raw_data = coralogix_search_command(
        cgx_private_key=cgx_private_key,
        cgx_endpoint_url=cgx_endpoint_url,
        query=incidents_base_query,
        application_name=application_name,
        subsystem_name=subsystem_name,
        severity=severity,
        columns_to_include_in_human_readable=columns_to_include_in_human_readable,
        since_timestamp=last_run_timestamp,
        max_items_to_retrieve=incidents_max_fetch,
    ).raw_response

    newest_incident_date_obj = datetime(year=1970, month=1, day=1)
    incidents = []
    if "hits" in raw_data and "hits" in raw_data["hits"]:
        for document in raw_data["hits"]["hits"]:
            flattened_document = flatten_json(document["_source"])
            incident_date = flattened_document["coralogix.timestamp"]
            incident = {
                "name": flattened_document[incidents_name_field],
                "occurred": incident_date + "Z",
                "rawJSON": json.dumps(flattened_document),
            }
            incidents.append(incident)
            incident_date_obj = dateutil.parser.parse(incident_date)
            if incident_date_obj.replace(tzinfo=UTC).timestamp() > newest_incident_date_obj.replace(tzinfo=UTC).timestamp():
                newest_incident_date_obj = incident_date_obj

    demisto.setLastRun({"last_run_timestamp": newest_incident_date_obj.replace(tzinfo=UTC).timestamp()})
    return incidents


def main():
    # CONSTANTS

    columns_to_include_in_human_readable = [
        "coralogix.timestamp",
        "coralogix.severity_str",
        "coralogix.metadata.applicationName",
        "coralogix.metadata.subsystemName",
        "security.source_ip",
        "security.destination_ip",
        "security.event_type",
        "security.source_port",
        "security.destination_port",
        "security.connection_state_description",
        "security.protocol",
        "security.local_orig",
        "security.local_respond",
        "security.total_bytes",
        "security.query",
        "security.query_type_name",
        "security.rcode_name",
        "security.ra",
        "security.rd",
        "awsRegion",
        "eventName",
        "eventSource",
        "sourceIPAddress",
        "userIdentity.sessionContext.sessionIssuer.userName",
        "userIdentity.type",
        "recipientAccountId",
        "source_ip",
        "destination_ip",
        "source_port",
        "destination_port",
        "protocol",
    ]
    default_incidents_query = 'alert_type_id:"53d222e2-e7b2-4fa6-80d4-9935425d47dd"'
    default_incidents_first_fetch_range = "3"
    default_incidents_max_fetch = "50"
    default_incident_description_field = "alert_name"
    default_max_items_to_retrieve = "50"

    # PARSE AND VALIDATE INTEGRATION PARAMS
    private_key = strip_or_empty(demisto.params().get("privatekey", ""))

    # get the service API url
    tags_api_url = urljoin(strip_or_empty(demisto.params().get("webapi_url", "")), "/api/v1/addTagPost")
    search_api_url = urljoin(strip_or_empty(demisto.params().get("esapi_url", "")), "/*/_search")

    # Get other parameters
    tag_application_name = strip_or_empty(demisto.params().get("app_name", ""))
    tag_subsystem_name = strip_or_empty(demisto.params().get("subsystem_name", ""))
    incidents_first_fetch_range_raw = strip_or_empty(
        demisto.params().get("incidents_first_fetch_range", default_incidents_first_fetch_range)
    )
    incidents_max_fetch_raw = strip_or_empty(demisto.params().get("incidents_max_fetch", default_incidents_max_fetch))
    incidents_query = strip_or_empty(demisto.params().get("incidents_query", default_incidents_max_fetch))
    incidents_name_field = strip_or_empty(demisto.params().get("incident_description_field", default_incident_description_field))
    incidents_application_name = strip_or_empty(demisto.params().get("incidents_application_name", ""))
    incidents_subsystem_name = strip_or_empty(demisto.params().get("incidents_subsystem_name", ""))
    incidents_severity = strip_or_empty(demisto.params().get("incidents_severity", ""))

    # Assigning defaults if needed
    if len(incidents_query) == 0:
        incidents_query = default_incidents_query
    if len(incidents_name_field) == 0:
        incidents_name_field = default_incident_description_field
    if len(incidents_first_fetch_range_raw) == 0 or not incidents_first_fetch_range_raw.isnumeric():
        incidents_first_fetch_range_raw = default_incidents_first_fetch_range
    if len(incidents_max_fetch_raw) == 0 or not incidents_max_fetch_raw.isnumeric():
        incidents_max_fetch_raw = default_incidents_max_fetch

    incidents_first_fetch_range = int(incidents_first_fetch_range_raw)
    incidents_max_fetch = int(incidents_max_fetch_raw)

    demisto.info(f"Command being called is {demisto.command()}")
    try:
        if demisto.command() == "test-module":
            # This is the call made when pressing the integration Test button.
            result = test_module(cgx_endpoint_url=search_api_url, cgx_private_key=private_key)
            demisto.results(result)
        elif demisto.command() == "fetch-incidents":
            # Set and define the fetch incidents command to run after activated via integration settings.
            if len(incidents_query) > 0 and len(incidents_name_field) > 0:
                incidents = fetch_incidents(
                    cgx_private_key=private_key,
                    cgx_endpoint_url=search_api_url,
                    incidents_base_query=incidents_query,
                    application_name=incidents_application_name,
                    subsystem_name=incidents_subsystem_name,
                    severity=incidents_severity,
                    incidents_name_field=incidents_name_field,
                    columns_to_include_in_human_readable=columns_to_include_in_human_readable,
                    incidents_max_fetch=incidents_max_fetch,
                    incidents_first_fetch_range=incidents_first_fetch_range,
                )
                demisto.incidents(incidents)
            else:
                if len(incidents_query) == 0:
                    raise ValueError("ERROR: Cannot fetch incidents. No incidents query defined.")
                if len(incidents_name_field) == 0:
                    raise ValueError("ERROR: Cannot fetch incidents. The incident_description_field is empty.")
                demisto.incidents([])
        elif demisto.command() == "coralogix-tag":
            if "name" in demisto.args():
                tag_name = strip_or_empty(demisto.args().get("name", ""))
            else:
                raise Exception("Tag name is missing")
            if "timestamp" in demisto.args():
                tag_timestamp = strip_or_empty(demisto.args().get("timestamp", ""))
            else:
                tag_timestamp = ""
            if "icon_url" in demisto.args():
                tag_icon_url = strip_or_empty(demisto.args().get("icon_url", ""))
            else:
                tag_icon_url = ""
            return_results(
                coralogix_tag_command(
                    private_key=private_key,
                    coralogix_url=tags_api_url,
                    application_name=tag_application_name,
                    subsystem_name=tag_subsystem_name,
                    tag_name=tag_name,
                    tag_timestamp=tag_timestamp,
                    tag_icon_url=tag_icon_url,
                )
            )
        elif demisto.command() == "coralogix-search":
            if "query" in demisto.args():
                query = demisto.args()["query"]
            else:
                raise Exception("No query specified")
            if "app_name" in demisto.args():
                search_app_name = strip_or_empty(demisto.args().get("app_name", ""))
            else:
                search_app_name = ""
            if "subsystem_name" in demisto.args():
                search_subsystem_name = strip_or_empty(demisto.args().get("subsystem_name", ""))
            else:
                search_subsystem_name = ""
            if "severity" in demisto.args():
                search_severity = strip_or_empty(demisto.args().get("severity", ""))
            else:
                search_severity = ""
            if "since_timestamp" in demisto.args():
                since_timestamp = strip_or_empty(demisto.args().get("since_timestamp", ""))
            else:
                since_timestamp = None
            if "to_timestamp" in demisto.args():
                to_timestamp = strip_or_empty(demisto.args().get("to_timestamp", ""))
            else:
                to_timestamp = "now"
            if "max_items_to_retrieve" in demisto.args():
                max_items_to_retrieve_raw = strip_or_empty(demisto.args().get("max_items_to_retrieve", ""))
                if not max_items_to_retrieve_raw.isnumeric():
                    max_items_to_retrieve_raw = default_max_items_to_retrieve
            else:
                max_items_to_retrieve_raw = default_max_items_to_retrieve
            max_items_to_retrieve = int(max_items_to_retrieve_raw)

            return_results(
                coralogix_search_command(
                    cgx_private_key=private_key,
                    cgx_endpoint_url=search_api_url,
                    query=query,
                    application_name=search_app_name,
                    subsystem_name=search_subsystem_name,
                    severity=search_severity,
                    since_timestamp=since_timestamp,
                    to_timestamp=to_timestamp,
                    max_items_to_retrieve=max_items_to_retrieve,
                    columns_to_include_in_human_readable=columns_to_include_in_human_readable,
                )
            )
        else:
            demisto.error("Unknown command")

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


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