Descripción general
En esta guía, se describe la implementación de extremo a extremo de Elastic Stack (Elasticsearch y Kibana) en un clúster compartido aislado de Google Distributed Cloud (GDC) y se muestra cómo integrarlo con un servicio de modelo de lenguaje grande (LLM) para evaluar las funciones potenciadas por IA generativa de Elastic.
La implementación usa el operador oficial de Elastic Cloud on Kubernetes (ECK) disponible en el Marketplace de GDC aislado. El operador se instala en un espacio de nombres de proyecto en un clúster de Kubernetes compartido y automatiza el aprovisionamiento y la administración del ciclo de vida de los recursos de Elasticsearch y Kibana. Una vez implementado, Kibana se conecta a un extremo de inferencia de LLM (como Gemini administrado en GDC o un servicio autoadministrado compatible con OpenAI, como Ollama o vLLM). Esta integración potencia las funciones de IA generativa de Elastic, incluido el agente de IA de Elastic, los agentes personalizados creados con Agent Builder y Attack Discovery.
Esta guía se escribió para Elastic v9.4.0, Elastic Kubernetes Operator v3.4.0 y GDC v1.16.2. Algunas instrucciones pueden variar un poco si usas versiones diferentes.
Arquitectura
La arquitectura consiste en una implementación de Elastic Stack administrada por el operador de ECK dentro de un espacio de nombres de proyecto en un clúster de usuario compartido de GDC. Kibana se comunica a través de la red interna de GDC con un extremo de inferencia de LLM.

Entre los componentes clave de esta arquitectura, se incluyen los siguientes:
- Clúster compartido de GDC: Es un clúster de Kubernetes compartido con alcance de organización que se conecta a varios proyectos y aloja paquetes de software y cargas de trabajo de aplicaciones de GDC Marketplace.
- Proyecto de GDC: Es el espacio de nombres del proyecto en el clúster compartido que aísla el operador de ECK, el clúster de Elasticsearch, la instancia de Kibana y los Secrets y las NetworkPolicies de Kubernetes asociados.
- Operador de Elastic Cloud on Kubernetes (ECK): Se instala desde el
Marketplace de GDC en el espacio de nombres del proyecto. El
operador administra los recursos personalizados
ElasticsearchyKibana, y concilia la licencia de Elastic necesaria para las funciones potenciadas por IA generativa y los conectores de LLM externos. - Elasticsearch: Almacena y, además, indexa datos operativos y de seguridad, y ejecuta consultas de búsqueda y ES|QL generadas por las funciones potenciadas por IA y los agentes de Kibana.
- Kibana: Aloja las funciones potenciadas por IA generativa de Elastic, incluido el agente de IA de Elastic (para la investigación de registros en lenguaje natural y la generación de ES|QL), el compilador de agentes (para crear agentes y herramientas conversacionales personalizados sobre los datos de Elasticsearch) y Attack Discovery (para la correlación de alertas de seguridad y el análisis de la cadena de ataques). Kibana también administra los conectores de acciones de LLM que organizan las llamadas a herramientas de varios pasos entre el usuario, Elasticsearch y el servicio de LLM.
- Servicio de LLM: Es un servicio de LLM administrado (como Gemini en GDC) o un servidor de inferencia autoadministrado (como Ollama o vLLM) que proporciona capacidades de finalización de chat y llamada a herramientas a Kibana a través de la red interna de GDC.
Antes de comenzar
Asegúrate de que tu entorno de GDC cumpla con los siguientes requisitos previos y suposiciones antes de comenzar la implementación:
- Clúster de Kubernetes y proyecto de GDC compartidos: Tienes acceso a un clúster compartido de GDC existente y a un proyecto de GDC conectado a ese clúster. El proyecto funciona como el contenedor de espacio de nombres para todos los recursos de Kubernetes que se implementan en esta guía. Asegúrate de que el clúster tenga suficientes recursos de procesamiento y almacenamiento persistente para alojar las cargas de trabajo del operador de ECK, Elasticsearch y Kibana.
- Servicio de LLM: Tienes acceso de red a un servicio de LLM, ya sea el
servicio administrado de Gemini (si está disponible en tu
entorno de GDC) o un servicio de LLM autoadministrado compatible con OpenAI (por ejemplo,
gemma4:31bentregado a través de Ollama o vLLM en un proyecto de GPU). La implementación de la infraestructura del LLM en sí está fuera del alcance de esta guía, pero los requisitos específicos del modelo y de la API se detallan en Requisitos para el servicio de LLM. Recupera la versión de Elastic que se incluye en tu entorno de GDC:
export ELASTIC_VERSION=$(curl -sk "https://harbor.${ORG}.${ZONE}.${GDCH_DOMAIN}/api/v2.0/projects/library/repositories/elasticsearch%252Felasticsearch/artifacts" | jq -r '[.[].tags[]?.name] | sort_by(split(".") | map(tonumber)) | last')Configura las variables de entorno a las que se hará referencia en esta guía. Modifica estos valores según sea necesario para que coincidan con tu entorno específico:
# GDC environment configuration export PROJECT_ID="your-project-id" export CLUSTER_NAME="your-shared-cluster-name" export ZONE="your-zone-name" export USER_NAME="your-gdc-user-email" # Marketplace package and Elastic Stack configuration export MARKETPLACE_SERVICE_NAME="elastic" # LLM service configuration export LLM_CONNECTOR_NAME="your-llm-connector-name" export LLM_API_URL="http://your-llm-service-host:11434/v1/chat/completions" export LLM_MODEL_NAME="your-llm-model-name" export LLM_API_KEY="your-llm-api-key"Configura
gdcloud:gdcloud config set project ${PROJECT_ID} gdcloud config set core/zone ${ZONE}Solicita o vincula los roles de IAM a nivel de proyecto necesarios para tu usuario en el proyecto de GDC de destino (
${PROJECT_ID}). Según la documentación del ciclo de vida de GDC Marketplace y los requisitos de redes de GDC, se requieren, al menos, los siguientes roles de proyecto:- Administrador de espacio de nombres (
namespace-admin): Acceso de administrador completo para crear y administrar cargas de trabajo, Secrets, Services y recursos personalizados de Kubernetes dentro del espacio de nombres del proyecto en el clúster compartido. - Visualizador de proyectos (
project-viewer): Acceso de lectura para ver el proyecto existente y sus recursos en la consola y la CLI de GDC. - Editor de Marketplace (
marketplace-editor): Permiso para instalar, actualizar y desinstalar paquetes de software de GDC Marketplace en el proyecto. - Administrador de NetworkPolicy del proyecto (
project-networkpolicy-admin): Permiso para configurar reglas globales deProjectNetworkPolicyque permitan que el tráfico externo y entre proyectos llegue a los servicios de LoadBalancer de Kibana y Elasticsearch.
Pídele al administrador de IAM de tu organización o al administrador de IAM del proyecto que te otorgue estos roles (o ejecuta los siguientes comandos si tienes privilegios de administrador de IAM):
gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=namespace-admin gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=project-viewer gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=marketplace-editor gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=project-networkpolicy-admin- Administrador de espacio de nombres (
Recupera el kubeconfig del clúster compartido y configura un alias de shell de
kuparakubectl:KUBECONFIG=kubeconfig-${CLUSTER_NAME}.yaml gdcloud clusters \ get-credentials ${CLUSTER_NAME} \ --zone ${ZONE} alias ku="kubectl --kubeconfig=kubeconfig-${CLUSTER_NAME}.yaml"
Instala el paquete de Marketplace de Elastic
En esta sección, instalarás el operador de Elastic Cloud on Kubernetes (ECK) en el espacio de nombres de tu proyecto desde el Marketplace de GDC aislado siguiendo la guía del ciclo de vida de Marketplace de GDC.
Instala a través de la CLI de gdcloud (o la consola de GDC)
Enumera los paquetes de software disponibles en Marketplace para confirmar el nombre del servicio y la versión disponible:
gdcloud marketplace services listEl resultado debería ser similar al siguiente:
SERVICE LATEST VERSION confluent <CONFLUENT_VERSION> dataproc <DATAPROC_VERSION> elastic <ECK_OPERATOR_VERSION> mariadb <MARIADB_VERSION> mattermost <MATTERMOST_VERSION> mongodb <MONGODB_VERSION> neo4j <NEO4J_VERSION> redis <REDIS_VERSION> starboard <STARBOARD_VERSION> starburst <STARBURST_VERSION> tes <TES_VERSION> vault <VAULT_VERSION>Instala el paquete de Elastic de Marketplace en tu clúster compartido de destino:
gdcloud marketplace services install ${MARKETPLACE_SERVICE_NAME} \ --cluster=${CLUSTER_NAME} \ --zone=${ZONE} \ --project ${PROJECT_ID}*(Como alternativa, en la consola de GDC, selecciona tu proyecto
${PROJECT_ID}, navega a Marketplace, selecciona la ficha Elastic Cloud on Kubernetes, haz clic en Instalar, selecciona el clúster${CLUSTER_NAME}y haz clic en Instalar).*Verifica que el StatefulSet y el Pod
elastic-operatorse estén ejecutando en el espacio de nombres de tu proyecto (${PROJECT_ID}):ku get statefulset,pods -n ${PROJECT_ID}El resultado debería ser similar al siguiente:
NAME READY AGE statefulset.apps/elastic-operator 1/1 3m NAME READY STATUS RESTARTS AGE pod/elastic-operator-0 1/1 Running 0 3m
Implementa Elastic Stack
Crea el manifiesto de Elastic Stack
El paquete de GDC Marketplace implementa el operador de ECK preconfigurado para extraer imágenes de contenedor de Elasticsearch y Kibana del registro local de Harbor aislado.
Crea elastic-stack.yaml para implementar un clúster de Elasticsearch de 1 nodo, una instancia de IU de Kibana, servicios HTTP de LoadBalancer y un allow-all NetworkPolicy en el espacio de nombres de tu proyecto (${PROJECT_ID}). El manifiesto también incluye una licencia Secret conciliada por el operador de ECK, configurada aquí como una prueba de Enterprise de 30 días (eck-trial-license) en caso de que aún no tengas tu propia licencia de Enterprise:
cat <<EOF > elastic-stack.yaml
apiVersion: v1
kind: Secret
metadata:
name: eck-trial-license
namespace: ${PROJECT_ID}
labels:
license.k8s.elastic.co/type: enterprise_trial
annotations:
elastic.co/eula: accepted
---
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: elastic-cluster
namespace: ${PROJECT_ID}
spec:
version: ${ELASTIC_VERSION}
http:
service:
spec:
type: LoadBalancer
nodeSets:
- name: default
count: 1
config:
# Disable mmap to avoid requiring privileged host sysctl modifications
node.store.allow_mmap: false
podTemplate:
spec:
initContainers:
- name: sysctl
securityContext:
privileged: false
allowPrivilegeEscalation: false
runAsNonRoot: true
capabilities:
drop:
- ALL
command: ["sh", "-c", "true"]
containers:
- name: elasticsearch
resources:
requests:
memory: 2Gi
cpu: 500m
limits:
memory: 4Gi
cpu: 2
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
name: kibana-ui
namespace: ${PROJECT_ID}
spec:
version: ${ELASTIC_VERSION}
count: 1
elasticsearchRef:
name: elastic-cluster
http:
service:
spec:
type: LoadBalancer
podTemplate:
spec:
containers:
- name: kibana
resources:
requests:
memory: 1Gi
cpu: 200m
limits:
memory: 2Gi
cpu: 1
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
namespace: ${PROJECT_ID}
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
EOF
Aplica y verifica la implementación
Aplica el manifiesto
ku apply -f elastic-stack.yaml
Aplica un ProjectNetworkPolicy global para que las reglas de firewall de GDC permitan el tráfico entrante a las IP de LoadBalancer y el tráfico entre proyectos (como la comunicación con el servicio de LLM):
kubectl --kubeconfig=kubeconfig-global.yaml apply -f - <<EOF
apiVersion: networking.global.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
namespace: ${PROJECT_ID}
name: allow-inbound-traffic-from-external
spec:
policyType: Ingress
subject:
subjectType: UserWorkload
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
---
apiVersion: networking.global.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
namespace: ${PROJECT_ID}
name: allow-inbound-traffic-from-all-projects
spec:
policyType: Ingress
subject:
subjectType: UserWorkload
ingress:
- from:
- projects: {}
EOF
Supervisa los Pods, los Services y los recursos personalizados hasta que Elasticsearch y
Kibana alcancen el estado Running y Ready, y obtengan las IP de LoadBalancer en
${CLUSTER_NAME}:
ku get pods,svc,elasticsearch,kibana -n ${PROJECT_ID}
Una vez que los recursos estén listos, el resultado debería ser similar al siguiente:
NAME READY STATUS RESTARTS AGE
pod/elastic-cluster-es-default-0 1/1 Running 0 3m
pod/elastic-operator-0 1/1 Running 0 15m
pod/kibana-ui-kb-869844478d-59ptw 1/1 Running 0 3m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/elastic-cluster-es-default ClusterIP None <none> 9200/TCP 11m
service/elastic-cluster-es-http LoadBalancer 10.101.18.181 <ELASTIC_EXTERNAL_IP> 9200:32159/TCP 11m
service/elastic-cluster-es-internal-http ClusterIP 10.101.18.81 <none> 9200/TCP 11m
service/elastic-cluster-es-transport ClusterIP None <none> 9300/TCP 11m
service/g-svc-g-fre-elastic-cluster-es-http-shared-clus-extern-56d5eb67 ClusterIP None <none> 9200/TCP 11m
service/g-svc-g-fre-kibana-ui-kb-http-shared-cluster-0b-extern-75a78b1f ClusterIP None <none> 5601/TCP 11m
service/kibana-ui-kb-http LoadBalancer 10.101.19.86 <KIBANA_EXTERNAL_IP> 5601:32735/TCP 11m
NAME HEALTH NODES VERSION PHASE AGE
elasticsearch.elasticsearch.k8s.elastic.co/elastic-cluster green 1 <ELASTIC_VERSION> Ready 3m
NAME HEALTH NODES VERSION AGE
kibana.kibana.k8s.elastic.co/kibana-ui green 1 <ELASTIC_VERSION> 3m
Exporta las IP externas de LoadBalancer asignadas a Elasticsearch y Kibana para usarlas en los pasos siguientes:
export ELASTICSEARCH_IP=$(ku get svc elastic-cluster-es-http \
-n ${PROJECT_ID} \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export KIBANA_IP=$(ku get svc kibana-ui-kb-http \
-n ${PROJECT_ID} \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Kibana External URL: https://${KIBANA_IP}:5601"
echo "Elasticsearch External URL: https://${ELASTICSEARCH_IP}:9200"
Recupera las credenciales y accede a la IU de Kibana
Recupera la contraseña de superusuario de elastic
ECK genera automáticamente un Secret de Kubernetes
(elastic-cluster-es-elastic-user) que contiene la contraseña predeterminada del superusuario
elastic. Recupérala y decodifícala con el siguiente comando:
export ELASTIC_PASSWORD=$(ku get secret elastic-cluster-es-elastic-user \
-n ${PROJECT_ID} \
-o jsonpath='{.data.elastic}' | base64 -d)
echo "${ELASTIC_PASSWORD}"
Verifica la conectividad y accede
Verifica que se pueda acceder a Elasticsearch a través de HTTPS desde tu estación de trabajo:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" "https://${ELASTICSEARCH_IP}:9200"Abre
https://${KIBANA_IP}:5601en tu navegador, acepta la advertencia del certificado autofirmado y accede con lo siguiente:- Nombre de usuario:
elastic - Contraseña:
<output from ELASTIC_PASSWORD above>
- Nombre de usuario:
Verifica la licencia de Elastic
Las funciones potenciadas por IA generativa de Elastic (incluidos el agente de IA de Elastic, Agent Builder y Attack Discovery) y los conectores de LLM externos requieren una licencia Enterprise (o una licencia de prueba de Enterprise activa de 30 días).
Cuando un operador de ECK administra un clúster de Elasticsearch, el license-controller de ECK concilia de forma continua la licencia del clúster con los Secrets de Kubernetes en el espacio de nombres del operador (${PROJECT_ID}). Si aún no tienes una licencia de Enterprise de producción, el Secret eck-trial-license incluido en elastic-stack.yaml (license.k8s.elastic.co/type:
enterprise_trial) le indica a ECK que aprovisione y mantenga una licencia de prueba de Enterprise de 30 días de forma automática. (Ten en cuenta que, si inicias una prueba de forma manual a través de POST
/_license/start_trial sin un Secret de licencia de ECK, ECK revertirá
el clúster a basic en el siguiente ciclo de conciliación).
Para verificar la licencia activa a través de la CLI, haz lo siguiente:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${ELASTICSEARCH_IP}:9200/_license" | jq .
*(Como alternativa, en Kibana, abre el menú de navegación y ve a Management
> Dev Tools y, luego, ejecuta GET /_license en la pestaña Console).*
El resultado debería ser similar al siguiente, que muestra "status": "active"
y "type": "enterprise" (o "type": "trial" si usas la licencia de prueba):
{
"license": {
"status": "active",
"uid": "d8c2b566-8c2b-436c-a1ea-5502d6790505",
"type": "trial",
"issue_date": "2026-09-17T23:19:25.053Z",
"issue_date_in_millis": 1789687165053,
"expiry_date": "2026-10-17T23:19:25.053Z",
"expiry_date_in_millis": 1792279165053,
"max_nodes": 1000,
"max_resource_units": null,
"issued_to": "elastic-cluster",
"issuer": "elasticsearch",
"start_date_in_millis": -1
}
}
Carga un conjunto de datos de muestra integrado
Para evaluar las funciones de IA de Elastic sin escribir canalizaciones de transferencia de datos personalizadas ni requerir acceso a Internet externo, puedes cargar el conjunto de datos de registros web de muestra integrado de Kibana (kibana_sample_data_logs). Como el archivo de datos de muestra se incluye en la imagen de contenedor de Kibana, se instala en segundos en un entorno aislado. El conjunto de datos propaga 14,074 documentos de registro de acceso web
que contienen direcciones IP de clientes (clientip), códigos de estado HTTP (response),
países de origen y destino (geo.src, geo.dest), bytes transferidos
(bytes), URLs y cadenas de usuario-agente.
Consulta
Agrega datos de muestra en Kibana
y
elastic-datasets-builtin.md
para obtener conjuntos de datos integrados adicionales (flights y ecommerce).
Carga el conjunto de datos
Ejecuta el siguiente comando para cargar kibana_sample_data_logs a través del extremo de muestra de datos interno de Kibana:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
-X POST "https://${KIBANA_IP}:5601/api/sample_data/logs" \
-H "kbn-xsrf: true" \
-H "x-elastic-internal-origin: Kibana"
Verifica el conjunto de datos de muestra
Verifica que el conjunto de datos de muestra logs esté marcado como installed en Kibana y
confirma que el flujo de datos de respaldo contenga documentos 14,074 en
Elasticsearch:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${KIBANA_IP}:5601/api/sample_data" \
-H "kbn-xsrf: true" \
-H "x-elastic-internal-origin: Kibana" \
| jq '.[] | select(.id == "logs") | {id, name, status}'
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${ELASTICSEARCH_IP}:9200/_cat/indices/kibana_sample_data_logs*?v"
El resultado debería ser similar al siguiente:
{
"id": "logs",
"name": "Sample web logs",
"status": "installed"
}
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
yellow open .ds-kibana_sample_data_logs-2026.09.22-000001 0YivG3oFQQmb-mYqmULr_g 1 1 14074 0 8.7mb 8.7mb 8.7mb
Requisitos para el servicio de LLM
En un entorno aislado de GDC, no se puede acceder a los LLM administrados de forma elástica alojados en la nube ni a los proveedores externos de LLM de SaaS públicos. Según tu entorno de GDC, haz lo siguiente:
- Gemini administrado en GDC: En los entornos de GDC que ofrecen Gemini como un servicio de LLM administrado, puedes conectar Kibana directamente al servicio de Gemini administrado si está disponible.
- Servicio de LLM autoadministrado: Como alternativa, puedes conectar Elastic a un servicio de LLM autoadministrado alojado en tu entorno de GDC (por ejemplo, en nodos de GPU de GDC).
La implementación de un servicio de LLM autoadministrado está fuera del alcance de esta guía. Cuando aprovisiones o selecciones un servicio de LLM autoadministrado para Elastic, asegúrate de que cumpla con los requisitos derivados de las guías oficiales de Elastic:
- Configura el acceso a los LLM
- LLM personalizados autoadministrados (consulta también Cómo conectarse a vLLM en entornos aislados)
- Observabilidad de LLM y de la IA de agentes
Resumen de los requisitos técnicos
En los siguientes puntos, se resumen los requisitos clave de la API, la capacidad del modelo, la ventana de contexto y el tiempo de ejecución necesarios para que un servicio de LLM autoadministrado funcione de manera confiable con el agente de IA, el Creador de agentes y el Descubrimiento de ataques de Elastic. Ten en cuenta que los nombres de los parámetros de configuración específicos que se citan en las siguientes secciones (como num_ctx, num_predict, repeat_penalty, repeat_last_n, OLLAMA_FLASH_ATTENTION y OLLAMA_NUM_PARALLEL) se proporcionan con Ollama como ejemplo. Aplica la
configuración equivalente si usas otro motor de inferencia, como vLLM.
- API de finalizaciones de chat compatible con OpenAI (
/v1/chat/completions) con llamada a funciones nativa:- Cualquier servidor de inferencia autoadministrado (como Ollama o vLLM) que exponga un extremo
/v1/chat/completionscompatible con OpenAI (incluido el streaming de eventos enviados por el servidor a través de"stream": true) se puede integrar con el conector de OpenAI de Kibana con el proveedor establecido en Otro (servicio compatible con OpenAI). - Habilita la llamada a funciones nativa (
"enableNativeFunctionCalling": true) en el conector de Kibana cuando uses modelos que admitan la llamada a herramientas nativa de la nube (comogemma4:31b). De forma predeterminada ("enableNativeFunctionCalling": false), Kibana usa la llamada a funciones simulada basada en instrucciones, que es menos confiable durante las llamadas a herramientas anidadas y puede fallar con errores de análisis (comoError: Missing name for tool use). Habilitar la llamada a funciones nativa delega el formato de la llamada a la herramienta directamente al protocolo OpenAItoolsdel servidor de inferencia.
- Cualquier servidor de inferencia autoadministrado (como Ollama o vLLM) que exponga un extremo
- Modelo ajustado según las instrucciones con capacidades nativas de llamadas a funciones y herramientas:
- El agente de IA de Elastic (
elastic-ai-agent) y los agentes personalizados de Agent Builder dependen de la llamada a funciones de varios pasos (platform.core.list_indices,platform.core.get_index_mapping,platform.core.search,platform.core.execute_esqlyplatform.core.cases). Usan estas herramientas para descubrir asignaciones de índices, generar y ejecutar consultas de ES|QL en Elasticsearch y sintetizar los hallazgos. - El modelo elegido debe estar ajustado según las instrucciones y ser capaz de generar invocaciones válidas de herramientas nativas de la nube, resultados JSON estructurados y consultas de ES|QL. Consulta la matriz de rendimiento de LLM para Observability y la matriz de rendimiento de LLM para Elastic Security de Elastic para conocer los criterios de evaluación de modelos.
- El agente de IA de Elastic (
- Ventana de contexto de al menos
128,000tokens (por ejemplo,num_ctx: 131072en Ollama):- Si bien la documentación general de LLM personalizados autoadministrados de Elastic cita un mínimo de 64,000 tokens, se recomienda una ventana de contexto de 128,000 tokens (tokens de
131,072) para el agente de IA de Elastic (elastic-ai-agent). - Durante las investigaciones de varios pasos, el agente combina instrucciones del sistema y definiciones de herramientas con esquemas de índice, documentación de ES|QL y resultados de búsqueda sin procesar (por ejemplo, recupera hasta 100 documentos de muestra para inspeccionar los valores de los campos antes de agregarlos). Una sola investigación puede superar los 70,000 tokens en el historial de conversaciones, lo que hace que una ventana de contexto de 64,000 tokens trunque la instrucción a mitad de la conversación.
- Ten en cuenta que, en Ollama, una directiva
PARAMETER num_ctxintegrada en elModelfilede un modelo anula la variable de entornoOLLAMA_CONTEXT_LENGTHdel contenedor. Asegúrate de queOLLAMA_CONTEXT_LENGTH=131072yPARAMETER num_ctx 131072en elModelfiledel modelo estén configurados (en una GPU NVIDIA A100 o H100 de 80 GB,gemma4:31ben el contexto128Kcabe cómodamente en la VRAM).
- Si bien la documentación general de LLM personalizados autoadministrados de Elastic cita un mínimo de 64,000 tokens, se recomienda una ventana de contexto de 128,000 tokens (tokens de
- Límite de tokens de salida y penalización por repetición leve (por ejemplo,
num_predict: 8192yrepeat_penalty: 1.05en Ollama):- El adaptador de transmisión de OpenAI de Kibana trata
"finish_reason": "length"como un error fatal deToken limit reachedy envía"temperature": 0(decodificación codiciosa) en cada solicitud. - En el caso de los modelos capaces de razonar (como
gemma4:31b, que genera tokens de pensamiento internos antes de emitir llamadas a herramientas), configura un límite de tokens de salida de al menos8,192tokens y una penalización por repetición suave. Por ejemplo, en Ollama, configuranum_predict: 8192,repeat_penalty: 1.05yrepeat_last_n: 64. Evita las penalizaciones por repetición demasiado agresivas (como1.15+en ventanas grandes), ya que penalizan al modelo cuando copia una consulta de ES|QL de su bloque de razonamiento interno en el JSON final de la llamada a la herramienta.
- El adaptador de transmisión de OpenAI de Kibana trata
- Aceleración de GPU, Flash Attention y ranuras de caché de KV paralelas (menos de 60 s por turno):
- El conector de OpenAI de Kibana aplica un tiempo de espera HTTP predeterminado de 60 segundos por turno de transmisión.
- Debido a que un solo turno de agente puede activar llamadas secundarias de LLM anidadas (por ejemplo, para traducir solicitudes de búsqueda en lenguaje natural en consultas de ES|QL mientras la conversación del agente principal está activa), ejecutar un servidor de inferencia con solo una ranura de caché de KV puede hacer que las llamadas secundarias expulsen el contexto almacenado en caché de la conversación principal y ralenticen los turnos posteriores.
- En Ollama, configura
OLLAMA_FLASH_ATTENTION=1yOLLAMA_NUM_PARALLEL=2para que Flash Attention acelere la evaluación de contexto largo y la conversación principal y las llamadas secundarias anidadas mantengan ranuras de caché de KV separadas en la memoria de la GPU.
Configura el conector de LLM en Kibana
Una vez que tu servicio de LLM compatible con OpenAI esté en ejecución y sea accesible desde el pod de Kibana, configura un conector de OpenAI en Kibana con la CLI o la IU de Kibana.
Opción 1: Crea el conector a través de la CLI
Ejecuta el siguiente comando
POST /api/actions/connectorpara crear un conector de OpenAI (.gen-ai) que apunte a tu servicio de LLM (${LLM_API_URL}) con la llamada a funciones nativa habilitada ("enableNativeFunctionCalling": true). Kibana genera y asigna automáticamente un ID de conector único:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/actions/connector" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"${LLM_CONNECTOR_NAME}\", \"connector_type_id\": \".gen-ai\", \"config\": { \"apiProvider\": \"Other\", \"apiUrl\": \"${LLM_API_URL}\", \"defaultModel\": \"${LLM_MODEL_NAME}\", \"enableNativeFunctionCalling\": true }, \"secrets\": { \"apiKey\": \"${LLM_API_KEY}\" } }" | jq .Verifica que el conector
${LLM_CONNECTOR_NAME}exista y no le falten secretos. Para ello, enumera los conectores de Kibana y filtra por nombre:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${KIBANA_IP}:5601/api/actions/connectors" \ -H "kbn-xsrf: true" \ | jq ".[] | select(.name == \"${LLM_CONNECTOR_NAME}\")"El resultado debería ser similar al siguiente:
{ "id": "<GENERATED_CONNECTOR_ID>", "name": "<LLM_CONNECTOR_NAME>", "config": { "apiProvider": "Other", "apiUrl": "<LLM_API_URL>", "defaultModel": "<LLM_MODEL_NAME>", "enableNativeFunctionCalling": true }, "connector_type_id": ".gen-ai", "is_preconfigured": false, "is_deprecated": false, "referenced_by_count": 0, "is_missing_secrets": false, "is_system_action": false }Para probar la conectividad de extremo a extremo en vivo entre Kibana y el servicio de LLM desde la CLI, busca el ID del conector generado de forma automática por nombre y llama a su extremo
_execute:export CONNECTOR_ID=$(curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${KIBANA_IP}:5601/api/actions/connectors" \ -H "kbn-xsrf: true" \ | jq -r ".[] | select(.name == \"${LLM_CONNECTOR_NAME}\") | .id") curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/actions/connector/${CONNECTOR_ID}/_execute" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"params\": { \"subAction\": \"invokeAI\", \"subActionParams\": { \"model\": \"${LLM_MODEL_NAME}\", \"messages\": [ { \"role\": \"user\", \"content\": \"Reply with the single word Pong.\" } ] } } }" | jq .El resultado debería ser similar al siguiente, en el que se muestra
"status": "ok"y la respuesta del modelo:{ "status": "ok", "data": { "message": "Pong", "usage": { "prompt_tokens": 23, "prompt_tokens_details": { "cached_tokens": 0 }, "completion_tokens": 38, "total_tokens": 61 } }, "connector_id": "<GENERATED_CONNECTOR_ID>" }
Opción 2: Crea el conector a través de la IU de Kibana
- Accede a Kibana (
https://${KIBANA_IP}:5601). - Abre el menú de navegación principal y ve a Administración > Alertas y estadísticas > Conectores.
- Haz clic en Crear conector y selecciona OpenAI.
- Configura los campos del conector:
- Nombre del conector: Ingresa el valor de
${LLM_CONNECTOR_NAME}(por ejemplo,Gemma4-31B). - Selecciona un proveedor de OpenAI: Selecciona Otro (servicio compatible con OpenAI).
- URL: Ingresa el valor de
${LLM_API_URL}(el extremo/v1/chat/completionsde tu servicio de LLM). - Modelo predeterminado: Ingresa el valor de
${LLM_MODEL_NAME}(por ejemplo,gemma4:31b). - Clave de API: Ingresa el valor de
${LLM_API_KEY}(cualquier cadena no vacía si tu servidor no aplica la autenticación de portador o la clave de API de tu servidor). - Habilitar la llamada a funciones nativas: Activa el botón de activación.
- Nombre del conector: Ingresa el valor de
- Haz clic en Guardar y probar para guardar el conector y abrir de inmediato la pestaña Probar.
- En la pestaña Test, ingresa una instrucción de ejemplo (por ejemplo,
Reply with the single word Pong.) y haz clic en Run para verificar que Kibana reciba una respuesta válida del servicio de LLM.
Prueba las integraciones de LLM
Con el conjunto de datos kibana_sample_data_logs instalado y el conector de LLM verificado, puedes probar las capacidades de IA de Elastic en Kibana.
Agente de IA de Elastic
El
agente de IA de Elastic
(elastic-ai-agent) es el agente conversacional predeterminado integrado en Kibana
para explorar y analizar datos en Elasticsearch con lenguaje natural.
Con la tecnología de tu conector de LLM configurado y las herramientas integradas de Agent Builder, el agente descubre automáticamente los índices y las asignaciones de Elasticsearch (platform.core.list_indices, platform.core.get_index_mapping), genera y ejecuta consultas de ES|QL (platform.core.search, platform.core.execute_esql) y sintetiza los hallazgos sin que necesites conocer el esquema subyacente de antemano. También puedes crear tus propios agentes personalizados con instrucciones y herramientas adaptadas. Consulta Agentes personalizados con Agent Builder para obtener más detalles.
Para abrir y configurar el agente de IA de Elastic en la IU de Kibana, haz lo siguiente:
- Abre el menú de navegación principal en Kibana y selecciona Agente de IA (o haz clic en
el botón Preguntar a la IA en la barra de encabezado superior, o navega directamente a
https://${KIBANA_IP}:5601/app/chat). - Asegúrate de que Elastic AI Agent esté seleccionado como el agente de IA activo y que tu conector de LLM (
${LLM_CONNECTOR_NAME}) esté seleccionado como el conector del modelo.
Ejemplo 1: Investiga las solicitudes HTTP con errores y los posibles escáneres
Pídele al agente de IA de Elastic que identifique las principales direcciones IP de clientes que generan
respuestas HTTP que no sean 200 en kibana_sample_data_logs, sus países de origen
y si alguna de esas IP también tuvo tráfico HTTP 200 exitoso:
Opción 1: A través de la IU de Kibana: Ingresa la siguiente pregunta en la instrucción de chat del agente de IA:
In kibana_sample_data_logs, which client IP addresses had the most failed HTTP requests (non-200 status codes), what countries did they come from, and did any of those IPs also have successful HTTP 200 requests?Opción 2: Con la CLI
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/converse" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"agent_id\": \"elastic-ai-agent\", \"connector_id\": \"${CONNECTOR_ID}\", \"input\": \"In kibana_sample_data_logs, which client IP addresses had the most failed HTTP requests (non-200 status codes), what countries did they come from, and did any of those IPs also have successful HTTP 200 requests?\" }" | jq '{ status: .status, tools_called: [.steps[] | select(.type == "tool_call") | {tool_id: .tool_id, params: .params}], response: .response.message }'
Qué sucede tras bambalinas:
- El agente llama a
platform.core.get_index_mapping(indices=["kibana_sample_data_logs"])para inspeccionar los campos disponibles (clientip,geo.src,response,bytes,request, etc.). Llama a
platform.core.searchpara inspeccionar documentos de muestra y confirmar que los códigos de estado HTTP se almacenen enresponse. Luego, ejecuta agregaciones de ES|QL para clasificar las solicitudes que no sean200porclientipygeo.src, y verificar cuáles de las IPs principales también registraron tráfico deresponse == "200":FROM kibana_sample_data_logs | WHERE response != "200" | STATS failed_count = COUNT(*) BY clientip, geo.src | SORT failed_count DESC | LIMIT 10FROM kibana_sample_data_logs | WHERE clientip IN ("30.156.16.164", "213.235.30.99", "221.241.228.46", "209.94.234.133", "7.127.73.138", "247.240.202.244", "130.125.65.24", "164.85.94.243", "81.194.200.150", "30.145.143.210") AND response == "200" | STATS success_count = COUNT(*) BY clientip
La respuesta del agente de IA de Elastic debería ser similar a la siguiente:
La dirección IP del cliente con la mayor cantidad de solicitudes HTTP fallidas (códigos de estado que no son 200) en
kibana_sample_data_logses 30.156.16.164, que se originó en Estados Unidos y registró 100 solicitudes fallidas.Otras direcciones IP principales con solicitudes con errores, todas las cuales también se originaron en Estados Unidos, incluyen las siguientes:
- 213.235.30.99: 6 fallas
- 221.241.228.46: 6 fallas
- 209.94.234.133: 6 fallas
- 7.127.73.138: 5 fallas
- 247.240.202.244: 5 fallas
- 130.125.65.24: 5 fallas
- 164.85.94.243: 4 fallas
- 81.194.200.150: 4 fallas
- 30.145.143.210: 4 fallas
En cuanto a las solicitudes exitosas, todas las IPs que se mencionaron anteriormente tuvieron al menos algunas respuestas HTTP 200 exitosas, excepto 30.156.16.164, que no tuvo solicitudes exitosas.
Ejemplo 2: Analiza el volumen de tráfico y los bytes de carga útil por código de estado HTTP
Pídele al agente de IA de Elastic el desglose de los códigos de estado de respuesta HTTP de
todas las solicitudes en kibana_sample_data_logs y el total de bytes transferidos para
cada código de estado:
Opción 1: A través de la IU de Kibana: Inicia una nueva conversación en Agente de IA y escribe la siguiente pregunta:
In kibana_sample_data_logs, what is the breakdown of HTTP response status codes across all requests, and how much total data (in bytes) was transferred for each status code?Opción 2: Con la CLI
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/converse" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"agent_id\": \"elastic-ai-agent\", \"connector_id\": \"${CONNECTOR_ID}\", \"input\": \"In kibana_sample_data_logs, what is the breakdown of HTTP response status codes across all requests, and how much total data (in bytes) was transferred for each status code?\" }" | jq '{ status: .status, tools_called: [.steps[] | select(.type == "tool_call") | {tool_id: .tool_id, params: .params}], response: .response.message }'
Qué sucede tras bambalinas:
- El agente inspecciona el esquema
kibana_sample_data_logsa través deplatform.core.get_index_mapping, identificando los camposresponse.keywordybytes. Invoca a
platform.core.search, que genera y ejecuta la siguiente agregación de ES|QL:FROM kibana_sample_data_logs | STATS request_count = COUNT(*), total_bytes = SUM(bytes) BY response.keyword | LIMIT 100
La respuesta del agente de IA de Elastic debería ser similar a la siguiente:
El desglose de los códigos de estado de respuesta HTTP y el total de datos transferidos en
kibana_sample_data_logses el siguiente:
| Código de estado | Recuento de solicitudes | Total de datos transferidos (bytes) |
|---|---|---|
| 200 | 12,832 | 75,681,246 |
| 404 | 801 | 4,044,443 |
| 503 | 441 | 0 |
Agentes personalizados con Agent Builder
Agent Builder
te permite crear agentes de IA personalizados adaptados a flujos de trabajo operativos específicos
combinando instrucciones del sistema personalizadas con herramientas integradas (como
platform.core.cases, platform.core.execute_esql y platform.core.search)
y herramientas definidas por el usuario (esql, index_search, workflow y mcp).
En el siguiente ejemplo, se compila un agente de respuesta ante incidentes de seguridad web que
opera directamente en el conjunto de datos kibana_sample_data_logs. Dada una instrucción en lenguaje natural, el agente hace lo siguiente:
- Ejecuta una herramienta de ES|QL personalizada (
investigate_suspicious_web_clients) para agregar respuestas HTTP que no sean200enkibana_sample_data_logspor IP de cliente. - Distingue la actividad maliciosa de reconocimiento y explotación
(
30.156.16.164) de las fallas de descarga benignas. - Invoca una herramienta personalizada de flujo de trabajo de Kibana (
create_security_case) para abrir automáticamente un caso de seguridad de Kibana estructurado con sus hallazgos y pasos de solución.
Crear herramientas personalizadas de ES|QL y de creación de casos
Crea una herramienta personalizada de
esqlllamadainvestigate_suspicious_web_clientsque consultekibana_sample_data_logspara obtener las principales direcciones IP de clientes que generan códigos de estado HTTP que no sean200, junto con sus códigos de estado, país de origen (geo.src), usuarios-agentes (agent) y URLs solicitadas (request):Opción 1: A través de la IU de Kibana:
- En Kibana, abre el menú de navegación principal y ve a
Elasticsearch > Agents y, luego, haz clic en Manage all tools
(o navega directamente a
https://${KIBANA_IP}:5601/app/agent_builder/tools) y haz clic en New tool. Configura los campos de la herramienta:
- ID de la herramienta:
investigate_suspicious_web_clients - Tipo: ES|QL
- Descripción:
Finds the top client IP addresses generating non-200 HTTP error responses in kibana_sample_data_logs, returning their error count, status codes, source country, user agents, and requested URL paths. - Etiquetas:
security,logs Consulta de ES|QL:
FROM kibana_sample_data_logs | WHERE response != "200" | STATS error_count = COUNT(*), status_codes = VALUES(response), country = VALUES(geo.src), user_agents = VALUES(agent), sample_requests = VALUES(request) BY clientip | SORT error_count DESC | LIMIT 5
- ID de la herramienta:
Haz clic en Guardar.
- En Kibana, abre el menú de navegación principal y ve a
Elasticsearch > Agents y, luego, haz clic en Manage all tools
(o navega directamente a
Opción 2: Con la CLI
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/tools" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "id": "investigate_suspicious_web_clients", "type": "esql", "description": "Finds the top client IP addresses generating non-200 HTTP error responses in kibana_sample_data_logs, returning their error count, status codes, source country, user agents, and requested URL paths.", "tags": ["security", "logs"], "configuration": { "query": "FROM kibana_sample_data_logs | WHERE response != \"200\" | STATS error_count = COUNT(*), status_codes = VALUES(response), country = VALUES(geo.src), user_agents = VALUES(agent), sample_requests = VALUES(request) BY clientip | SORT error_count DESC | LIMIT 5", "params": {} } }' | jq '{id: .id, type: .type}'
Debido a que la herramienta
platform.core.casesintegrada de Agent Builder es de solo lectura (se usa para buscar y recuperar casos existentes), crea un flujo de trabajo de Kibana (create-security-case) que invoque el pasocases.createCasenativo de Kibana y regístralo como una herramientaworkflowllamadacreate_security_case:Opción 1: A través de la IU de Kibana:
- Abre el menú de navegación principal y ve a Administración >
Flujos de trabajo (o navega directamente a
https://${KIBANA_IP}:5601/app/workflows) y, luego, haz clic en Crear un flujo de trabajo nuevo. En el editor de YAML, pega la siguiente definición de flujo de trabajo y haz clic en Guardar:
version: "1" name: Create Security Case description: Creates a new Kibana Case in Elastic Security enabled: true triggers: - type: manual inputs: - name: title type: string required: true description: Title of the security case - name: description type: string required: true description: Detailed markdown description of the incident findings - name: severity type: string default: high description: Severity of the case (low, medium, high, critical) steps: - name: create_case type: cases.createCase with: title: ${{ inputs.title }} description: ${{ inputs.description }} severity: ${{ inputs.severity }} owner: securitySolution tags: - agent-builder - web-logsRegresa a la página Herramientas de Agent Builder (
https://${KIBANA_IP}:5601/app/agent_builder/toolso ve a Elasticsearch > Agentes y haz clic en Administrar todas las herramientas) y haz clic en Nueva herramienta.Configura los campos de la herramienta de flujo de trabajo y haz clic en Guardar:
- ID de la herramienta:
create_security_case - Tipo: Flujo de trabajo
- Flujo de trabajo: Selecciona Crear caso de seguridad
(
create-security-case) - Descripción:
Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical). - Etiquetas:
security,cases
- ID de la herramienta:
- Abre el menú de navegación principal y ve a Administración >
Flujos de trabajo (o navega directamente a
Opción 2: Con la CLI
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/workflows" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "workflows": [ { "yaml": "version: \"1\"\nname: Create Security Case\ndescription: Creates a new Kibana Case in Elastic Security\nenabled: true\ntriggers:\n - type: manual\ninputs:\n - name: title\n type: string\n required: true\n description: Title of the security case\n - name: description\n type: string\n required: true\n description: Detailed markdown description of the incident findings\n - name: severity\n type: string\n default: high\n description: Severity of the case (low, medium, high, critical)\nsteps:\n - name: create_case\n type: cases.createCase\n with:\n title: ${{ inputs.title }}\n description: ${{ inputs.description }}\n severity: ${{ inputs.severity }}\n owner: securitySolution\n tags:\n - agent-builder\n - web-logs\n" } ] }' | jq '.created[] | {id: .id, valid: .valid}' curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/tools" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "id": "create_security_case", "type": "workflow", "description": "Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical).", "tags": ["security", "cases"], "configuration": { "workflow_id": "create-security-case" } }' | jq '{id: .id, type: .type}'
Crea el agente personalizado de Incident Responder
Crea el agente web-security-incident-responder y asígnale las herramientas
investigate_suspicious_web_clients, create_security_case y
platform.core.cases:
Opción 1: A través de la IU de Kibana:
- Abre el menú de navegación principal y ve a Elasticsearch >
Agentes (o navega directamente a
https://${KIBANA_IP}:5601/app/agent_builder/agents) y, luego, haz clic en Nuevo agente. Configura los detalles del agente:
- ID del agente:
web-security-incident-responder - Nombre visible:
Web Security Incident Responder - Descripción visible:
Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings. - Etiquetas:
security,incident-response Instrucciones personalizadas:
You are an automated Web Security Incident Responder. When asked to investigate suspicious web traffic: 1. Call the `investigate_suspicious_web_clients` tool to inspect the top client IPs generating non-200 HTTP responses in `kibana_sample_data_logs`. 2. Identify any client IP exhibiting malicious reconnaissance or exploitation behavior (such as Nmap scans, WordPress login probing, `/etc/passwd` path traversal, or `/level/15/exec` command execution attempts) versus benign download errors. 3. Call the `create_security_case` tool to open a new Kibana Security Case for the malicious client IP. Set an appropriate `title`, `severity` (`high`), and a well-structured Markdown `description` summarizing the source IP, geolocation, total failed requests, observed attack categories (mapped to MITRE ATT&CK where applicable), sample malicious URLs, and recommended remediation steps. 4. Return a concise summary of your investigation and confirm the created Kibana Case details.Herramientas: Selecciona
investigate_suspicious_web_clients,create_security_caseyplatform.core.cases.
- ID del agente:
Haz clic en Guardar.
- Abre el menú de navegación principal y ve a Elasticsearch >
Agentes (o navega directamente a
Opción 2: Con la CLI
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/agents" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "id": "web-security-incident-responder", "name": "Web Security Incident Responder", "description": "Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings.", "labels": ["security", "incident-response"], "avatar_color": "#E7664C", "avatar_symbol": "IR", "configuration": { "instructions": "You are an automated Web Security Incident Responder. When asked to investigate suspicious web traffic:\n1. Call the `investigate_suspicious_web_clients` tool to inspect the top client IPs generating non-200 HTTP responses in `kibana_sample_data_logs`.\n2. Identify any client IP exhibiting malicious reconnaissance or exploitation behavior (such as Nmap scans, WordPress login probing, `/etc/passwd` path traversal, or `/level/15/exec` command execution attempts) versus benign download errors.\n3. Call the `create_security_case` tool to open a new Kibana Security Case for the malicious client IP. Set an appropriate `title`, `severity` (`high`), and a well-structured Markdown `description` summarizing the source IP, geolocation, total failed requests, observed attack categories (mapped to MITRE ATT&CK where applicable), sample malicious URLs, and recommended remediation steps.\n4. Return a concise summary of your investigation and confirm the created Kibana Case details.", "tools": [ { "tool_ids": [ "investigate_suspicious_web_clients", "create_security_case", "platform.core.cases" ] } ] } }' | jq '{id: .id, name: .name}'
Ejecuta el agente y verifica el caso de Kibana
Inicia una conversación con el agente de Web Security Incident Responder:
Opción 1: A través de la IU de Kibana:
- Abre el menú de navegación principal y ve a Elasticsearch >
Agentes (o navega directamente a
https://${KIBANA_IP}:5601/app/agent_builder). - Selecciona Web Security Incident Responder en el selector de agentes
y asegúrate de que esté seleccionado tu conector de LLM (
${LLM_CONNECTOR_NAME}). Escribe la siguiente instrucción:
Investigate the top client IPs generating HTTP errors in our web logs, identify any attacker performing reconnaissance or exploitation, and open a high-severity Kibana Security Case with your findings.
- Abre el menú de navegación principal y ve a Elasticsearch >
Agentes (o navega directamente a
Opción 2: Con la CLI
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/converse" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"agent_id\": \"web-security-incident-responder\", \"connector_id\": \"${CONNECTOR_ID}\", \"input\": \"Investigate the top client IPs generating HTTP errors in our web logs, identify any attacker performing reconnaissance or exploitation, and open a high-severity Kibana Security Case with your findings.\" }" | jq '{ status: .status, tools_called: [.steps[] | select(.type == "tool_call") | .tool_id], response: .response.message }'El resultado debería ser similar al siguiente:
{ "status": "completed", "tools_called": [ "investigate_suspicious_web_clients", "create_security_case" ], "response": "I have completed the investigation into the web logs. \n\nThe analysis identified client IP **`30.156.16.164`** (located in the US) as a malicious actor. This IP generated 100 failed requests (HTTP 404) exhibiting clear patterns of reconnaissance and exploitation:\n\n* **Reconnaissance:** Extensive probing for WordPress installations and plugins, as well as Nmap scan signatures (e.g., `/nmaplowercheck...`).\n* **Path Traversal:** Attempts to access sensitive system files via `/etc/passwd`.\n* **Remote Command Execution (RCE):** Attempts to execute commands through paths such as `/level/15/exec`.\n* **Application Probing:** Targeting common management interfaces including Tomcat, Axis2, and JMX consoles.\n\nAs a result, I have opened a **high-severity** Kibana Security Case to track this incident:\n\n* **Case ID:** `1f0e00a5-cc89-4f62-b305-f286fae3d70e`\n* **Title:** Malicious Reconnaissance and Exploitation Attempt from 30.156.16.164\n* **Recommended Action:** Immediately block the IP at the firewall/WAF and verify that no unauthorized access was achieved." }
Verifica el caso de seguridad de Kibana recién creado:
Opción 1: A través de la IU de Kibana:
- Abre el menú de navegación principal y ve a Seguridad > Casos (o navega directamente a
https://${KIBANA_IP}:5601/app/security/cases). - Haz clic en Intento de reconocimiento y explotación maliciosos desde 30.156.16.164 para ver el resumen del incidente en formato Markdown, el análisis de MITRE ATT&CK, las URLs maliciosas de muestra y la lista de tareas para la corrección.
- Abre el menú de navegación principal y ve a Seguridad > Casos (o navega directamente a
Opción 2: Con la CLI
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${KIBANA_IP}:5601/api/cases/_find" \ -H "kbn-xsrf: true" \ | jq '.cases[0] | {id: .id, title: .title, severity: .severity, status: .status, tags: .tags, description: .description}'El resultado debería ser similar al siguiente:
{ "id": "1f0e00a5-cc89-4f62-b305-f286fae3d70e", "title": "Malicious Reconnaissance and Exploitation Attempt from 30.156.16.164", "severity": "high", "status": "open", "tags": [ "agent-builder", "web-logs" ], "description": "### Incident Summary\n**Source IP:** `30.156.16.164`\n**Geolocation:** US\n**Total Failed Requests:** 100\n**Status Codes Observed:** 404\n\n### Analysis\nThe client IP exhibited clear patterns of malicious reconnaissance and exploitation attempts:\n\n- **Reconnaissance (MITRE ATT&CK T1595):** Extensive probing for WordPress installations (`/wp-login.php`, various plugin paths) and Nmap scan signatures (`/nmaplowercheck...`).\n- **Path Traversal (MITRE ATT&CK T1083):** Attempted to access sensitive system files via `/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd`.\n- **Remote Command Execution (MITRE ATT&CK T1203):** Attempts to execute commands via paths like `/level/15/exec/-/configure/http`.\n- **Application Probing:** Targeted common management interfaces such as `/tomcat/manager/html/`, `/axis2/axis2-admin/`, and `/jmx-console/`.\n\n### Sample Malicious URLs\n- `/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd`\n- `/level/15/exec/-/configure/http`\n- `/nmaplowercheck1331872976`\n- `/wp-login.php`\n\n### Recommended Remediation\n1. **Block IP:** Immediately block `30.156.16.164` at the network firewall/WAF.\n2. **Log Review:** Verify that all requests from this IP resulted in 404 errors and no unauthorized access was gained.\n3. **Hardening:** Ensure that sensitive directories (e.g., `/cgi-bin/`, `/manager/`) are properly restricted." }
Descubrimiento de ataques
Attack Discovery es una función de IA de Elastic Security que analiza las alertas de seguridad abiertas, las asigna al marco de MITRE ATT&CK y correlaciona las alertas, las direcciones IP y las cuentas de usuario relacionadas en cadenas de ataque visuales. Dado que la detección de ataques requiere un modelo grande para sintetizar de forma confiable resultados JSON estructurados con varias alertas, usa un modelo de clase 30B o más grande (como gemma4:31b).
Genera alertas de seguridad a partir de los registros de muestra
De forma predeterminada, el conjunto de datos de kibana_sample_data_logs contiene registros de acceso web sin procesar y no completa el índice de alertas de Elastic Security (.alerts-security.alerts-default). Además, el generador de datos de muestra de Kibana asigna valores de @timestamp futuros (hasta 30 días antes) a las 100 solicitudes de análisis y explotación de 30.156.16.164, lo que las coloca fuera del período de now-30d a now que consultan las reglas de detección de Elastic Security.
Para demostrar el Descubrimiento de ataques con los registros de muestra, desplaza 30 días hacia atrás el @timestamp de los registros de 30.156.16.164, enriquécelos con los campos source.ip y user.name del esquema común de Elastic (ECS) y crea dos reglas de detección que generen alertas de seguridad a partir de esos eventos:
Asigna
clientipasource.ip, estableceuser.name(www-data) y desplaza@timestamp30 días hacia atrás en los registros de30.156.16.164enkibana_sample_data_logs*:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${ELASTICSEARCH_IP}:9200/kibana_sample_data_logs*/_update_by_query?conflicts=proceed&refresh=true" \ -H "Content-Type: application/json" \ -d '{ "query": { "term": { "clientip": "30.156.16.164" } }, "script": { "source": "ctx._source.source = [\"ip\": ctx._source.clientip]; ctx._source.user = [\"name\": \"www-data\"]; ctx._source[\"@timestamp\"] = ZonedDateTime.parse(ctx._source[\"@timestamp\"]).minusDays(30).toString();", "lang": "painless" } }' | jq '{updated: .updated, failures: .failures}'Crea dos reglas de detección de Elastic Security dirigidas a
kibana_sample_data_logs*. La primera regla detecta el sondeo de inicio de sesión de Nmap y WordPress (Reconnaissance), y la segunda regla detecta intentos de recorrido de ruta de/etc/passwdy ejecución de comandos remotos (Initial Access):curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/detection_engine/rules" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "name": "Suspicious Web Reconnaissance and Vulnerability Scanning", "description": "Detects Nmap active scanning and WordPress login/plugin probing in web access logs.", "risk_score": 47, "severity": "medium", "type": "query", "language": "kuery", "query": "clientip: \"30.156.16.164\" and (request: *nmap* or request: *wp-login*)", "index": ["kibana_sample_data_logs*"], "from": "now-30d", "interval": "1h", "max_signals": 2, "enabled": true, "threat": [ { "framework": "MITRE ATT&CK", "tactic": { "id": "TA0043", "name": "Reconnaissance", "reference": "https://attack.mitre.org/tactics/TA0043/" }, "technique": [ { "id": "T1595", "name": "Active Scanning", "reference": "https://attack.mitre.org/techniques/T1595/" } ] } ] }' | jq '{id: .id, name: .name, enabled: .enabled}' curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/detection_engine/rules" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "name": "Web Path Traversal and Remote Command Execution Attempt", "description": "Detects path traversal attempts targeting /etc/passwd and Cisco IOS privilege level 15 remote command execution (/level/15/exec).", "risk_score": 73, "severity": "high", "type": "query", "language": "kuery", "query": "clientip: \"30.156.16.164\" and (request: *passwd* or request: *exec*)", "index": ["kibana_sample_data_logs*"], "from": "now-30d", "interval": "1h", "max_signals": 2, "enabled": true, "threat": [ { "framework": "MITRE ATT&CK", "tactic": { "id": "TA0001", "name": "Initial Access", "reference": "https://attack.mitre.org/tactics/TA0001/" }, "technique": [ { "id": "T1190", "name": "Exploit Public-Facing Application", "reference": "https://attack.mitre.org/techniques/T1190/" } ] } ] }' | jq '{id: .id, name: .name, enabled: .enabled}'Espera aproximadamente 60 segundos para que se complete la ejecución inicial de la regla y, luego, verifica que se hayan indexado 4 alertas en
.alerts-security.alerts-default:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${ELASTICSEARCH_IP}:9200/.alerts-security.alerts-default/_search?size=4" \ | jq '.hits.hits[]._source | {rule: .["kibana.alert.rule.name"], source_ip: .source.ip, user_name: .user.name, request: .request}'El resultado debería ser similar al siguiente:
{ "rule": "Web Path Traversal and Remote Command Execution Attempt", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd" } { "rule": "Web Path Traversal and Remote Command Execution Attempt", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd" } { "rule": "Suspicious Web Reconnaissance and Vulnerability Scanning", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/nmaplowercheck1331872976" } { "rule": "Suspicious Web Reconnaissance and Vulnerability Scanning", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/nmaplowercheck1331873003" }
Ejecuta el descubrimiento de ataques
- En Kibana, abre el menú de navegación principal y ve a Seguridad >
Detección de ataques (o navega directamente a
https://${KIBANA_IP}:5601/app/security/attack_discovery). - Selecciona tu conector de LLM (
${LLM_CONNECTOR_NAME}) en el menú de conectores que se encuentra en la parte superior de la página. - Haz clic en Generar para analizar las alertas abiertas en
.alerts-security.alerts-default.
También puedes activar e inspeccionar Attack Discovery desde la CLI:
export EXECUTION_UUID=$(curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
-X POST "https://${KIBANA_IP}:5601/api/attack_discovery/_generate" \
-H "kbn-xsrf: true" \
-H "elastic-api-version: 2023-10-31" \
-H "Content-Type: application/json" \
-d "{
\"alertsIndexPattern\": \".alerts-security.alerts-default\",
\"anonymizationFields\": [
{\"id\": \"_id\", \"field\": \"_id\", \"allowed\": true},
{\"id\": \"@timestamp\", \"field\": \"@timestamp\", \"allowed\": true},
{\"id\": \"kibana.alert.original_time\", \"field\": \"kibana.alert.original_time\", \"allowed\": true},
{\"id\": \"message\", \"field\": \"message\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.name\", \"field\": \"kibana.alert.rule.name\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.description\", \"field\": \"kibana.alert.rule.description\", \"allowed\": true},
{\"id\": \"kibana.alert.severity\", \"field\": \"kibana.alert.severity\", \"allowed\": true},
{\"id\": \"kibana.alert.risk_score\", \"field\": \"kibana.alert.risk_score\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.tactic.id\", \"field\": \"kibana.alert.rule.threat.tactic.id\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.tactic.name\", \"field\": \"kibana.alert.rule.threat.tactic.name\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.technique.id\", \"field\": \"kibana.alert.rule.threat.technique.id\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.technique.name\", \"field\": \"kibana.alert.rule.threat.technique.name\", \"allowed\": true},
{\"id\": \"source.ip\", \"field\": \"source.ip\", \"allowed\": true},
{\"id\": \"user.name\", \"field\": \"user.name\", \"allowed\": true}
],
\"apiConfig\": {
\"actionTypeId\": \".gen-ai\",
\"connectorId\": \"${CONNECTOR_ID}\"
},
\"replacements\": {},
\"size\": 20,
\"subAction\": \"invokeAI\"
}" | jq -r '.execution_uuid')
echo "Attack Discovery execution UUID: ${EXECUTION_UUID}"
Una vez que finalice la generación (aproximadamente entre 60 y 90 segundos), recupera la cadena de ataque descubierta:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${KIBANA_IP}:5601/api/attack_discovery/_find" \
-H "kbn-xsrf: true" \
-H "elastic-api-version: 2023-10-31" \
| jq '.data[0]'
El descubrimiento de ataque generado correlaciona las cuatro alertas en un solo incidente con la IP de origen, la cuenta de usuario y las tácticas de MITRE ATT&CK vinculadas:
{
"alert_ids": [
"05c30118398ea2a8b70c55ce81fdb559dbe14c460a8a95253bd81719415a28b5",
"73ebaeca440b463ffa5812da5b68a2485d465a648ffaf16385a54d8bae9a53d2",
"60ae9f16b40cec1bdc27674ade0e5eb69680ccca3d956fbf2a75418843799987",
"de503ae52444db7712d062c54750f413b90a75521cdfff0de9bdf41a64141346"
],
"alert_rule_uuid": "attack_discovery_ad_hoc_rule_id",
"alert_start": "2026-10-03T01:05:00.384Z",
"alert_updated_at": "2026-10-03T01:05:03.460Z",
"alert_workflow_status": "open",
"alert_workflow_status_updated_at": "2026-10-03T01:05:03.460Z",
"connector_id": "<GENERATED_CONNECTOR_ID>",
"connector_name": "<GENERATED_CONNECTOR_ID>",
"details_markdown": "- The attack sequence began at `2026-10-01T12:43:41.756Z` when an actor using IP `30.156.16.164` performed active reconnaissance scanning against the server.\n- Following the initial scan, the attacker attempted to exploit a path traversal vulnerability at `2026-10-01T12:43:51.756Z` and again at `2026-10-01T12:44:02.756Z`, specifically targeting the `/etc/passwd` file via the `mj_wwwusr` CGI endpoint.\n- The activity concluded with further reconnaissance scanning at `2026-10-01T12:44:21.756Z` from the same source IP `30.156.16.164`.\n- All requests were associated with the user `www-data` and resulted in 404 errors, suggesting the attempts were unsuccessful.",
"entity_summary_markdown": "Activity targeting user `www-data` from source IP `30.156.16.164`.",
"generation_uuid": "<EXECUTION_UUID>",
"id": "1f71ed004c7340d5088d242ca2483525a164a9ea9a80a3f90bd1f514d43231bc",
"mitre_attack_tactics": [
"Reconnaissance",
"Initial Access"
],
"risk_score": 240,
"summary_markdown": "An attacker from `30.156.16.164` performed reconnaissance and path traversal attempts against the system.",
"timestamp": "2026-10-03T01:05:00.384Z",
"title": "Web Reconnaissance and Exploitation Attempt",
"user_id": "elastic",
"user_name": "elastic",
"users": [
{
"id": "elastic",
"name": "elastic"
}
],
"index": ".internal.adhoc.alerts-security.attack.discovery.alerts-default-000001"
}