Présentation
Ce guide décrit le déploiement de bout en bout d'Elastic Stack (Elasticsearch et Kibana) sur un cluster partagé isolé de Google Distributed Cloud (GDC) et explique comment l'intégrer à un service de grand modèle de langage (LLM) pour évaluer les fonctionnalités d'IA générative d'Elastic.
Le déploiement utilise l'opérateur Elastic Cloud sur Kubernetes (ECK) officiel disponible sur la place de marché GDC sous air gap. L'opérateur s'installe dans un espace de noms de projet sur un cluster Kubernetes partagé, et automatise le provisionnement et la gestion du cycle de vie des ressources Elasticsearch et Kibana. Une fois déployé, Kibana se connecte à un point de terminaison d'inférence LLM (comme Gemini géré sur GDC ou un service compatible avec OpenAI autogéré comme Ollama ou vLLM). Cette intégration alimente les capacités d'IA générative d'Elastic, y compris l'agent IA Elastic, les agents personnalisés créés avec Agent Builder et Attack Discovery.
Ce guide a été rédigé pour Elastic v9.4.0, l'opérateur Elastic Kubernetes v3.4.0 et GDC v1.16.2. Certaines instructions peuvent différer légèrement si vous utilisez des versions différentes.
Architecture
L'architecture se compose d'un déploiement Elastic Stack géré par l'opérateur ECK dans un espace de noms de projet sur un cluster d'utilisateur partagé GDC. Kibana communique sur le réseau GDC interne avec un point de terminaison d'inférence LLM.

Voici les principaux composants de cette architecture :
- Cluster partagé GDC : cluster Kubernetes partagé au niveau de l'organisation, associé à plusieurs projets, hébergeant des packages logiciels et des charges de travail d'application GDC Marketplace.
- Projet GDC : espace de noms du projet sur le cluster partagé qui isole l'opérateur ECK, le cluster Elasticsearch, l'instance Kibana, ainsi que les secrets et les NetworkPolicies Kubernetes associés.
- Opérateur Elastic Cloud sur Kubernetes (ECK) : installé depuis GDC Marketplace dans l'espace de noms du projet, l'opérateur gère les ressources personnalisées
ElasticsearchetKibana, et rapproche la licence Elastic requise pour les fonctionnalités d'IA générative et les connecteurs LLM externes. - Elasticsearch : stocke et indexe les données opérationnelles et de sécurité, et exécute les requêtes de recherche et ES|QL générées par les fonctionnalités d'IA et les agents de Kibana.
- Kibana : héberge les fonctionnalités d'IA générative d'Elastic, y compris l'agent IA Elastic (pour l'investigation des journaux en langage naturel et la génération ES|QL), Agent Builder (pour créer des agents conversationnels et des outils personnalisés sur les données Elasticsearch) et Attack Discovery (pour la corrélation des alertes de sécurité et l'analyse de la chaîne d'attaque). Kibana gère également les connecteurs d'action LLM qui orchestrent l'appel d'outils en plusieurs étapes entre l'utilisateur, Elasticsearch et le service LLM.
- Service LLM : service LLM géré (tel que Gemini sur GDC) ou serveur d'inférence autogéré (tel qu'Ollama ou vLLM) qui fournit des fonctionnalités de complétion de chat et d'appel d'outils à Kibana sur le réseau GDC interne.
Avant de commencer
Avant de commencer le déploiement, assurez-vous que votre environnement GDC remplit les conditions préalables et les hypothèses suivantes :
- Cluster Kubernetes partagé et projet GDC : vous avez accès à un cluster partagé GDC existant et à un projet GDC associé à ce cluster. Le projet sert de conteneur d'espace de noms pour toutes les ressources Kubernetes déployées dans ce guide. Assurez-vous que le cluster dispose de suffisamment de ressources de calcul et de stockage persistant pour héberger l'opérateur ECK, Elasticsearch et les charges de travail Kibana.
- Service LLM : vous disposez d'un accès réseau à un service LLM, qu'il s'agisse du service Gemini géré (s'il est disponible dans votre environnement GDC) ou d'un service LLM autogéré compatible avec OpenAI (par exemple,
gemma4:31bdiffusé via Ollama ou vLLM dans un projet GPU). Le déploiement de l'infrastructure LLM elle-même n'entre pas dans le cadre de ce guide, mais les exigences spécifiques du modèle et de l'API sont détaillées dans la section Exigences pour le service LLM. Récupérez la version d'Elastic fournie avec votre environnement 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')Configurez les variables d'environnement qui seront référencées tout au long de ce guide. Modifiez ces valeurs selon vos besoins pour qu'elles correspondent à votre environnement spécifique :
# 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"Configurez
gdcloud:gdcloud config set project ${PROJECT_ID} gdcloud config set core/zone ${ZONE}Demandez ou associez les rôles IAM requis au niveau du projet pour votre utilisateur dans le projet GDC cible (
${PROJECT_ID}). D'après la documentation sur le cycle de vie de GDC Marketplace et les exigences de mise en réseau de GDC, au moins les rôles de projet suivants sont requis :- Administrateur de l'espace de noms (
namespace-admin) : accès administratif complet pour créer et gérer des charges de travail, des secrets, des services et des ressources personnalisées Kubernetes dans l'espace de noms du projet sur le cluster partagé. - Lecteur de projet (
project-viewer) : accès en lecture pour afficher le projet existant et ses ressources dans la console et la CLI GDC. - Éditeur Marketplace (
marketplace-editor) : autorisation d'installer, de mettre à niveau et de désinstaller des packages logiciels GDC Marketplace dans le projet. - Administrateur NetworkPolicy du projet (
project-networkpolicy-admin) : autorisation de configurer des règlesProjectNetworkPolicyglobales permettant au trafic interprojets et externe d'atteindre les services LoadBalancer Kibana et Elasticsearch.
Demandez à l'administrateur IAM de votre organisation ou de votre projet de vous accorder ces rôles (ou exécutez les commandes suivantes si vous disposez de droits d'administrateur 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- Administrateur de l'espace de noms (
Récupérez le fichier kubeconfig du cluster partagé et configurez un alias d'interface système
kupourkubectl:KUBECONFIG=kubeconfig-${CLUSTER_NAME}.yaml gdcloud clusters \ get-credentials ${CLUSTER_NAME} \ --zone ${ZONE} alias ku="kubectl --kubeconfig=kubeconfig-${CLUSTER_NAME}.yaml"
Installer le package de place de marché Elastic
Dans cette section, vous allez installer l'opérateur Elastic Cloud sur Kubernetes (ECK) dans l'espace de noms de votre projet à partir de la place de marché GDC sous air gap en suivant le guide du cycle de vie de la place de marché GDC.
Installer via la CLI gdcloud (ou la console GDC)
Listez les packages logiciels Marketplace disponibles pour confirmer le nom du service et la version disponible :
gdcloud marketplace services listLe résultat doit ressembler à ce qui suit :
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>Installez le package Elastic Marketplace sur votre cluster partagé cible :
gdcloud marketplace services install ${MARKETPLACE_SERVICE_NAME} \ --cluster=${CLUSTER_NAME} \ --zone=${ZONE} \ --project ${PROJECT_ID}*(Vous pouvez également sélectionner votre projet dans la console GDC
${PROJECT_ID}, accéder à Marketplace, sélectionner l'annonce Elastic Cloud on Kubernetes, cliquer sur Installer, sélectionner le cluster${CLUSTER_NAME}, puis cliquer sur Installer.)*Vérifiez que l'objet StatefulSet et le pod
elastic-operatorsont en cours d'exécution dans l'espace de noms de votre projet (${PROJECT_ID}) :ku get statefulset,pods -n ${PROJECT_ID}Le résultat doit ressembler à ce qui suit :
NAME READY AGE statefulset.apps/elastic-operator 1/1 3m NAME READY STATUS RESTARTS AGE pod/elastic-operator-0 1/1 Running 0 3m
Déployer la pile Elastic Stack
Créer le fichier manifeste Elastic Stack
Le package GDC Marketplace déploie l'opérateur ECK préconfiguré pour extraire les images de conteneurs Elasticsearch et Kibana du registre Harbor local sous air gap.
Créez elastic-stack.yaml pour déployer un cluster Elasticsearch à un nœud, une instance d'interface utilisateur Kibana, des services HTTP LoadBalancer et un allow-all NetworkPolicy dans l'espace de noms de votre projet (${PROJECT_ID}). Le fichier manifeste inclut également une licence Secret rapprochée par l'opérateur ECK, configurée ici en tant qu'essai Enterprise de 30 jours (eck-trial-license) si vous ne disposez pas encore de votre propre licence 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
Appliquer et vérifier le déploiement
Appliquez le fichier manifeste :
ku apply -f elastic-stack.yaml
Appliquez un ProjectNetworkPolicy global pour que les règles de pare-feu GDC autorisent le trafic entrant vers les adresses IP de l'équilibreur de charge, ainsi que le trafic interprojets (comme la communication avec le service 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
Surveillez les pods, les services et les ressources personnalisées jusqu'à ce qu'Elasticsearch et Kibana atteignent l'état Running et Ready, et obtiennent des adresses IP d'équilibreur de charge sur ${CLUSTER_NAME} :
ku get pods,svc,elasticsearch,kibana -n ${PROJECT_ID}
Une fois les ressources prêtes, le résultat devrait ressembler à ceci :
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
Exportez les adresses IP LoadBalancer externes attribuées à Elasticsearch et Kibana pour les utiliser lors des étapes suivantes :
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"
Récupérer les identifiants et accéder à l'interface utilisateur de Kibana
Récupérer le mot de passe du super-utilisateur elastic
ECK génère automatiquement un secret Kubernetes (elastic-cluster-es-elastic-user) contenant le mot de passe du super-utilisateur elastic par défaut. Récupérez-la et décodez-la avec la commande suivante :
export ELASTIC_PASSWORD=$(ku get secret elastic-cluster-es-elastic-user \
-n ${PROJECT_ID} \
-o jsonpath='{.data.elastic}' | base64 -d)
echo "${ELASTIC_PASSWORD}"
Vérifier la connectivité et se connecter
Vérifiez qu'Elasticsearch est accessible via HTTPS depuis votre poste de travail :
curl -sk -u "elastic:${ELASTIC_PASSWORD}" "https://${ELASTICSEARCH_IP}:9200"Ouvrez
https://${KIBANA_IP}:5601dans votre navigateur, acceptez l'avertissement concernant le certificat autosigné, puis connectez-vous avec les identifiants suivants :- Nom d'utilisateur :
elastic - Mot de passe :
<output from ELASTIC_PASSWORD above>
- Nom d'utilisateur :
Vérifier la licence Elastic
Les fonctionnalités d'IA générative d'Elastic (y compris l'agent IA Elastic, Agent Builder et Attack Discovery) et les connecteurs LLM externes nécessitent une licence Enterprise (ou une licence d'essai Enterprise de 30 jours active).
Lorsqu'un cluster Elasticsearch est géré par l'opérateur ECK, le license-controller d'ECK rapproche en permanence la licence du cluster des secrets Kubernetes dans l'espace de noms de l'opérateur (${PROJECT_ID}). Si vous ne disposez pas encore d'une licence Enterprise de production, le secret eck-trial-license inclus dans elastic-stack.yaml (license.k8s.elastic.co/type:
enterprise_trial) indique à ECK de provisionner et de gérer automatiquement une licence d'essai Enterprise de 30 jours. (Notez que si vous démarrez un essai manuellement via POST
/_license/start_trial sans secret de licence ECK, ECK rétablira le cluster sur basic lors du prochain cycle de rapprochement.)
Pour vérifier la licence active à l'aide de la CLI :
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${ELASTICSEARCH_IP}:9200/_license" | jq .
*(Vous pouvez également ouvrir le menu de navigation dans Kibana, accéder à Management > Dev Tools (Gestion > Outils de développement), puis exécuter GET /_license dans l'onglet "Console".)*
Le résultat doit ressembler à ce qui suit, avec "status": "active" et "type": "enterprise" (ou "type": "trial" si vous utilisez la licence d'essai) :
{
"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
}
}
Charger un exemple d'ensemble de données intégré
Pour évaluer les fonctionnalités d'IA d'Elastic sans écrire de pipelines d'ingestion de données personnalisés ni nécessiter d'accès à Internet externe, vous pouvez charger l'ensemble de données d'exemples de journaux Web intégré de Kibana (kibana_sample_data_logs). Comme l'archive de données d'exemple est intégrée à l'image de conteneur Kibana, elle s'installe en quelques secondes dans un environnement sous air gap. L'ensemble de données contient 14 074 documents de journaux d'accès Web contenant des adresses IP client (clientip), des codes d'état HTTP (response), des pays source et de destination (geo.src, geo.dest), des octets transférés (bytes), des URL et des chaînes user-agent.
Consultez Ajouter des exemples de données dans Kibana et elastic-datasets-builtin.md pour obtenir des ensembles de données intégrés supplémentaires (flights et ecommerce).
Charger l'ensemble de données
Exécutez la commande suivante pour charger kibana_sample_data_logs via le point de terminaison d'exemples de données internes 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"
Vérifier l'exemple d'ensemble de données
Vérifiez que l'exemple d'ensemble de données logs est marqué comme installed dans Kibana et que le flux de données de sauvegarde contient des documents 14,074 dans 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"
Le résultat doit ressembler à ce qui suit :
{
"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
Exigences concernant le service LLM
Dans un environnement GDC sous air gap, les LLM gérés Elastic hébergés dans le cloud et les fournisseurs de LLM SaaS publics externes sont inaccessibles. Selon votre environnement GDC :
- Gemini géré sur GDC : dans les environnements GDC qui proposent Gemini en tant que service LLM géré, vous pouvez connecter Kibana directement au service Gemini géré, s'il est disponible.
- Service LLM autogéré : vous pouvez également connecter Elastic à un service LLM autogéré hébergé dans votre environnement GDC (par exemple, sur des nœuds GPU GDC).
Le déploiement d'un service LLM autogéré n'entre pas dans le cadre de ce guide. Lorsque vous provisionnez ou sélectionnez un service LLM autogéré pour Elastic, assurez-vous qu'il répond aux exigences issues des guides officiels d'Elastic :
- Configurer l'accès aux LLM
- LLM personnalisés autogérés (voir aussi Se connecter à vLLM dans des environnements air gap)
- Observabilité des LLM et de l'IA agentique
Résumé des exigences techniques
Les points suivants résument les exigences clés concernant l'API, les capacités du modèle, la fenêtre de contexte et l'environnement d'exécution nécessaires pour qu'un service LLM autogéré fonctionne de manière fiable avec l'agent IA, Agent Builder et Attack Discovery d'Elastic. Notez que les noms de paramètres de configuration spécifiques cités dans les sections suivantes (tels que num_ctx, num_predict, repeat_penalty, repeat_last_n, OLLAMA_FLASH_ATTENTION et OLLAMA_NUM_PARALLEL) sont fournis en utilisant Ollama comme exemple. Appliquez les paramètres équivalents si vous utilisez un autre moteur d'inférence, tel que vLLM.
- API de complétion de chat compatible avec OpenAI (
/v1/chat/completions) avec appel de fonction natif :- Tout serveur d'inférence autogéré (tel qu'Ollama ou vLLM) qui expose un point de terminaison
/v1/chat/completionscompatible avec OpenAI (y compris le streaming d'événements envoyés par le serveur via"stream": true) peut être intégré à l'aide du connecteur OpenAI de Kibana, avec le fournisseur défini sur Autre (service compatible avec OpenAI). - Activez l'appel de fonction natif (
"enableNativeFunctionCalling": true) sur le connecteur Kibana lorsque vous utilisez des modèles compatibles avec l'appel d'outil cloud natif (commegemma4:31b). Par défaut ("enableNativeFunctionCalling": false), Kibana utilise l'appel de fonction simulé basé sur un prompt, qui est moins fiable lors des appels d'outils imbriqués et peut échouer avec des erreurs d'analyse (commeError: Missing name for tool use). L'activation de l'appel de fonction natif délègue la mise en forme de l'appel d'outil directement au protocole OpenAItoolsdu serveur d'inférence.
- Tout serveur d'inférence autogéré (tel qu'Ollama ou vLLM) qui expose un point de terminaison
- Modèle réglé par instructions avec des fonctionnalités d'appel de fonction et d'outil natif :
- L'agent IA Elastic (
elastic-ai-agent) et les agents Agent Builder personnalisés s'appuient sur l'appel d'outils en plusieurs étapes (platform.core.list_indices,platform.core.get_index_mapping,platform.core.search,platform.core.execute_esqletplatform.core.cases). Ils utilisent ces outils pour découvrir les mappages d'index, générer et exécuter des requêtes ES|QL sur Elasticsearch, et synthétiser les résultats. - Le modèle choisi doit être réglé par instructions et capable de générer des appels d'outils cloud natifs valides, des sorties JSON structurées et des requêtes ES|QL. Pour connaître les critères d'évaluation des modèles, consultez la matrice de performances des LLM pour l'observabilité et la matrice de performances des LLM pour Elastic Security d'Elastic.
- L'agent IA Elastic (
- Fenêtre de contexte d'au moins
128,000jetons (par exemple,num_ctx: 131072dans Ollama) :- Bien que la documentation générale d'Elastic sur les LLM personnalisés autogérés indique un minimum de 64 000 jetons, une fenêtre de contexte de 128 000 jetons (
131,072jetons) est vivement recommandée pour l'agent IA Elastic (elastic-ai-agent). - Lors d'investigations en plusieurs étapes, l'agent combine les instructions système et les définitions d'outils avec les schémas d'index, la documentation ES|QL et les résultats de recherche bruts (par exemple, en récupérant jusqu'à 100 exemples de documents pour inspecter les valeurs des champs avant l'agrégation). Une seule investigation peut dépasser 70 000 jetons dans l'historique de la conversation, ce qui entraîne la troncature de la requête en milieu de conversation par une fenêtre de contexte de 64 000 jetons.
- Notez que dans Ollama, une directive
PARAMETER num_ctxintégrée auModelfiled'un modèle remplace la variable d'environnementOLLAMA_CONTEXT_LENGTHdu conteneur. Assurez-vous queOLLAMA_CONTEXT_LENGTH=131072etPARAMETER num_ctx 131072sont configurés dans leModelfiledu modèle (sur un GPU NVIDIA A100 ou H100 de 80 Go,gemma4:31bà un contexte de128Ktient confortablement dans la VRAM).
- Bien que la documentation générale d'Elastic sur les LLM personnalisés autogérés indique un minimum de 64 000 jetons, une fenêtre de contexte de 128 000 jetons (
- Limite de jetons de sortie et pénalité de répétition légère (par exemple,
num_predict: 8192etrepeat_penalty: 1.05dans Ollama) :- L'adaptateur de flux OpenAI de Kibana traite
"finish_reason": "length"comme une erreurToken limit reachedfatale et envoie"temperature": 0(décodage gourmand) dans chaque requête. - Pour les modèles capables de raisonner (comme
gemma4:31b, qui génère des jetons de réflexion internes avant d'émettre des appels d'outils), configurez une limite de jetons de sortie d'au moins8,192jetons et une pénalité de répétition légère. Par exemple, dans Ollama, définisseznum_predict: 8192,repeat_penalty: 1.05etrepeat_last_n: 64. Évitez les pénalités de répétition trop agressives (comme1.15+sur de grandes fenêtres), car elles pénalisent le modèle lors de la copie d'une requête ES|QL de son bloc de raisonnement interne vers le JSON d'appel d'outil final.
- L'adaptateur de flux OpenAI de Kibana traite
- Accélération GPU, Flash Attention et emplacements de cache KV parallèles (< 60 s par tour) :
- Le connecteur OpenAI de Kibana applique un délai avant expiration HTTP par défaut de 60 secondes par tour de streaming.
- Étant donné qu'un seul tour d'agent peut déclencher des sous-appels LLM imbriqués (par exemple, pour traduire des requêtes de recherche en langage naturel en requêtes ES|QL pendant que la conversation de l'agent principal est active), l'exécution d'un serveur d'inférence avec un seul emplacement de cache KV peut entraîner l'éviction du contexte mis en cache de la conversation principale par les sous-appels et ralentir les tours suivants.
- Dans Ollama, définissez
OLLAMA_FLASH_ATTENTION=1etOLLAMA_NUM_PARALLEL=2pour que Flash Attention accélère l'évaluation des contextes longs, et que la conversation principale et les sous-appels imbriqués conservent des emplacements de cache KV distincts dans la mémoire du GPU.
Configurer le connecteur LLM dans Kibana
Une fois que votre service LLM compatible avec OpenAI est en cours d'exécution et accessible depuis le pod Kibana, configurez un connecteur OpenAI dans Kibana à l'aide de la CLI ou de l'interface utilisateur de Kibana.
Option 1 : Créer le connecteur via la CLI
Exécutez la commande
POST /api/actions/connectorsuivante pour créer un connecteur OpenAI (.gen-ai) pointant vers votre service LLM (${LLM_API_URL}) avec l'appel de fonction natif activé ("enableNativeFunctionCalling": true). Kibana génère et attribue automatiquement un ID de connecteur unique :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 .Vérifiez que le connecteur
${LLM_CONNECTOR_NAME}existe et qu'aucun secret n'est manquant en listant les connecteurs de Kibana et en filtrant par nom :curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${KIBANA_IP}:5601/api/actions/connectors" \ -H "kbn-xsrf: true" \ | jq ".[] | select(.name == \"${LLM_CONNECTOR_NAME}\")"Le résultat doit ressembler à ce qui suit :
{ "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 }Pour tester la connectivité de bout en bout entre Kibana et le service LLM à partir de la CLI, recherchez l'ID du connecteur généré automatiquement par son nom et appelez son point de terminaison
_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 .Le résultat doit ressembler à ce qui suit, avec
"status": "ok"et la réponse du modèle :{ "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>" }
Option 2 : Créer le connecteur via l'interface utilisateur de Kibana
- Connectez-vous à Kibana (
https://${KIBANA_IP}:5601). - Ouvrez le menu de navigation principal, puis accédez à Gestion > Alertes et insights > Connecteurs.
- Cliquez sur Créer un connecteur, puis sélectionnez OpenAI.
- Configurez les champs du connecteur :
- Nom du connecteur : saisissez la valeur de
${LLM_CONNECTOR_NAME}(par exemple,Gemma4-31B). - Sélectionner un fournisseur OpenAI : sélectionnez Autre (service compatible avec OpenAI).
- URL : saisissez la valeur de
${LLM_API_URL}(le point de terminaison/v1/chat/completionsde votre service LLM). - Modèle par défaut : saisissez la valeur de
${LLM_MODEL_NAME}(par exemple,gemma4:31b). - Clé API : saisissez la valeur de
${LLM_API_KEY}(toute chaîne non vide si votre serveur n'applique pas l'authentification du porteur, ou la clé API de votre serveur). - Activer l'appel de fonction natif : activez le bouton.
- Nom du connecteur : saisissez la valeur de
- Cliquez sur Enregistrer et tester pour enregistrer le connecteur et ouvrir immédiatement l'onglet Tester.
- Dans l'onglet Test, saisissez un exemple de prompt (par exemple,
Reply with the single word Pong.) et cliquez sur Run (Exécuter) pour vérifier que Kibana reçoit une réponse valide du service LLM.
Tester les intégrations de LLM
Une fois l'ensemble de données kibana_sample_data_logs installé et votre connecteur LLM validé, vous pouvez tester les fonctionnalités d'IA d'Elastic dans Kibana.
Agent IA Elastic
L'agent IA Elastic (elastic-ai-agent) est l'agent conversationnel par défaut intégré à Kibana pour explorer et analyser les données dans Elasticsearch à l'aide du langage naturel.
Grâce au connecteur LLM que vous avez configuré et aux outils Agent Builder intégrés, l'agent découvre automatiquement les index et les mappages Elasticsearch (platform.core.list_indices, platform.core.get_index_mapping), génère et exécute des requêtes ES|QL (platform.core.search, platform.core.execute_esql), et synthétise les résultats sans que vous ayez besoin de connaître le schéma sous-jacent à l'avance. Vous pouvez également créer vos propres agents personnalisés avec des instructions et des outils adaptés. Pour en savoir plus, consultez Agents personnalisés avec Agent Builder.
Pour ouvrir et configurer l'agent IA Elastic dans l'interface utilisateur de Kibana :
- Ouvrez le menu de navigation principal dans Kibana et sélectionnez Agent IA (ou cliquez sur le bouton Demander à l'IA dans la barre d'en-tête supérieure, ou accédez directement à
https://${KIBANA_IP}:5601/app/chat). - Assurez-vous qu'Elastic agent IA est sélectionné comme agent actif et que votre connecteur LLM (
${LLM_CONNECTOR_NAME}) est sélectionné comme connecteur de modèle.
Exemple 1 : Examiner les requêtes HTTP ayant échoué et les scanners potentiels
Demandez à l'agent IA Elastic d'identifier les principales adresses IP client générant des réponses HTTP autres que 200 dans kibana_sample_data_logs, leurs pays d'origine et si l'une de ces adresses IP a également généré du trafic HTTP 200 réussi :
Option 1 : Via l'UI Kibana : saisissez la question suivante dans le prompt de chat de l'agent 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?Option 2 : via 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 }'
Que se passe-t-il en arrière-plan ?
- L'agent appelle
platform.core.get_index_mapping(indices=["kibana_sample_data_logs"])pour inspecter les champs disponibles (clientip,geo.src,response,bytes,request, etc.). Il appelle
platform.core.searchpour inspecter des exemples de documents et confirmer que les codes d'état HTTP sont stockés dansresponse, puis exécute des agrégations ES|QL pour classer les requêtes non-200parclientipetgeo.src, et vérifier lesquelles des principales adresses IP ont également enregistré du traficresponse == "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 réponse de l'agent IA Elastic doit ressembler à ceci :
L'adresse IP client avec le plus de requêtes HTTP ayant échoué (codes d'état autres que 200) dans
kibana_sample_data_logsest 30.156.16.164, qui provient des États-Unis et a enregistré 100 requêtes ayant échoué.Voici d'autres adresses IP principales ayant généré des requêtes ayant échoué, toutes provenant également des États-Unis :
- 213.235.30.99 : 6 échecs
- 221.241.228.46 : 6 échecs
- 209.94.234.133 : 6 échecs
- 7.127.73.138 : 5 échecs
- 247.240.202.244 : 5 échecs
- 130.125.65.24 : 5 échecs
- 164.85.94.243 : 4 échecs
- 81.194.200.150 : 4 échecs
- 30.145.143.210 : 4 échecs
En ce qui concerne les requêtes réussies, toutes les adresses IP listées ci-dessus ont reçu au moins quelques réponses HTTP 200 réussies, à l'exception de 30.156.16.164, qui n'a reçu aucune requête réussie.
Exemple 2 : Analyser le volume de trafic et les octets de charge utile par code d'état HTTP
Demandez à l'agent IA Elastic la répartition des codes d'état de réponse HTTP pour toutes les requêtes dans kibana_sample_data_logs et le nombre total d'octets transférés pour chaque code d'état :
Option 1 : Via l'UI Kibana : démarrez une nouvelle conversation dans l'agent IA et saisissez la question suivante :
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?Option 2 : via 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 }'
Que se passe-t-il en arrière-plan ?
- L'agent inspecte le schéma
kibana_sample_data_logsviaplatform.core.get_index_mapping, en identifiant les champsresponse.keywordetbytes. Il appelle
platform.core.search, qui génère et exécute l'agrégation ES|QL suivante :FROM kibana_sample_data_logs | STATS request_count = COUNT(*), total_bytes = SUM(bytes) BY response.keyword | LIMIT 100
La réponse de l'agent IA Elastic doit ressembler à ceci :
La répartition des codes d'état de réponse HTTP et le total des données transférées dans
kibana_sample_data_logssont les suivants :
| Code d'état | Nombre de requêtes | Total des données transférées (octets) |
|---|---|---|
| 200 | 12 832 | 75 681 246 |
| 404 | 801 | 4 044 443 |
| 503 | 441 | 0 |
Agents personnalisés avec Agent Builder
Agent Builder vous permet de créer des agents IA personnalisés adaptés à des workflows opérationnels spécifiques en combinant des instructions système personnalisées avec des outils intégrés (comme platform.core.cases, platform.core.execute_esql et platform.core.search) et des outils définis par l'utilisateur (esql, index_search, workflow et mcp).
L'exemple suivant crée un agent Web Security Incident Responder qui opère directement sur l'ensemble de données kibana_sample_data_logs. À partir d'une requête en langage naturel, l'agent :
- Exécute un outil ES|QL personnalisé (
investigate_suspicious_web_clients) pour agréger les réponses HTTP non-200danskibana_sample_data_logspar adresse IP du client. - Distingue les activités de reconnaissance et d'exploitation malveillantes (
30.156.16.164) des échecs de téléchargement bénins. - Appelle un outil de workflow Kibana personnalisé (
create_security_case) pour ouvrir automatiquement un cas de sécurité Kibana structuré avec ses résultats et ses étapes de correction.
Créer des outils personnalisés de création de requêtes ES|QL et de cas
Créez un outil
esqlpersonnalisé nomméinvestigate_suspicious_web_clientsqui interrogekibana_sample_data_logspour obtenir les principales adresses IP client générant des codes d'état HTTP autres que200, ainsi que leurs codes d'état, leur pays d'origine (geo.src), leurs user-agents (agent) et les URL demandées (request) :Option 1 : Via l'interface utilisateur de Kibana
- Dans Kibana, ouvrez le menu de navigation principal et accédez à Elasticsearch > Agents, puis cliquez sur Gérer tous les outils (ou accédez directement à
https://${KIBANA_IP}:5601/app/agent_builder/tools) et cliquez sur Nouvel outil. Configurez les champs de l'outil :
- ID de l'outil :
investigate_suspicious_web_clients - Type : ES|QL
- 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. - Étiquettes :
security,logs Requête 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 l'outil :
Cliquez sur Enregistrer.
- Dans Kibana, ouvrez le menu de navigation principal et accédez à Elasticsearch > Agents, puis cliquez sur Gérer tous les outils (ou accédez directement à
Option 2 : via 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}'
Étant donné que l'outil
platform.core.casesintégré d'Agent Builder est en lecture seule (utilisé pour rechercher et récupérer des demandes existantes), créez un workflow Kibana (create-security-case) qui appelle l'étapecases.createCasenative de Kibana, puis enregistrez-le en tant qu'outilworkflownommécreate_security_case:Option 1 : Via l'interface utilisateur de Kibana
- Ouvrez le menu de navigation principal, accédez à Gestion > Workflows (ou accédez directement à
https://${KIBANA_IP}:5601/app/workflows), puis cliquez sur Créer un workflow. Dans l'éditeur YAML, collez la définition de workflow suivante, puis cliquez sur Enregistrer :
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-logsRevenez à la page Outils d'Agent Builder (
https://${KIBANA_IP}:5601/app/agent_builder/toolsou accédez à Elasticsearch > Agents, puis cliquez sur Gérer tous les outils) et cliquez sur Nouvel outil.Configurez les champs de l'outil de workflow, puis cliquez sur Enregistrer :
- ID de l'outil :
create_security_case - Type : Workflow
- Workflow : sélectionnez Créer une demande de sécurité (
create-security-case). - Description :
Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical). - Étiquettes :
security,cases
- ID de l'outil :
- Ouvrez le menu de navigation principal, accédez à Gestion > Workflows (ou accédez directement à
Option 2 : via 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}'
Créer l'agent personnalisé de gestion des incidents
Créez l'agent web-security-incident-responder et attribuez-lui les outils investigate_suspicious_web_clients, create_security_case et platform.core.cases :
Option 1 : Via l'interface utilisateur Kibana
- Ouvrez le menu de navigation principal, puis accédez à Elasticsearch > Agents (ou accédez directement à
https://${KIBANA_IP}:5601/app/agent_builder/agents), puis cliquez sur New agent (Nouvel agent). Configurez les détails de l'agent :
- ID de l'agent :
web-security-incident-responder - Display name (Nom à afficher) :
Web Security Incident Responder - Description à afficher :
Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings. - Libellés :
security,incident-response Instructions personnalisées :
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.Outils : sélectionnez
investigate_suspicious_web_clients,create_security_caseetplatform.core.cases.
- ID de l'agent :
Cliquez sur Enregistrer.
- Ouvrez le menu de navigation principal, puis accédez à Elasticsearch > Agents (ou accédez directement à
Option 2 : via 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}'
Exécuter l'agent et vérifier le cas Kibana
Lancez une conversation avec l'agent Web Security Incident Responder :
Option 1 : Via l'interface utilisateur Kibana
- Ouvrez le menu de navigation principal, puis accédez à Elasticsearch > Agents (ou accédez directement à
https://${KIBANA_IP}:5601/app/agent_builder). - Sélectionnez Web Security Incident Responder (Répondeur aux incidents de sécurité Web) dans le sélecteur d'agents et assurez-vous que votre connecteur LLM (
${LLM_CONNECTOR_NAME}) est sélectionné. Saisissez le prompt suivant :
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.
- Ouvrez le menu de navigation principal, puis accédez à Elasticsearch > Agents (ou accédez directement à
Option 2 : via 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 }'Le résultat doit ressembler à ce qui suit :
{ "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." }
Vérifiez le cas de sécurité Kibana que vous venez de créer :
Option 1 : Via l'interface utilisateur de Kibana
- Ouvrez le menu de navigation principal, puis accédez à Sécurité > Demandes (ou accédez directement à
https://${KIBANA_IP}:5601/app/security/cases). - Cliquez sur Malicious Reconnaissance and Exploitation Attempt from 30.156.16.164 (Tentative de reconnaissance et d'exploitation malveillante depuis l'adresse 30.156.16.164) pour afficher le résumé de l'incident au format Markdown, l'analyse MITRE ATT&CK, des exemples d'URL malveillantes et la checklist de correction.
- Ouvrez le menu de navigation principal, puis accédez à Sécurité > Demandes (ou accédez directement à
Option 2 : via 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}'Le résultat doit ressembler à ce qui suit :
{ "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." }
Détection des attaques
Attack Discovery est une fonctionnalité d'IA d'Elastic Security qui analyse les alertes de sécurité ouvertes, les met en correspondance avec le framework MITRE ATT&CK et met en corrélation les alertes, les adresses IP et les comptes utilisateur associés dans des chaînes d'attaque visuelles. Étant donné qu'Attack Discovery nécessite un modèle volumineux pour synthétiser de manière fiable des sorties JSON structurées multi-alertes, utilisez un modèle de classe 30B ou plus (tel que gemma4:31b).
Générer des alertes de sécurité à partir des journaux d'exemple
Par défaut, l'ensemble de données kibana_sample_data_logs contient des journaux d'accès Web bruts et ne remplit pas l'index des alertes Elastic Security (.alerts-security.alerts-default). De plus, le générateur d'exemples de données de Kibana attribue des valeurs @timestamp futures (jusqu'à 30 jours à l'avance) aux 100 requêtes d'analyse et d'exploitation de 30.156.16.164, ce qui les place en dehors de la fenêtre now-30d à now interrogée par les règles de détection Elastic Security.
Pour illustrer la découverte d'attaques avec les journaux exemples, décalons les @timestamp des enregistrements de 30.156.16.164 de 30 jours, enrichissons-les avec les champs source.ip et user.name d'Elastic Common Schema (ECS), et créons deux règles de détection qui génèrent des alertes de sécurité à partir de ces événements :
Mappez
clientipsursource.ip, définissezuser.name(www-data) et décalez@timestampde 30 jours en arrière sur les enregistrements30.156.16.164danskibana_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}'Créez deux règles de détection Elastic Security ciblant
kibana_sample_data_logs*. La première règle détecte les tentatives de connexion Nmap et WordPress (Reconnaissance), et la seconde détecte les tentatives de traversée de chemin d'accès et d'exécution de commandes à distance (Initial Access) :/etc/passwdcurl -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}'Attendez environ 60 secondes que l'exécution initiale de la règle soit terminée, puis vérifiez que quatre alertes ont été indexées dans
.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}'Le résultat doit ressembler à ce qui suit :
{ "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" }
Exécuter Attack Discovery
- Dans Kibana, ouvrez le menu de navigation principal et accédez à Sécurité > Détection des attaques (ou accédez directement à
https://${KIBANA_IP}:5601/app/security/attack_discovery). - Sélectionnez votre connecteur LLM (
${LLM_CONNECTOR_NAME}) dans le menu des connecteurs en haut de la page. - Cliquez sur Générer pour analyser les alertes ouvertes dans
.alerts-security.alerts-default.
Vous pouvez également déclencher et inspecter la découverte d'attaques à partir de 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}"
Une fois la génération terminée (environ 60 à 90 secondes), récupérez la chaîne d'attaque découverte :
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]'
La découverte d'attaque générée met en corrélation les quatre alertes en un seul incident avec l'adresse IP source, le compte utilisateur et les tactiques MITRE ATT&CK associés :
{
"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"
}