Query.AI

Query.AI is a decentralized data access and analysis technology that simplifies security investigations across disparate platforms without data duplication.

Analytics & SIEM · QueryAI

Details

IDQuery.AI
ProviderQueryAI Inc.
CategoryAnalytics & SIEM
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Query.AI

Query.AI is a decentralized data access and analysis technology that simplifies security investigations across disparate platforms, without data duplication.

In order to use this integration you need the following:

  1. The URL of Query.AI Proxy component (see below)
  2. An account registered with Query.AI belonging to your Organization
  3. The API token associated with above account
  4. Platform Connection Details of any platform integrated via Query.AI you wish to connect to (This can be overridden while executing commands)

BASE_URL

The base URL would be of the Query.AI Proxy . Replace with hostname and port of the Query.AI Proxy component running in your environment.

Configure Query.AI in Cortex

Parameter Description Required
url Query.AI Proxy URL True
api_token Query.AI API token True
alias Default Platform Alias to retrieve data True
connection_params Default Connection params as JSON object. Eg - {“platform_alias”:{“username”:”my_username”,”password”:”my_password”}} True
timeout Request Timeout (in seconds). Default value is 60 seconds but it may take longer time to retrieve data based upon your data platform. False
proxy Use system proxy settings False
insecure Trust any certificate (not secure) 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.

  1. Returns response for the query being run on Query.AI: queryai-run-query

1. queryai-run-query


Returns response for the query being run on Query.AI.

Base Command

queryai-run-query

Input
Argument Name Description Required
query Search Query. Required
alias Platform Alias. Optional
connection_params Connection params as JSON object. Eg- {“alias”:{“username”:”my_username”,”password”:”my_password”}}. Optional
workflow_params Workflow params as JSON object. Eg- {“param1”:”value1”,”param2”:”value2”}. Optional
time_text Search time period. Optional
Context Output
Path Type Description
QueryAI.query.result Unknown Response after running query.
QueryAI.query.markdown_string String Readable Response after running query.
Command Example

!queryai-run-query query="run workflow my_workflow" alias="my_alias" connection_params="{\"my_alias\":{\"username\":\"my_username\",\"password\":\"my_password\"}}" workflow_params="{\"param1\":\"value1\",\"param2\":\"value2\"}" time_text="search 1 year ago to now"

Context Example
{
    "QueryAI": {
        "query": {
            "markdown_string": "### Query.AI Result for the query: run workflow my_workflow\n|agegroupbin|agegroupdesc|\n|---|---|\n| 2 | 18-19 |\n| 3 | 20-21 |\n### Click here to [see details](https://app.query.ai/login;questions=run%20workflow%20my_workflow;alias=my_alias;queryDuration=search%201%20year%20ago%20to%20now;params=%7B%22param1%22%3A%22value1%22%2C%22param2%22%3A%22value2%22%7D;)",
            "result": [
                {
                    "agegroupbin": 2,
                    "agegroupdesc": "18-19"
                },
                {
                    "agegroupbin": 3,
                    "agegroupdesc": "20-21"
                }
            ]
        }
    }
}
Human Readable Output

Query.AI Result for the query: run workflow my_workflow

agegroupbin agegroupdesc
2 18-19
3 20-21

Click here to see details


Support

For any other assistance or feedback, feel free to contact us.

Configuration parameters

  • url — Query.AI Proxy URL (required)
  • api_token — API Token (required)
  • alias — Default Platform Alias (required)
  • connection_params — Default Platform Connection Params. (required)
  • timeout — Request Timeout (in seconds).
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (1)

  • queryai-run-query

    Returns response for the query being run on Query.AI.

import urllib.parse
from typing import Any

import demistomock as demisto
import urllib3
from CommonServerPython import *

from CommonServerUserPython import *

# Disable insecure warnings
urllib3.disable_warnings()

DEFAULT_TIMEOUT = 60  # in seconds


class Client(BaseClient):  # type: ignore
    def __init__(
        self,
        base_url,
        verify=True,
        proxy=False,
        ok_codes=(),
        headers=None,
        auth=None,
        api_token=None,
        connection_params="{}",
        alias=None,
        timeout=DEFAULT_TIMEOUT,
    ):
        super().__init__(base_url, verify, proxy, ok_codes, headers, auth)
        self._api_token = api_token
        self.alias = alias
        self.connection_params = safe_load_json(connection_params) if connection_params else {}
        self.timeout = timeout
        self._headers = {"Content-Type": "application/json", "Accept": "application/json", "Authorization": self._api_token}

    def queryai_http_request(self, method, url_suffix, json_data=None, params=None, data=None, **kwargs):
        if not json_data:
            json_data = {}
        return self._http_request(
            method=method, url_suffix=url_suffix, params=params, json_data=json_data, data=data, timeout=self.timeout, **kwargs
        )

    def run_query(
        self, query: str, connection_params: str = None, workflow_params: str = None, time_text: str = None, alias: str = None
    ) -> dict[str, Any]:
        """Gets result from QueryAI for the given query

        :type query: ``str``
        :param query: Query to be run

        :type connection_params: ``str``
        :param connection_params: Connection parameters as Stringified JSON

        :type workflow_params: ``str``
        :param workflow_params: Workflow parameters as Stringified JSON

        :type time_text: ``str``
        :param time_text: Search time-period

        :type alias: ``str``
        :param alias: Platform alias name

        :return: dict containing the response from the API
        :rtype: ``Dict[str, Any]``
        """

        if workflow_params:
            workflow_params = safe_load_json(workflow_params)

        if connection_params:
            connection_params = safe_load_json(connection_params)
        else:
            connection_params = self.connection_params  # type: ignore[assignment]

        if not alias:
            alias = self.alias

        return self.queryai_http_request(
            method="POST",
            url_suffix="/query",
            json_data={
                "query": query,
                "connection_params": connection_params,
                "workflow_params": workflow_params,
                "time_text": time_text,
                "alias": alias,
            },
        )


def generate_drilldown_url(query, alias, time_text=None, workflow_params=None):
    base_drilldown = "https://app.query.ai/login;"
    questions_url_param = f"questions={urllib.parse.quote(query)};"
    alias_url_param = f"alias={urllib.parse.quote(alias)};"
    drilldown_url = base_drilldown + questions_url_param + alias_url_param

    if time_text:
        query_duration_url_param = f"queryDuration={urllib.parse.quote(time_text)};"
        drilldown_url += query_duration_url_param
    if workflow_params:
        workflow_params_url_param = f"params={urllib.parse.quote(workflow_params)};"
        drilldown_url += workflow_params_url_param

    return drilldown_url


def queryai_run_query_command(client: Client, args: dict[str, Any]) -> CommandResults:  # type: ignore
    """queryai-run-query command: Returns response for the query being run on QueryAI

    :type client: ``Client``
    :param client: QueryAI client to use

    :type args: ``Dict[str, Any]``
    :param args:
        all command arguments, usually passed from ``demisto.args()``.
        ``args['query']`` query to run

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains an alert

    :rtype: ``CommandResults``
    """

    query = args.get("query", "")
    connection_params = args.get("connection_params", client.connection_params)
    workflow_params = args.get("workflow_params", None)
    time_text = args.get("time_text", None)
    alias = args.get("alias", client.alias)

    if not query:
        raise ValueError('Missing argument: "query"')

    try:
        result = client.run_query(
            query=query, connection_params=connection_params, workflow_params=workflow_params, time_text=time_text, alias=alias
        )

        drilldown_url = f"### Click here to [see details]({generate_drilldown_url(query, alias, time_text, workflow_params)})"
        readable_output = tableToMarkdown(f"Query.AI Result for the query: {query}", result["data"]) if result.get("data") else ""
        readable_output = readable_output + "### " + result["reply"] + "\n" if result.get("reply") else readable_output
        readable_output += drilldown_url
        reply = {"result": result["data"] if result.get("data") else result["reply"], "markdown_string": readable_output}
        return CommandResults(
            readable_output=readable_output, outputs_prefix="QueryAI.query", outputs_key_field="", outputs=reply
        )
    except DemistoException as e:
        return_error(str(e))


def test_module(client: Client) -> str:
    """Tests API connectivity and authentication'

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    :type client: ``Client``
    :param client: QueryAI client to use

    :return: 'ok' if test passed, anything else will fail the test.
    :rtype: ``str``
    """

    try:
        client.run_query(query="hello")
    except DemistoException as e:
        if "Forbidden" in str(e):
            return "Authorization Error: make sure API token is correctly set"
        elif "requests.exceptions.ConnectionError" in str(e) or "Error in API call" in str(e):
            return "Connection Error - Check that the Query.AI Proxy URL parameter is correct."
        else:
            raise e
    return "ok"


def main() -> None:
    """main function, parses params and runs command functions"""
    params = demisto.params()
    api_token = params.get("api_token")
    base_url = urljoin(params["url"], "/api/v1")
    alias = params.get("alias")
    connection_params = params.get("connection_params", "{}")
    timeout = int(params.get("timeout", DEFAULT_TIMEOUT))

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

    demisto.debug(f"Command being called is {demisto.command()}")
    try:
        headers = {}  # type:  Dict[str, str]
        client = Client(
            base_url=base_url,
            verify=verify_certificate,
            headers=headers,
            proxy=proxy,
            api_token=api_token,
            alias=alias,
            connection_params=connection_params,
            timeout=timeout,
        )

        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() == "queryai-run-query":
            return_results(queryai_run_query_command(client, demisto.args()))

        else:
            return_error(f"Unsupported Command: {demisto.command()}.\n")

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


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