Kibana
This integration enables using Elastic Security for SIEM for security operations management and searching Elastic logs. This pack is to be used in combination with the Elasticsearch v2 integration.
Analytics & SIEM · Kibana
Details
| ID | Kibana |
|---|---|
| Provider | Elastic |
| Category | Analytics & SIEM |
| From Version | 6.0.0 |
| Docker Image | demisto/elasticsearch:1.0.0.10133006 |
README
Use the Kibana integration to manage Elastic Security cases, detection alerts, rules, and value lists for security operations.
This integration was tested with Elasticsearch versions 6.6.2, 7.3, 8.4.1, and 9.3.1.
Configure Kibana in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | The Elasticsearch server to which the integration connects. Ensure that the URL includes the correct Elasticsearch port. The default port for Elasticsearch v7 and below is 9200. Use the Server URL for on-premises deployments. | True |
| Elastic API Port | The port for the Elastic API. | False |
| Kibana API Port | The port for the Kibana API. | False |
| Authorization type | The authentication type and credentials to use: Basic Auth (Username and Password), Bearer Auth (Username and Password), or API Key Auth (API Key ID and API Key). | False |
| API key ID | False | |
| API Key | False | |
| Username | The username and password to use instead of API key and API ID. | False |
| Password | False | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Client type | In some hosted ElasticSearch environments, the standard ElasticSearch client is not supported. If you encounter any related client issues, please consider using the OpenSearch client type. | False |
| Request timeout (in seconds). | 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.
kibana-cases-find
Lists cases in Kibana.
Base Command
kibana-cases-find
Input
| Argument Name | Description | Required |
|---|---|---|
| status | The status of the cases to retrieve. Possible values are: open, in-progress, closed. Default is open. | Optional |
| severity | The severity of the cases to retrieve. Possible values are: critical, high, medium, low. | Optional |
| from_time | The earliest time to search from (for example, 2025-10-02T00:27:58.162Z). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.Cases.Status | unknown | The status of the case in Kibana. |
| Kibana.Cases.Version | unknown | The version number of the case in Kibana. |
| Kibana.Cases.ID | unknown | The ID number of the case in Kibana. |
kibana-case-alerts-find
Returns information on the alerts of the input case in Kibana.
Base Command
kibana-case-alerts-find
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | The ID of the case in Kibana. Locate it with the “kibana-cases-find” command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.CaseAlerts.ID | unknown | The ID of alerts tied to the case in Kibana. |
kibana-alert-status-update
Updates the status of an input alert.
Base Command
kibana-alert-status-update
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The alert ID to update. Find it with the “kibana-detection-alerts-list” command. | Required |
| status | The status to set the alert to. Possible values are: open, closed. | Required |
Context Output
There is no context output for this command.
kibana-case-status-update
Updates the status of an input case.
Base Command
kibana-case-status-update
Input
| Argument Name | Description | Required |
|---|---|---|
| status | The status of the case to update. Possible values are: open, in-progress, closed. | Required |
| case_id | The ID of the case in Kibana. Locate it with the “kibana-cases-find” command. | Required |
| version_id | The version ID of the case. Find it with the “kibana-cases-find” command. This ID changes after each case update. | Required |
Context Output
There is no context output for this command.
kibana-user-spaces-find
Gets the list of user spaces in Kibana.
Base Command
kibana-user-spaces-find
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.UserSpaces.description | unknown | The default user space description. |
| Kibana.UserSpaces.disabledFeatures | unknown | The list of disabled Kibana features. |
kibana-case-comments-find
Finds comments for an input case ID.
Base Command
kibana-case-comments-find
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | The case ID to find comments for. Locate it with the “kibana-cases-find” command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.CaseComments.version | unknown | The version number of the case comment in Kibana. |
| Kibana.CaseComments.id | unknown | The ID number of the case comment in Kibana. |
kibana-case-delete
Deletes a case in Kibana based on case ID.
Base Command
kibana-case-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | The case ID to delete. Locate it with the “kibana-cases-find” command. | Required |
Context Output
There is no context output for this command.
kibana-rule-delete
Deletes a rule in Kibana based on the input rule ID.
Base Command
kibana-rule-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_id | The rule ID to delete. Find it with the “kibana-rule-details-search” command. | Required |
Context Output
There is no context output for this command.
kibana-rule-details-search
Retrieves details about a detection rule in Kibana based on the input KQL filter.
Base Command
kibana-rule-details-search
Input
| Argument Name | Description | Required |
|---|---|---|
| kql_query | The KQL filter to search rules with. For example: “alert.attributes.name: Smith”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.RuleDetails.enabled | unknown | Whether the rule is enabled in Kibana. |
| Kibana.RuleDetails.name | unknown | The name of the rule in Kibana. |
| Kibana.RuleDetails.id | unknown | The ID of the rule in Kibana. |
kibana-case-comment-add
Adds a comment to a case in Kibana. The case ID and owner can be obtained from the “kibana-cases-find” command.
Base Command
kibana-case-comment-add
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | The case ID to add the comment to. Locate it with the “kibana-cases-find” command. | Required |
| case_owner | The owner of the case, as listed in the “kibana-cases-find” command output. Possible values are: cases, observability, securitySolution. | Required |
| comment | The comment to add to the case in Kibana. | Required |
Context Output
There is no context output for this command.
kibana-user-list-get
Searches for the list of users in Kibana and returns the users’ UIDs.
Base Command
kibana-user-list-get
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.UserList.username | unknown | The username of the user in Kibana. |
| Kibana.UserList.roles | unknown | The associated roles of the user in Kibana. |
kibana-alert-assign
Assigns an alert in Kibana to a user via user ID input.
Base Command
kibana-alert-assign
Input
| Argument Name | Description | Required |
|---|---|---|
| user_id | The UID of the user to be assigned. Locate it with the “kibana-user-list-get” command. | Required |
| alert_id | The alert ID to assign the user to. Find it with the “kibana-detection-alerts-list” command. | Required |
Context Output
There is no context output for this command.
kibana-detection-alerts-list
Searches for detection alerts in Kibana.
Base Command
kibana-detection-alerts-list
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_status | The status of the detection alert to search for. Possible values are: open, closed. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.DetectionAlerts.bhe.windows.security_id | unknown | The username associated with the detection alert. |
| Kibana.DetectionAlerts.kibana.alert.original_data_stream.dataset | unknown | The dataset associated with the detection alert. |
| Kibana.DetectionAlerts.message | unknown | The raw log message of the detection alert. |
| Kibana.DetectionAlerts.kibana.alert.uuid | unknown | The ID of the detection alert. |
| Kibana.DetectionAlerts.kibana.alert.rule.name | unknown | The rule name associated with the detection alert. |
kibana-alert-note-add
Adds a note to an alert in Kibana.
Base Command
kibana-alert-note-add
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The alert ID to update the note on. Find it with the “kibana-detection-alerts-list” command. | Required |
| note | The note text to add to the alert. | Required |
Context Output
There is no context output for this command.
kibana-alerting-health-get
Retrieves the health status of the Kibana alerting framework.
Base Command
kibana-alerting-health-get
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.AlertingFrameworkHealth.alerting_framework_health.decryption_health.status | unknown | Whether Kibana can successfully decrypt encrypted alert data. |
| Kibana.AlertingFrameworkHealth.alerting_framework_health.execution_health.status | unknown | Whether rules are running on time or failing. |
| Kibana.AlertingFrameworkHealth.alerting_framework_health.read_health.status | unknown | Whether rule configurations can be successfully retrieved from internal Kibana indices. |
kibana-alert-rule-disable
Disables a detection alerting rule. Clears associated alerts from the active alerts page.
Base Command
kibana-alert-rule-disable
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_id | The rule ID to disable. Find it with the “kibana-rule-details-search” command. | Required |
Context Output
There is no context output for this command.
kibana-alert-rule-enable
Enables a rule used for detection alerting.
Base Command
kibana-alert-rule-enable
Input
| Argument Name | Description | Required |
|---|---|---|
| rule_id | The rule ID to enable. Find it with the “kibana-rule-details-search” command. | Required |
Context Output
There is no context output for this command.
kibana-exception-lists-get
Retrieves a list of all exception list containers.
Base Command
kibana-exception-lists-get
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.ExceptionLists.name | unknown | The name of the exception list. |
| Kibana.ExceptionLists.list_id | unknown | The list ID of the exception list. |
| Kibana.ExceptionLists.description | unknown | The description of the exception list. |
kibana-value-list-create
Creates a value list in Kibana.
Base Command
kibana-value-list-create
Input
| Argument Name | Description | Required |
|---|---|---|
| description | The description of the value list. | Required |
| name | The name of the value list. | Required |
| data_type | The Elasticsearch data type the list container holds. Possible values are: keyword, ip, ip_range, text. | Required |
| list_id | The identifier of the value list. | Required |
Context Output
There is no context output for this command.
kibana-value-lists-get
Finds all value lists in the Kibana Detection Rules menu.
Base Command
kibana-value-lists-get
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.ValueLists.name | unknown | The name of the value list. |
| Kibana.ValueLists.id | unknown | The ID of the value list. |
| Kibana.ValueLists.description | unknown | The description of the value list. |
kibana-value-list-items-import
Imports value list items from a TXT or CSV file.
Base Command
kibana-value-list-items-import
Input
| Argument Name | Description | Required |
|---|---|---|
| list_id | The value list ID to import values to. Find it with the “kibana-value-lists-get” command. | Required |
| file_content | The IOC file entries to import to Kibana in Python string format. | Required |
Context Output
There is no context output for this command.
kibana-value-list-item-create
Creates a value list item and associates it with the specified value list.
Base Command
kibana-value-list-item-create
Input
| Argument Name | Description | Required |
|---|---|---|
| list_id | The value list ID to update. Find it with the “kibana-value-lists-get” command. | Required |
| new_value_list_item | The item to add to the specified value list. | Required |
Context Output
There is no context output for this command.
kibana-value-list-items-get
Displays entries in an input value list.
Base Command
kibana-value-list-items-get
Input
| Argument Name | Description | Required |
|---|---|---|
| list_id | The value list ID to retrieve values for. Find it with the “kibana-value-lists-get” command. | Required |
| result_size | The size of results to return. Default is 100. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.ValueListItems.value | unknown | The value of the value list item. |
| Kibana.ValueListItems.id | unknown | The ID of the value list item. |
| Kibana.ValueListItems.list_id | unknown | The list ID of the value list. |
kibana-value-list-item-delete
Deletes a value list item, given the item ID and list ID as input.
Base Command
kibana-value-list-item-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| item_id | The value list entry ID to delete. Find it with the “kibana-value-list-items-get” command. | Required |
| list_id | The value list ID to delete the value from. Find it with the “kibana-value-lists-get” command. | Required |
Context Output
There is no context output for this command.
kibana-value-list-delete
Deletes a value list given the list ID as input.
Base Command
kibana-value-list-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| list_id | The value list ID to delete. Find it with the “kibana-value-lists-get” command. | Required |
Context Output
There is no context output for this command.
kibana-status-get
Checks the Kibana operational status.
Base Command
kibana-status-get
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.OperationalStatus.core.elasticsearch.level | unknown | The connection health between Kibana and Elasticsearch. |
| Kibana.OperationalStatus.overall.level | unknown | The aggregated health status of the Kibana instance. |
| Kibana.OperationalStatus.core.savedObjects.level | unknown | The health status of the Saved Objects repository. |
kibana-task-manager-health-get
Retrieves the health status of the Kibana task manager.
Base Command
kibana-task-manager-health-get
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.TaskManagerHealth.capacity_estimation.status | unknown | The ability to handle scheduled tasks in Kibana. |
| Kibana.TaskManagerHealth.configuration.status | unknown | The configuration status of the Kibana task manager. |
| Kibana.TaskManagerHealth.runtime.status | unknown | The performance, drift, and load of Kibana task execution. |
| Kibana.TaskManagerHealth.workload.status | unknown | The status of tasks running, to identify potential overload. |
kibana-upgrade-readiness-status-get
Checks the status of the cluster.
Base Command
kibana-upgrade-readiness-status-get
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.UpgradeReadinessStatus.details | unknown | The details for what is needed prior to Kibana upgrades. |
| Kibana.UpgradeReadinessStatus.readyForUpgrade | unknown | Whether Kibana is ready for upgrade. |
kibana-case-comment-delete
Deletes a case comment.
Base Command
kibana-case-comment-delete
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | The case ID to delete the comment on. Retrieve case IDs with the “kibana-cases-find” command. | Required |
| comment_id | The identifier for the comment. Find comment IDs with the “kibana-case-comments-find” command. | Required |
Context Output
There is no context output for this command.
kibana-case-file-add
Attaches a file to a case.
Base Command
kibana-case-file-add
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | The case ID to attach the file to. Locate it with the “kibana-cases-find” command. | Required |
| file_id | The file entry ID from Cortex XSOAR context data to add to the case. | Required |
Context Output
There is no context output for this command.
kibana-user-by-email-get
Searches for a single user’s UID in Kibana by email address filter.
Base Command
kibana-user-by-email-get
Input
| Argument Name | Description | Required |
|---|---|---|
| email_wildcard | The full or partial email address to search for the user with (for example, william.smith@*). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.UserData.profile_uid | unknown | The user ID for tracking user activity and checking privileges. |
| Kibana.UserData.roles | unknown | The roles tied to the user account. |
kibana-case-information-get
Retrieves information for a specific case in Kibana.
Base Command
kibana-case-information-get
Input
| Argument Name | Description | Required |
|---|---|---|
| case_id | The case ID to retrieve information for. View available case IDs with the “kibana-cases-find” command. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Kibana.CaseInfo.status | unknown | Whether the case is open, in-progress, or closed. |
| Kibana.CaseInfo.owner | unknown | The application that created the case. |
| Kibana.CaseInfo.version | unknown | The version of the case being updated. |
| Kibana.CaseInfo.id | unknown | The unique identifier for a case. |
Configuration parameters
url— Server URL (required)elastic_port— Elastic API Portkibana_port— Kibana API Portauth_type— Authorization typeapi_key_auth_credentials— API key IDcredentials— Usernameinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsclient_type— Client typetimeout— Request timeout (in seconds).
Commands (32)
-
kibana-alert-assignAssigns an alert in Kibana to a user via user ID input.
-
kibana-alert-note-addAdds a note to an alert in Kibana.
-
kibana-alert-rule-disableDisables a detection alerting rule. Clears associated alerts from the active alerts page.
-
kibana-alert-rule-enableEnables a rule used for detection alerting.
-
kibana-alert-status-updateUpdates the status of an input alert.
-
kibana-alerting-health-getRetrieves the health status of the Kibana alerting framework.
-
kibana-case-alerts-findReturns information on the alerts of the input case in Kibana.
-
kibana-case-comment-addAdds a comment to a case in Kibana. The case ID and owner can be obtained from the "kibana-cases-find" command.
-
kibana-case-comment-deleteDeletes a case comment.
-
kibana-case-comments-findFinds comments for an input case ID.
-
kibana-case-deleteDeletes a case in Kibana based on case ID.
-
kibana-case-file-addAttaches a file to a case.
-
kibana-case-information-getRetrieves information for a specific case in Kibana.
-
kibana-case-status-updateUpdates the status of an input case.
-
kibana-cases-findLists cases in Kibana.
-
kibana-detection-alerts-listSearches for detection alerts in Kibana.
-
kibana-exception-lists-getRetrieves a list of all exception list containers.
-
kibana-rule-deleteDeletes a rule in Kibana based on the input rule ID.
-
kibana-rule-details-searchRetrieves details about a detection rule in Kibana based on the input KQL filter.
-
kibana-status-getChecks the Kibana operational status.
-
kibana-task-manager-health-getRetrieves the health status of the Kibana task manager.
-
kibana-upgrade-readiness-status-getChecks the status of the cluster.
-
kibana-user-by-email-getSearches for a single user's UID in Kibana by email address filter.
-
kibana-user-list-getSearches for the list of users in Kibana and returns the users' UIDs.
-
kibana-user-spaces-findGets the list of user spaces in Kibana.
-
kibana-value-list-createCreates a value list in Kibana.
-
kibana-value-list-deleteDeletes a value list given the list ID as input.
-
kibana-value-list-item-createCreates a value list item and associates it with the specified value list.
-
kibana-value-list-item-deleteDeletes a value list item, given the item ID and list ID as input.
-
kibana-value-list-items-getDisplays entries in an input value list.
-
kibana-value-list-items-importImports value list items from a TXT or CSV file.
-
kibana-value-lists-getFinds all value lists in the Kibana Detection Rules menu.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 from CommonServerUserPython import * """IMPORTS""" import requests import json from datetime import datetime, timezone UTC = timezone.utc # noqa: UP017 """Parameters""" PARAMS = demisto.params() AUTH_TYPE = PARAMS.get("auth_type", "Basic auth") USERNAME: str = PARAMS.get("credentials", {}).get("identifier") PASSWORD: str = PARAMS.get("credentials", {}).get("password") AUTH = (USERNAME, PASSWORD) API_KEY_ID: str = PARAMS.get("api_key_auth_credentials", {}).get("identifier") API_KEY_SECRET: str = PARAMS.get("api_key_auth_credentials", {}).get("password") API_KEY = None PROXY = PARAMS.get("proxy") ELASTIC_SERVER = PARAMS.get("url", "").rstrip("/") + ":" + (PARAMS.get("elastic_port") or "9200") KIBANA_SERVER = PARAMS.get("url", "").rstrip("/") + ":" + (PARAMS.get("kibana_port") or "443") INSECURE = not PARAMS.get("insecure", False) TIMEOUT = int(PARAMS.get("timeout") or 60) # .ymla values BASIC_AUTH = "Basic auth" BEARER_AUTH = "Bearer auth" API_KEY_AUTH = "API key auth" API_KEY_PREFIX = "_api_key_id:" # Using API key auth by username and password fields for backward compatibility. if AUTH_TYPE == BASIC_AUTH: if USERNAME and USERNAME.startswith(API_KEY_PREFIX): AUTH_TYPE = API_KEY_AUTH API_KEY_ID = USERNAME[len(API_KEY_PREFIX) :] API_KEY = (API_KEY_ID, PASSWORD) elif AUTH_TYPE == API_KEY_AUTH: API_KEY = (API_KEY_ID, API_KEY_SECRET) ELASTICSEARCH_V8 = "Elasticsearch_v8" ELASTICSEARCH_V9 = "Elasticsearch_v9" OPEN_SEARCH = "OpenSearch" ELASTIC_SEARCH_CLIENT = PARAMS.get("client_type") or "Elasticsearch" if ELASTIC_SEARCH_CLIENT == OPEN_SEARCH: from opensearchpy import RequestsHttpConnection from opensearchpy import OpenSearch as Elasticsearch elif ELASTIC_SEARCH_CLIENT in [ELASTICSEARCH_V8, ELASTICSEARCH_V9]: from elastic_transport import RequestsHttpNode from elasticsearch import Elasticsearch # type: ignore[assignment] else: # Elasticsearch (<= v7) from elasticsearch7 import Elasticsearch, RequestsHttpConnection # type: ignore[assignment,misc] def get_api_key_header_val(api_key): """ Check the type of the passed api_key and return the correct header value for the `API Key authentication <https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html>` :arg api_key, either a tuple or a base64 encoded string """ if isinstance(api_key, tuple | list): s = f"{api_key[0]}:{api_key[1]}".encode() return "ApiKey " + base64.b64encode(s).decode("utf-8") return "ApiKey " + api_key def is_access_token_expired(expires_in: str) -> bool: """Check if access token is expired. Args: expires_in: ISO format datetime string representing when the token expires (UTC) Returns: bool: True if token is expired or will expire within 1 minute, False otherwise """ try: # Parse the expires_in string to a UTC datetime object expiration_time = datetime.strptime(expires_in, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC) # Subtract 1 min to refresh slightly early and avoid expiration issues. current_time_with_buffer = datetime.now(UTC) + timedelta(minutes=1) is_not_expired = expiration_time > current_time_with_buffer if is_not_expired: demisto.debug( f"is_access_token_expired - using existing Access token from integration context (expires in {expires_in})." ) return False else: demisto.debug("is_access_token_expired - Access token expired.") return True except (ValueError, TypeError) as e: demisto.debug(f"is_access_token_expired - Error parsing expiration time: {e}. Treating as expired.") return True def get_elastic_token(proxies): """ Authenticates and retrieves an OAuth 2.0 access token from Elasticsearch. Returns an access token either by refreshing an existing token or performing a new token request. 1. Check if existing access token is valid (with 1min buffer). 2. If not, try to use refresh token if it exists and is valid. 3. If not, perform a full password grant authentication for receiving initial access token. """ try: url = urljoin(ELASTIC_SERVER, "_security/oauth2/token") headers = {"Content-Type": "application/json"} integration_context = get_integration_context() access_token = integration_context.get("access_token", "") access_token_expires_in = integration_context.get("access_token_expires_in", "") refresh_token = integration_context.get("refresh_token", "") refresh_token_expires_in = integration_context.get("refresh_token_expires_in", "") # 1. Check if token exists and if it is still valid if access_token and not is_access_token_expired(access_token_expires_in): demisto.debug("get_elastic_token - Using existing access token from integration context.") return access_token if not USERNAME or not PASSWORD: demisto.debug("get_elastic_token - username or password fields are missing.") raise DemistoException("username or password fields are missing.") # 2. Token exists but expired, and refresh token is valid if refresh_token and not is_access_token_expired(refresh_token_expires_in): demisto.debug( "get_elastic_token - Access token expired, but Refresh token valid. Attempting to get token using refresh token" ) payload = {"grant_type": "refresh_token", "refresh_token": refresh_token} response = requests.post( url, headers=headers, json=payload, verify=INSECURE, auth=(USERNAME, PASSWORD), proxies=proxies ) if response.status_code == 200: now = datetime.now(UTC) token_data = response.json() access_token_expires_in = (now + timedelta(seconds=token_data.get("expires_in"))).strftime("%Y-%m-%dT%H:%M:%SZ") refresh_token_expires_in = (now + timedelta(hours=24)).strftime( "%Y-%m-%dT%H:%M:%SZ" ) # refresh token has a lifetime of 24 hours integration_context.update( { "access_token": token_data.get("access_token"), "refresh_token": token_data.get("refresh_token"), "access_token_expires_in": access_token_expires_in, "refresh_token_expires_in": refresh_token_expires_in, } ) set_integration_context(integration_context) demisto.debug( "get_elastic_token - Access token received successfully by refresh token and set to integration context." ) return integration_context["access_token"] # If refresh fails, clear the refresh token to force generating of new token demisto.debug("get_elastic_token - refresh fails, a new token will be generated via password grant.") integration_context.update({"refresh_token": None, "refresh_token_expires_in": None}) set_integration_context(integration_context) # Generate a new access vi password grant demisto.debug("get_elastic_token - Attempting to get token using grant_type:password") payload = {"grant_type": "password", "username": USERNAME, "password": PASSWORD} response = requests.post(url, headers=headers, auth=(USERNAME, PASSWORD), json=payload, verify=INSECURE, proxies=proxies) if response.status_code == 200: now = datetime.now(UTC) token_data = response.json() access_token_expires_in = (now + timedelta(seconds=token_data.get("expires_in"))).strftime("%Y-%m-%dT%H:%M:%SZ") refresh_token_expires_in = (now + timedelta(hours=24)).strftime( "%Y-%m-%dT%H:%M:%SZ" ) # refresh token has a lifetime of 24 hours integration_context.update( { "access_token": token_data.get("access_token"), "refresh_token": token_data.get("refresh_token"), "access_token_expires_in": access_token_expires_in, "refresh_token_expires_in": refresh_token_expires_in, } ) set_integration_context(integration_context) demisto.debug( "get_elastic_token - Access token received successfully via password grant and set to integration context." ) return integration_context["access_token"] demisto.debug(f"Failed to authenticate: {response.status_code}\n{response.text}") try: reason = json.loads(response.text).get("error", {}).get("reason") except Exception: reason = response.reason or response.text raise DemistoException(f"{response.status_code}, {reason}") except Exception as e: demisto.debug(f"get_elastic_token error: \n{str(e)}") raise DemistoException(f"{str(e)}") def elasticsearch_builder(proxies): """Builds an Elasticsearch obj with the necessary credentials, proxy settings and secure connection.""" connection_args: Dict[str, Union[bool, int, str, list, tuple[str, str], RequestsHttpConnection]] = { "hosts": [ELASTIC_SERVER], "verify_certs": INSECURE, "timeout": TIMEOUT, } if ELASTIC_SEARCH_CLIENT not in [ELASTICSEARCH_V9, ELASTICSEARCH_V8]: # Adding the proxy related parameters to the Elasticsearch client v7 and below or OpenSearch (BC) connection_args["connection_class"] = RequestsHttpConnection # type: ignore[assignment] connection_args["proxies"] = proxies else: # Adding the proxy related parameter to the Elasticsearch client v8 # Reference- https://github.com/elastic/elastic-transport-python/issues/53#issuecomment-1447903214 class CustomHttpNode(RequestsHttpNode): # pylint: disable=E0601 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.session.proxies = proxies connection_args["node_class"] = CustomHttpNode # type: ignore[assignment] if AUTH_TYPE == API_KEY_AUTH and API_KEY: connection_args["api_key"] = API_KEY elif AUTH_TYPE == BASIC_AUTH and USERNAME and PASSWORD: if ELASTIC_SEARCH_CLIENT in [ELASTICSEARCH_V9, ELASTICSEARCH_V8]: connection_args["basic_auth"] = (USERNAME, PASSWORD) else: # Elasticsearch version v7 and below or OpenSearch (BC) connection_args["http_auth"] = (USERNAME, PASSWORD) elif AUTH_TYPE == BEARER_AUTH: connection_args["bearer_auth"] = get_elastic_token(proxies) es = Elasticsearch(**connection_args) # type: ignore[arg-type] # Ensuring api_key will be set correctly in case the authentication type is API key auth. # this should be passed as api_key via Elasticsearch init, but this code ensures it'll be set correctly # In some versions of the ES library, the transport object does not have a get_session func if AUTH_TYPE == API_KEY_AUTH and hasattr(es, "transport") and hasattr(es.transport, "get_connection"): es.transport.get_connection().session.headers["authorization"] = get_api_key_header_val( # type: ignore[attr-defined] API_KEY ) return es def http_request( method, url_suffix, headers, auth=AUTH, params=None, data=None, files=None, safe=False, parse_json=True, proxies=None ): """ A wrapper for requests lib to send our requests and handle requests and responses better. :type method: ``str`` :param method: HTTP method for the request. :type url_suffix: ``str`` :param url_suffix: The suffix of the URL (endpoint) :type params: ``dict`` :param params: The URL params to be passed. :type data: ``dict`` :param data: The body data of the request. :type headers: ``dict`` :param headers: Request headers :type safe: ``bool`` :param safe: If set to true will return None in case of error :return: Returns the http request response json :rtype: ``dict`` or ``str`` """ url = KIBANA_SERVER + url_suffix try: res = requests.request( method, url, auth=auth, verify=INSECURE, params=params, json=data, files=files, headers=headers, proxies=proxies, ) except requests.exceptions.RequestException as e: demisto.debug(str(e)) return_error("Error in connection to the server. Please make sure you entered the URL correctly.") # Handle error responses gracefully if res.status_code not in {200, 201}: if safe: return None elif res.status_code == 401: reason = "Unauthorized. Please check your API token" elif res.status_code == 204: return None else: try: reason = res.json() except ValueError: reason = res.reason return_error(f"Error in API call status code: {res.status_code}, reason: {reason}") if parse_json: return res.json() return res.content def test_func(proxies): """ Tests API connectivity to the Elasticsearch server. Tests the existence of all necessary fields for fetch. Due to load considerations, the test module doesn't check the validity of the fetch-incident - to test that the fetch works as excepted the user should run the es-integration-health-check command. """ success, message = test_connectivity_auth(proxies) if not success: return message return "ok" def test_connectivity_auth(proxies) -> tuple[bool, str]: """ Test connectivity and authentication with Elasticsearch server Args: proxies (dict): Dictionary of proxy settings Returns: tuple[bool, str]: (success status, message) """ demisto.debug("test_connectivity_auth started") headers = {"Content-Type": "application/json"} res = None try: if AUTH_TYPE == BASIC_AUTH: demisto.debug("test_connectivity_auth - Basic auth setting authorization header and sending request") res = requests.get(ELASTIC_SERVER, auth=(USERNAME, PASSWORD), verify=INSECURE, headers=headers, proxies=proxies) elif AUTH_TYPE == API_KEY_AUTH: demisto.debug("test_connectivity_auth - API key auth setting authorization header and sending request") headers["authorization"] = get_api_key_header_val(API_KEY) res = requests.get(ELASTIC_SERVER, verify=INSECURE, headers=headers, proxies=proxies) elif AUTH_TYPE == BEARER_AUTH: demisto.debug("test_connectivity_auth - Bearer auth setting authorization header and sending request") headers["Authorization"] = f"Bearer {get_elastic_token(proxies)}" res = requests.get(ELASTIC_SERVER, verify=INSECURE, headers=headers, proxies=proxies) if res is not None: if res.status_code >= 400: demisto.debug(f"test_connectivity_auth - Failed to connect.\n{res.status_code=}, {res.text=}") return False, f"Failed to connect.\nStatus:{res.status_code}, {res.reason}" elif res.status_code == 200: demisto.debug("test_connectivity_auth - Connectivity test successful") verify_es_server_version(res.json()) return True, "Connectivity test successful" return False, "No response received from server" except Exception as e: demisto.debug(f"test_connectivity_auth - Failed to connect.\nError message: {e}") return False, f"Failed to connect.\n{e}" def verify_es_server_version(res): """ Gets the requests.get raw response, extracts the elasticsearch server version, and verifies that the client type parameter is configured accordingly. Raises exceptions for server version miss configuration issues. Args: res(dict): requests.models.Response object including information regarding the elasticsearch server. """ es_server_version = res.get("version", {}).get("number", "") demisto.debug(f"Elasticsearch server version is: {es_server_version}") if es_server_version: major_version = es_server_version.split(".")[0] if major_version: if int(major_version) >= 8 and ELASTIC_SEARCH_CLIENT not in [ELASTICSEARCH_V9, ELASTICSEARCH_V8, OPEN_SEARCH]: raise ValueError( f"Configuration Error: Your Elasticsearch server is version {es_server_version}. " f"Please ensure that the client type is set to {ELASTICSEARCH_V9}, {ELASTICSEARCH_V8} or {OPEN_SEARCH}. " f"For more information please see the integration documentation." ) elif int(major_version) <= 7 and ELASTIC_SEARCH_CLIENT not in [OPEN_SEARCH, "Elasticsearch"]: raise ValueError( f"Configuration Error: Your Elasticsearch server is version {es_server_version}. " f"Please ensure that the client type is set to Elasticsearch or {OPEN_SEARCH}. " f"For more information please see the integration documentation." ) def kibana_find_cases(args, proxies): """ Returns information on the cases in Kibana. API reference: https://www.elastic.co/docs/api/doc/kibana/operation/operation-findcasesdefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } status = args.get("status") severity = args.get("severity") from_time = arg_to_datetime(args.get("from_time")) from_time_str = from_time.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if from_time else None query_params = {"status": status, "severity": severity, "from": from_time_str} response = http_request(method="GET", url_suffix="/api/cases/_find", params=query_params, headers=headers, proxies=proxies) json_data = response["cases"] # output results to markdown table md = tableToMarkdown("Kibana Cases", json_data, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Cases", outputs=json_data, raw_response=response) return result def kibana_find_alerts_for_case(args, proxies): """ Returns information on the alerts of a case in Kibana. API reference: https://www.elastic.co/docs/api/doc/kibana/operation/operation-getcasealertsdefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") response = http_request(method="GET", url_suffix=f"/api/cases/{case_id}/alerts", headers=headers, proxies=proxies) # output results to markdown table md = tableToMarkdown("Kibana Alerts For Case", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Alerts.For.Case", outputs=response) return result def kibana_update_alert_status(args, proxies): """ Update status of input alert in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-setalertsstatus """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } alert_id = args.get("alert_id") status = args.get("status") data = { "status": status, "signal_ids": [ alert_id, ], } http_request(method="POST", url_suffix="/api/detection_engine/signals/status", data=data, headers=headers, proxies=proxies) return CommandResults(readable_output=f"Updated alert ID {alert_id} to status of {status}") def kibana_update_case_status(args, proxies): """ Update status of input case in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-updatecasedefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") status = args.get("status") version = args.get("version_id") data = {"cases": [{"id": case_id, "status": status, "version": version}]} response = http_request(method="PATCH", url_suffix="/api/cases", data=data, headers=headers, proxies=proxies) # output results to markdown table md = tableToMarkdown("Kibana Updated Case Status", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Updated.Case.Status", outputs=response) return result def kibana_find_user_spaces(args, proxies): """ Get list of user spaces in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-spaces-space """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } response = http_request(method="GET", url_suffix="/api/spaces/space", headers=headers, proxies=proxies) # output results to markdown table md = tableToMarkdown("Kibana User Spaces", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.User.Spaces", outputs=response) return result def kibana_find_case_comments(args, proxies): """ Get list of comments for a case in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-findcasecommentsdefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") response = http_request(method="GET", url_suffix=f"/api/cases/{case_id}/comments/_find", headers=headers, proxies=proxies) response = response["comments"] # output results to markdown table md = tableToMarkdown("Kibana Case Comments", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Case.Comments", outputs=response) return result def kibana_delete_case(args, proxies): """ Delete case in Kibana based on input case ID. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-deletecasedefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") params = {"ids": json.dumps([case_id])} http_request(method="DELETE", url_suffix="/api/cases", params=params, headers=headers, proxies=proxies) return CommandResults(readable_output=f"Successfully deleted case with ID of {case_id}") def kibana_delete_rule(args, proxies): """ Delete rule in Kibana based on input rule ID. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-delete-alerting-rule-id """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } rule_id = args.get("rule_id") http_request(method="DELETE", url_suffix=f"/api/alerting/rule/{rule_id}", headers=headers, proxies=proxies) return CommandResults(readable_output=f"Successfully deleted rule with ID of {rule_id}") def kibana_search_rule_details(args, proxies): """ Retrieve details about detection rule in Kibana based on input KQL filter. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-alerting-rules-find """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } kql_query = args.get("kql_query") params = {"filter": kql_query} response = http_request(method="GET", url_suffix="/api/alerting/rules/_find", params=params, headers=headers, proxies=proxies) response = response["data"] # output results to markdown table md = tableToMarkdown("Kibana Rule Details", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Rule.Details", outputs=response) return result def kibana_add_case_comment(args, proxies): """ Add comment to case in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-addcasecommentdefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") case_owner = args.get("case_owner") comment = args.get("comment") json_data = { "type": "user", "owner": case_owner, "comment": comment, } response = http_request( method="POST", url_suffix=f"/api/cases/{case_id}/comments", data=json_data, headers=headers, proxies=proxies ) updated_at = response["updated_at"] return CommandResults(readable_output=f"Case comment updated at {updated_at}") def kibana_get_user_list(args, proxies): """ Search for a list of all users and UIDs in Kibana. Reference - https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-query-user """ es = elasticsearch_builder(proxies) try: all_users = es.security.query_user(with_profile_uid=True, size=100) # The v8/v9 client wraps the response in an ObjectApiResponse exposing `.body`, # while the v7 and OpenSearch clients return a plain dict. all_users = getattr(all_users, "body", all_users)["users"] # output results to markdown table md = tableToMarkdown("Kibana User List", all_users, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.User.List", outputs=all_users) return result except Exception as e: # pragma: no cover raise DemistoException(f"Error querying all users: {e}") def kibana_assign_alert_user(args, proxies): """ Assign user to input alert in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-setalertassignees """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } alert_id = args.get("alert_id") user_id = args.get("user_id") json_data = { "ids": [ alert_id, ], "assignees": { "add": [ user_id, ], "remove": [], }, } http_request( method="POST", url_suffix="/api/detection_engine/signals/assignees", data=json_data, headers=headers, proxies=proxies ) return CommandResults(readable_output=f"Assigned user ID {user_id} to alert {alert_id}") def kibana_list_detection_alerts(args, proxies): """ List detection alerts in Kibana matching a status filter. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-searchalerts """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } alert_status = args.get("alert_status") json_data = { "query": { "bool": { "filter": [ { "bool": { "must": [], "filter": [ { "match_phrase": { "kibana.alert.workflow_status": alert_status, }, }, ], }, }, ], }, }, "runtime_mappings": {}, } response = http_request( method="POST", url_suffix="/api/detection_engine/signals/search", data=json_data, headers=headers, proxies=proxies ) result_json = response["hits"] result_list = result_json.get("hits") result_list_final = [] # append each _source dict in list of dicts to a final results list for item in result_list: result_list_final.append(item.get("_source")) # output results to markdown table md = tableToMarkdown("Kibana Detection Alerts", result_list_final, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Detection.Alerts", outputs=result_list_final) return result def kibana_add_alert_note(args, proxies): """ Add note to detection alerts in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-persistnoteroute """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } event_id = args.get("alert_id") note = args.get("note") json_data = { "note": { "eventId": event_id, "note": note, "timelineId": "", }, } http_request(method="PATCH", url_suffix="/api/note", data=json_data, headers=headers, proxies=proxies) return CommandResults(readable_output=f"Added note {note} to alert {event_id}") def kibana_get_alerting_health(args, proxies): """ Get alerting framework health in Kibana. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-getalertinghealth """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } response = http_request(method="GET", url_suffix="/api/alerting/_health", headers=headers, proxies=proxies) # output results to markdown table md = tableToMarkdown("Alerting Framework Health", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Alerting.Framework.Health", outputs=response) return result def kibana_disable_alert_rule(args, proxies): """ Used to disable a rule used for detection alerting. Clears all associated alerts from active alerts page. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-alerting-rule-id-disable """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } rule_id = args.get("rule_id") json_data = { "untrack": True, } http_request( method="POST", url_suffix=f"/api/alerting/rule/{rule_id}/_disable", data=json_data, headers=headers, proxies=proxies ) return CommandResults(readable_output=f"Successfully disabled rule with ID of {rule_id}") def kibana_enable_alert_rule(args, proxies): """ Used to enable a rule used for detection alerting. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-post-alerting-rule-id-enable """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } rule_id = args.get("rule_id") http_request(method="POST", url_suffix=f"/api/alerting/rule/{rule_id}/_enable", headers=headers, proxies=proxies) return CommandResults(readable_output=f"Successfully enabled rule with ID of {rule_id}") def kibana_get_exception_lists(args, proxies): """ Used to get a list of all exception list containers. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-findexceptionlists """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } response = http_request(method="GET", url_suffix="/api/exception_lists/_find", headers=headers, proxies=proxies) response = response["data"] # output results to markdown table md = tableToMarkdown("Kibana Exception Lists", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Exception.Lists", outputs=response) return result def kibana_create_value_list(args, proxies): """ Used to create a value list in Kibana Detection Rules menu. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-createlist """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } description = args.get("description") list_id = args.get("list_id") name = args.get("name") data_type = args.get("data_type") json_data = { "id": list_id, "name": name, "type": data_type, "description": description, } http_request(method="POST", url_suffix="/api/lists", data=json_data, headers=headers, proxies=proxies) return CommandResults(readable_output=f"Successfully created value list with name of {name}") def kibana_get_value_lists(args, proxies): """ Used to find all value lists in Kibana Detection Rules menu. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-findlists """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } response = http_request(method="GET", url_suffix="/api/lists/_find", headers=headers, proxies=proxies) result_json = response["data"] # output results to markdown table md = tableToMarkdown("Alerting Value Lists", result_json, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Alerting.Value.Lists", outputs=result_json) return result def kibana_import_value_list_items(args, proxies): """ Used to import value list items from a TXT file. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-importlistitems """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } list_id = args.get("list_id") file_content = args.get("file_content") json_data = { "list_id": list_id, } files = {"file": ("value_list.txt", file_content, "text/plain")} http_request( method="POST", url_suffix="/api/lists/items/_import", params=json_data, files=files, headers=headers, proxies=proxies ) return CommandResults(readable_output=f"Successfully imported {file_content} to value list with ID of {list_id}") def kibana_create_value_list_item(args, proxies): """ Used to create a value list item and associate it with the specified value list. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-createlistitem """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } list_id = args.get("list_id") new_item = args.get("new_value_list_item") json_data = {"value": new_item, "list_id": list_id} http_request(method="POST", url_suffix="/api/lists/items", data=json_data, headers=headers, proxies=proxies) return CommandResults(readable_output=f"Successfully added {new_item} to value list with ID of {list_id}") def kibana_get_value_list_items(args, proxies): """ Used to display entries in an input value list. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-findlistitems """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } list_id = args.get("list_id") result_size = args.get("result_size") params = {"list_id": list_id, "sort_field": "created_at", "sort_order": "asc", "per_page": result_size} response = http_request(method="GET", url_suffix="/api/lists/items/_find", params=params, headers=headers, proxies=proxies) result_output = response["data"] # output results to markdown table md = tableToMarkdown("Value List Items", result_output, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Value.List.Items", outputs=result_output) return result def kibana_delete_value_list_item(args, proxies): """ Used to delete a value list item given the item ID as input. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-deletelistitem """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests "Content-Type": "application/json", } item_id = args.get("item_id") list_id = args.get("list_id") json_data = {"id": item_id, "list_id": list_id} http_request(method="DELETE", url_suffix="/api/lists/items", params=json_data, headers=headers, proxies=proxies) return CommandResults(readable_output=f"Successfully deleted {item_id} from value list with ID of {list_id}") def kibana_delete_value_list(args, proxies): """ Used to delete a value list given the list ID as input. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-deletelist """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests "Content-Type": "application/json", } list_id = args.get("list_id") params = {"id": list_id} http_request(method="DELETE", url_suffix="/api/lists", params=params, headers=headers, proxies=proxies) return CommandResults(readable_output=f"Successfully deleted value list with ID of {list_id}") def kibana_get_status(args, proxies): """ Used to check Kibana's operational status. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-status """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } response = http_request(method="GET", url_suffix="/api/status", headers=headers, proxies=proxies) response = response["status"] # output results to markdown table md = tableToMarkdown("Kibana Operational Status", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Operational.Status", outputs=response) return result def kibana_get_task_manager_health(args, proxies): """ Get the health status of the Kibana task manager. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-task-manager-health """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } response = http_request(method="GET", url_suffix="/api/task_manager/_health", headers=headers, proxies=proxies) response = response["stats"] # output results to markdown table md = tableToMarkdown("Kibana Task Manager Health", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Task.Manager.Health", outputs=response) return result def kibana_get_upgrade_readiness_status(args, proxies): """ Check the upgrade readiness status of your cluster. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-get-upgrade-status """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } response = http_request(method="GET", url_suffix="/api/upgrade_assistant/status", headers=headers, proxies=proxies) # output results to markdown table md = tableToMarkdown("Kibana Upgrade Readiness Status", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Upgrade.Readiness.Status", outputs=response) return result def kibana_delete_case_comment(args, proxies): """ Delete a case comment. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-deletecasecommentdefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") comment_id = args.get("comment_id") http_request(method="DELETE", url_suffix=f"/api/cases/{case_id}/comments/{comment_id}", headers=headers, proxies=proxies) return CommandResults(readable_output=f"Deleted comment with ID {comment_id} from case {case_id}") def kibana_add_file_to_case(args, proxies): """ Attach a file to a case. Reference - https://www.elastic.co/docs/api/doc/kibana/operation/operation-addcasefiledefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") file_id = args.get("file_id") file_path_dict = demisto.getFilePath(file_id) file_path = file_path_dict["path"] file_name = file_path_dict["name"] with open(file_path, "rb") as f: files = {"file": (file_name, f)} http_request(method="POST", url_suffix=f"/api/cases/{case_id}/files", files=files, headers=headers, proxies=proxies) return f"Successfully added file {file_name} to case {case_id}" def kibana_get_user_by_email(args, proxies): """ Search for a single user's UID in Kibana by email address filter. Reference - https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-query-user """ email_wildcard = args.get("email_wildcard") es = elasticsearch_builder(proxies) query_body = {"query": {"wildcard": {"email": {"value": email_wildcard, "case_insensitive": True}}}} try: user_data = es.security.query_user(with_profile_uid=True, body=query_body) user_data = user_data["users"] # output results to markdown table md = tableToMarkdown("Kibana User Data", user_data, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.User.Data", outputs=user_data) return result except Exception as e: # pragma: no cover raise DemistoException(f"Error querying users by email: {e}") def kibana_get_case_information(args, proxies): """ Retrieve information for a specific case in Kibana. API reference: https://www.elastic.co/docs/api/doc/kibana/operation/operation-getcasedefaultspace """ headers = { "kbn-xsrf": "true", # Required for Kibana API requests } case_id = args.get("case_id") response = http_request(method="GET", url_suffix=f"/api/cases/{case_id}", headers=headers, proxies=proxies) # output results to markdown table md = tableToMarkdown("Kibana Case Info", response, headers=[]) result = CommandResults(readable_output=md, outputs_prefix="Kibana.Case.Info", outputs=response) return result def main(): # pragma: no cover proxies = handle_proxy() proxies = proxies if proxies else None args = demisto.args() try: demisto.debug(f"command is {demisto.command()}") if demisto.command() == "test-module": return_results(test_func(proxies)) elif demisto.command() == "kibana-cases-find": return_results(kibana_find_cases(args, proxies)) elif demisto.command() == "kibana-case-alerts-find": return_results(kibana_find_alerts_for_case(args, proxies)) elif demisto.command() == "kibana-alert-status-update": return_results(kibana_update_alert_status(args, proxies)) elif demisto.command() == "kibana-case-status-update": return_results(kibana_update_case_status(args, proxies)) elif demisto.command() == "kibana-user-spaces-find": return_results(kibana_find_user_spaces(args, proxies)) elif demisto.command() == "kibana-case-comments-find": return_results(kibana_find_case_comments(args, proxies)) elif demisto.command() == "kibana-case-delete": return_results(kibana_delete_case(args, proxies)) elif demisto.command() == "kibana-rule-delete": return_results(kibana_delete_rule(args, proxies)) elif demisto.command() == "kibana-rule-details-search": return_results(kibana_search_rule_details(args, proxies)) elif demisto.command() == "kibana-case-comment-add": return_results(kibana_add_case_comment(args, proxies)) elif demisto.command() == "kibana-user-list-get": return_results(kibana_get_user_list(args, proxies)) elif demisto.command() == "kibana-alert-assign": return_results(kibana_assign_alert_user(args, proxies)) elif demisto.command() == "kibana-detection-alerts-list": return_results(kibana_list_detection_alerts(args, proxies)) elif demisto.command() == "kibana-alert-note-add": return_results(kibana_add_alert_note(args, proxies)) elif demisto.command() == "kibana-alerting-health-get": return_results(kibana_get_alerting_health(args, proxies)) elif demisto.command() == "kibana-alert-rule-disable": return_results(kibana_disable_alert_rule(args, proxies)) elif demisto.command() == "kibana-alert-rule-enable": return_results(kibana_enable_alert_rule(args, proxies)) elif demisto.command() == "kibana-exception-lists-get": return_results(kibana_get_exception_lists(args, proxies)) elif demisto.command() == "kibana-value-list-create": return_results(kibana_create_value_list(args, proxies)) elif demisto.command() == "kibana-value-lists-get": return_results(kibana_get_value_lists(args, proxies)) elif demisto.command() == "kibana-value-list-items-import": return_results(kibana_import_value_list_items(args, proxies)) elif demisto.command() == "kibana-value-list-item-create": return_results(kibana_create_value_list_item(args, proxies)) elif demisto.command() == "kibana-value-list-items-get": return_results(kibana_get_value_list_items(args, proxies)) elif demisto.command() == "kibana-value-list-item-delete": return_results(kibana_delete_value_list_item(args, proxies)) elif demisto.command() == "kibana-value-list-delete": return_results(kibana_delete_value_list(args, proxies)) elif demisto.command() == "kibana-status-get": return_results(kibana_get_status(args, proxies)) elif demisto.command() == "kibana-task-manager-health-get": return_results(kibana_get_task_manager_health(args, proxies)) elif demisto.command() == "kibana-upgrade-readiness-status-get": return_results(kibana_get_upgrade_readiness_status(args, proxies)) elif demisto.command() == "kibana-case-comment-delete": return_results(kibana_delete_case_comment(args, proxies)) elif demisto.command() == "kibana-case-file-add": return_results(kibana_add_file_to_case(args, proxies)) elif demisto.command() == "kibana-user-by-email-get": return_results(kibana_get_user_by_email(args, proxies)) elif demisto.command() == "kibana-case-information-get": return_results(kibana_get_case_information(args, proxies)) else: raise NotImplementedError(f"Command {demisto.command()} is not implemented.") except Exception as e: demisto.debug(f"An unexpected error occurred:\n{traceback.format_exc()}") return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e}") if __name__ in ("__main__", "builtin", "builtins"): main()