Deployment delle funzionalità di AI generativa di Elastic Stack su GDC con air gap

Panoramica

Questa guida illustra il deployment end-to-end di Elastic Stack (Elasticsearch e Kibana) su un cluster condiviso Google Distributed Cloud (GDC) con air gap e mostra come integrarlo con un servizio di modello linguistico di grandi dimensioni (LLM) per valutare le funzionalità di AI generativa di Elastic.

Il deployment utilizza l'operatore Elastic Cloud on Kubernetes (ECK) ufficiale disponibile nel Marketplace GDC con air gap. L'operatore si installa in uno spazio dei nomi del progetto su un cluster Kubernetes condiviso e automatizza il provisioning e la gestione del ciclo di vita delle risorse Elasticsearch e Kibana. Una volta eseguito il deployment, Kibana si connette a un endpoint di inferenza LLM (come Gemini gestito su GDC o un servizio compatibile con OpenAI autogestito come Ollama o vLLM). Questa integrazione supporta le funzionalità di AI generativa di Elastic, tra cui Elastic AI Agent, gli agenti personalizzati creati con Agent Builder e Attack Discovery.

Questa guida è stata scritta per Elastic v9.4.0, Elastic Kubernetes Operator v3.4.0 e GDC v1.16.2. Alcune istruzioni potrebbero variare leggermente se utilizzi versioni diverse.

Architettura

L'architettura è costituita da un deployment di Elastic Stack gestito dall'operatore ECK all'interno di uno spazio dei nomi del progetto su un cluster utente condiviso di GDC. Kibana comunica sulla rete GDC interna con un endpoint di inferenza LLM.

Diagramma dell'architettura di Elastic Stack.

I componenti chiave di questa architettura includono:

  • Cluster GDC condiviso: un cluster Kubernetes condiviso con ambito a livello di organizzazione collegato a più progetti, che ospita pacchetti software e workload delle applicazioni di Marketplace GDC.
  • Progetto GDC: lo spazio dei nomi del progetto nel cluster condiviso che isola l'operatore ECK, il cluster Elasticsearch, l'istanza Kibana e i secret e i criteri di rete Kubernetes associati.
  • Operatore Elastic Cloud on Kubernetes (ECK): installato dal Marketplace GDC nello spazio dei nomi del progetto, l'operatore gestisce le risorse personalizzate Elasticsearch e Kibana e riconcilia la licenza Elastic richiesta per le funzionalità di AI generativa e i connettori LLM esterni.
  • Elasticsearch: archivia e indicizza i dati operativi e di sicurezza ed esegue query di ricerca ed ES|QL generate dagli agenti e dalle funzionalità di AI di Kibana.
  • Kibana: ospita le funzionalità di AI generativa di Elastic, tra cui Elastic AI Agent (per l'analisi dei log in linguaggio naturale e la generazione di ES|QL), Agent Builder (per la creazione di agenti e strumenti conversazionali personalizzati sui dati di Elasticsearch) e Attack Discovery (per la correlazione degli avvisi di sicurezza e l'analisi della catena di attacco). Kibana gestisce anche i connettori di azioni LLM che orchestrano la chiamata di strumenti in più passaggi tra l'utente, Elasticsearch e il servizio LLM.
  • Servizio LLM: un servizio LLM gestito (come Gemini su GDC) o un server di inferenza autogestito (come Ollama o vLLM) che fornisce funzionalità di completamento della chat e chiamata di strumenti a Kibana sulla rete GDC interna.

Prima di iniziare

Prima di iniziare il deployment, assicurati che l'ambiente GDC soddisfi i seguenti prerequisiti e presupposti:

  • Cluster Kubernetes condiviso e progetto GDC: hai accesso a un cluster condiviso GDC esistente e a un progetto GDC collegato a quel cluster. Il progetto funge da container dello spazio dei nomi per tutte le risorse Kubernetes di cui viene eseguito il deployment in questa guida. Assicurati che il cluster disponga di risorse di computing e archiviazione permanente sufficienti per ospitare i workload dell'operatore ECK, di Elasticsearch e di Kibana.
  • Servizio LLM: hai accesso di rete a un servizio LLM, ovvero il servizio Gemini gestito (se disponibile nel tuo ambiente GDC) o un servizio LLM compatibile con OpenAI autogestito (ad esempio, gemma4:31b fornito tramite Ollama o vLLM in un progetto GPU). Il deployment dell'infrastruttura LLM non rientra nell'ambito di questa guida, ma i requisiti specifici di modello e API sono descritti in dettaglio in Requisiti per il servizio LLM.
  • Recupera la versione di Elastic fornita con il tuo ambiente 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 le variabili di ambiente a cui verrà fatto riferimento in questa guida. Modifica questi valori in base alle esigenze del tuo ambiente specifico:

    # 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}
    
  • Richiedi o associa i ruoli IAM a livello di progetto richiesti per il tuo utente nel progetto GDC di destinazione (${PROJECT_ID}). In base alla documentazione sul ciclo di vita di GDC Marketplace e ai requisiti di networking GDC, sono necessari almeno i seguenti ruoli di progetto:

    • Namespace Admin (namespace-admin): accesso amministrativo completo per creare e gestire workload, secret, servizi e risorse personalizzate di Kubernetes all'interno dello spazio dei nomi del progetto sul cluster condiviso.
    • Project Viewer (project-viewer): accesso in lettura per visualizzare il progetto esistente e le relative risorse nella console e nell'interfaccia a riga di comando di GDC.
    • Marketplace Editor (marketplace-editor): autorizzazione per installare, eseguire l'upgrade e disinstallare i pacchetti software di GDC Marketplace nel progetto.
    • Project NetworkPolicy Admin (project-networkpolicy-admin): autorizzazione per configurare le regole ProjectNetworkPolicy globali che consentono al traffico tra progetti ed esterno di raggiungere i servizi LoadBalancer di Kibana ed Elasticsearch.

    Chiedi all'amministratore IAM dell'organizzazione o all'amministratore IAM del progetto di concedere questi ruoli (o esegui i seguenti comandi se disponi dei privilegi amministrativi):

    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
    
  • Recupera il kubeconfig per il cluster condiviso e configura un alias della shell ku per kubectl:

    KUBECONFIG=kubeconfig-${CLUSTER_NAME}.yaml gdcloud clusters \
      get-credentials ${CLUSTER_NAME} \
      --zone ${ZONE}
    
    alias ku="kubectl --kubeconfig=kubeconfig-${CLUSTER_NAME}.yaml"
    

Installa il pacchetto marketplace Elastic

In questa sezione, installerai l'operatore Elastic Cloud on Kubernetes (ECK) nello spazio dei nomi del tuo progetto dal Marketplace GDC con air gap seguendo la guida al ciclo di vita del Marketplace GDC.

Installa tramite l'interfaccia a riga di comando gdcloud (o la console GDC)

  1. Elenca i pacchetti software di Marketplace disponibili per confermare il nome del servizio e la versione disponibile:

    gdcloud marketplace services list
    

    L'output dovrebbe essere simile al seguente:

    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>
    
  2. Installa il pacchetto marketplace Elastic nel cluster condiviso di destinazione:

    gdcloud marketplace services install ${MARKETPLACE_SERVICE_NAME} \
      --cluster=${CLUSTER_NAME} \
      --zone=${ZONE} \
      --project ${PROJECT_ID}
    

    *(In alternativa, nella console GDC, seleziona il tuo progetto ${PROJECT_ID}, vai a Marketplace, seleziona la scheda Elastic Cloud on Kubernetes, fai clic su Installa, seleziona il cluster ${CLUSTER_NAME} e fai clic su Installa).*

  3. Verifica che il pod e lo StatefulSet elastic-operator siano in esecuzione nello spazio dei nomi del progetto (${PROJECT_ID}):

    ku get statefulset,pods -n ${PROJECT_ID}
    

    L'output dovrebbe essere simile al seguente:

    NAME                                READY   AGE
    statefulset.apps/elastic-operator   1/1     3m
    
    NAME                   READY   STATUS    RESTARTS   AGE
    pod/elastic-operator-0 1/1     Running   0          3m
    

Esegui il deployment di Elastic Stack

Crea il manifest di Elastic Stack

Il pacchetto Marketplace GDC esegue il deployment dell'operatore ECK preconfigurato per eseguire il pull delle immagini container di Elasticsearch e Kibana dal registry Harbor locale con air gap.

Crea elastic-stack.yaml per eseguire il deployment di un cluster Elasticsearch a 1 nodo, un'istanza dell'interfaccia utente Kibana, servizi HTTP LoadBalancer e un allow-all NetworkPolicy nello spazio dei nomi del progetto (${PROJECT_ID}). Il manifest include anche una licenza Secret riconciliata dall'operatore ECK, configurata qui come prova Enterprise di 30 giorni (eck-trial-license) nel caso in cui non disponi ancora di una licenza 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

Applica e verifica il deployment

Applica il manifest:

ku apply -f elastic-stack.yaml

Applica un ProjectNetworkPolicy globale in modo che le regole firewall GDC consentano il traffico in entrata verso gli IP LoadBalancer e il traffico tra progetti (ad esempio la comunicazione con il servizio 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

Monitora i pod, i servizi e le risorse personalizzate finché Elasticsearch e Kibana non raggiungono lo stato Running e Ready e ottengono gli IP LoadBalancer su ${CLUSTER_NAME}:

ku get pods,svc,elasticsearch,kibana -n ${PROJECT_ID}

Una volta pronte le risorse, l'output dovrebbe essere simile al seguente:

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

Esporta gli IP esterni del bilanciatore del carico assegnati a Elasticsearch e Kibana per utilizzarli nei passaggi successivi:

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 le credenziali e accedi alla UI di Kibana

Recupera la password del super utente elastic

ECK genera automaticamente un secret Kubernetes (elastic-cluster-es-elastic-user) contenente la password predefinita del super utente elastic. Recuperalo e decodificalo con:

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 connettività e accedi

  1. Verifica che Elasticsearch sia raggiungibile tramite HTTPS dalla tua workstation:

    curl -sk -u "elastic:${ELASTIC_PASSWORD}" "https://${ELASTICSEARCH_IP}:9200"
    
  2. Apri https://${KIBANA_IP}:5601 nel browser, accetta l'avviso relativo al certificato autofirmato e accedi con:

    • Nome utente: elastic
    • Password: <output from ELASTIC_PASSWORD above>

Verifica la licenza Elastic

Le funzionalità di AI generativa di Elastic (tra cui Elastic AI Agent, Agent Builder e Attack Discovery) e i connettori LLM esterni richiedono una licenza Enterprise (o una licenza di prova Enterprise attiva di 30 giorni).

Quando un cluster Elasticsearch è gestito dall'operatore ECK, license-controller di ECK riconcilia continuamente la licenza del cluster con i secret Kubernetes nello spazio dei nomi dell'operatore (${PROJECT_ID}). Se non disponi ancora di una licenza Enterprise di produzione, il secret eck-trial-license incluso in elastic-stack.yaml (license.k8s.elastic.co/type: enterprise_trial) indica a ECK di eseguire il provisioning e la manutenzione automatica di una licenza di prova Enterprise di 30 giorni. Tieni presente che l'avvio manuale di una prova tramite POST /_license/start_trial senza un secret di licenza ECK causerà il ripristino del cluster a basic nel ciclo di riconciliazione successivo.

Per verificare la licenza attiva tramite l'interfaccia a riga di comando:

curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
  "https://${ELASTICSEARCH_IP}:9200/_license" | jq .

*(In alternativa, in Kibana, apri il menu di navigazione e vai a Management > Dev Tools, quindi esegui GET /_license nella scheda Console).*

L'output dovrebbe essere simile al seguente, con "status": "active" e "type": "enterprise" (o "type": "trial" se utilizzi la licenza di prova):

{
  "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
  }
}

Caricare un set di dati di esempio integrato

Per valutare le funzionalità di AI di Elastic senza scrivere pipeline di importazione dei dati personalizzate o richiedere l'accesso a internet esterno, puoi caricare il set di dati di log web di esempio (kibana_sample_data_logs) integrato di Kibana. Poiché l'archivio dei dati di esempio è incluso nell'immagine container di Kibana, l'installazione avviene in pochi secondi in un ambiente air-gap. Il set di dati compila 14.074 documenti di log di accesso web contenenti indirizzi IP client (clientip), codici di stato HTTP (response), paesi di origine e di destinazione (geo.src, geo.dest), byte trasferiti (bytes), URL e stringhe user agent.

Consulta Aggiungere dati di esempio in Kibana e elastic-datasets-builtin.md per altri set di dati integrati (flights e ecommerce).

Carica il set di dati

Esegui questo comando per caricare kibana_sample_data_logs tramite l'endpoint dei dati di esempio interni di 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 il set di dati di esempio

Verifica che il set di dati di esempio logs sia contrassegnato come installed in Kibana e conferma che il stream di dati di supporto contenga 14,074 documenti in 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"

L'output dovrebbe essere simile al seguente:

{
  "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

Requisiti per il servizio LLM

In un ambiente GDC con air gap, gli LLM gestiti da Elastic ospitati nel cloud e i provider di LLM SaaS pubblici esterni non sono raggiungibili. A seconda del tuo ambiente GDC:

  • Gemini gestito su GDC: negli ambienti GDC che offrono Gemini come servizio LLM gestito, puoi connettere Kibana direttamente al servizio Gemini gestito, se disponibile.
  • Servizio LLM autogestito: in alternativa, puoi connettere Elastic a un servizio LLM autogestito ospitato nel tuo ambiente GDC (ad esempio, sui nodi GPU GDC).

Il deployment di un servizio LLM autogestito non rientra nell'ambito di questa guida. Quando esegui il provisioning o selezioni un servizio LLM con gestione indipendente per Elastic, assicurati che soddisfi i requisiti derivati dalle guide ufficiali di Elastic:

Riepilogo dei requisiti tecnici

I seguenti punti riassumono i requisiti chiave relativi ad API, funzionalità del modello, finestra di contesto e runtime necessari per il funzionamento affidabile di un servizio LLM autogestito con l'agente AI, l'Agent Builder e l'Attack Discovery di Elastic. Tieni presente che i nomi dei parametri di configurazione specifici citati nelle sezioni seguenti (ad esempio num_ctx, num_predict, repeat_penalty, repeat_last_n, OLLAMA_FLASH_ATTENTION e OLLAMA_NUM_PARALLEL) vengono forniti utilizzando Ollama come esempio. Applica le impostazioni equivalenti se utilizzi un altro motore di inferenza come vLLM.

  • API di completamento della chat compatibile con OpenAI (/v1/chat/completions) con chiamata di funzione nativa:
    • Qualsiasi server di inferenza autogestito (come Ollama o vLLM) che espone un endpoint /v1/chat/completions compatibile con OpenAI (incluso lo streaming di eventi inviati dal server tramite "stream": true) può essere integrato utilizzando il connettore OpenAI di Kibana con il provider impostato su Altro (servizio compatibile con OpenAI).
    • Abilita la chiamata di funzioni native ("enableNativeFunctionCalling": true) sul connettore Kibana quando utilizzi modelli che supportano la chiamata di strumenti nativi del cloud (come gemma4:31b). Per impostazione predefinita ("enableNativeFunctionCalling": false), Kibana utilizza la chiamata di funzioni simulate basata su prompt, che è meno affidabile durante le chiamate di strumenti nidificati e può non riuscire con errori di analisi (come Error: Missing name for tool use). L'attivazione della chiamata di funzioni native delega la formattazione della chiamata di strumenti direttamente al protocollo OpenAI tools del server di inferenza.
  • Modello ottimizzato per le istruzioni con funzionalità native di chiamata di funzioni e strumenti:
    • Elastic AI Agent (elastic-ai-agent) e gli agenti personalizzati di Agent Builder si basano sulla chiamata di strumenti in più passaggi (platform.core.list_indices, platform.core.get_index_mapping, platform.core.search, platform.core.execute_esql e platform.core.cases). Utilizzano questi strumenti per scoprire i mapping degli indici, generare ed eseguire query ES|QL su Elasticsearch e sintetizzare i risultati.
    • Il modello scelto deve essere ottimizzato per le istruzioni e in grado di generare chiamate di strumenti cloud-native valide, output JSON strutturati e query ES|QL. Consulta la matrice delle prestazioni LLM per l'osservabilità e la matrice delle prestazioni LLM per Elastic Security per i criteri di valutazione del modello.
  • Finestra contestuale di almeno 128,000 token (ad esempio, num_ctx: 131072 in Ollama):
    • Sebbene la documentazione LLM personalizzata autogestita generale di Elastic citi un minimo di 64.000 token, una finestra contestuale di 128.000 token (131,072 token) è fortemente consigliata per Elastic agente AI (elastic-ai-agent).
    • Durante le indagini in più fasi, l'agente combina le istruzioni di sistema e le definizioni degli strumenti con gli schemi di indice, la documentazione ES|QL e i risultati di ricerca non elaborati (ad esempio, recuperando fino a 100 documenti di esempio per ispezionare i valori dei campi prima dell'aggregazione). Una singola indagine può superare i 70.000 token nella cronologia delle conversazioni, il che fa sì che una finestra contestuale di 64.000 token tronchi il prompt a metà conversazione.
    • Tieni presente che in Ollama, un'istruzione PARAMETER num_ctx integrata nel Modelfile di un modello esegue l'override della variabile di ambiente OLLAMA_CONTEXT_LENGTH del container. Assicurati che sia OLLAMA_CONTEXT_LENGTH=131072 sia PARAMETER num_ctx 131072 in Modelfile del modello siano configurati (su una GPU NVIDIA A100 o H100 da 80 GB, gemma4:31b al contesto 128K si adatta comodamente alla VRAM).
  • Limite di token di output e penalità di ripetizione moderata (ad esempio, num_predict: 8192 e repeat_penalty: 1.05 in Ollama):
    • L'adattatore di stream OpenAI di Kibana tratta "finish_reason": "length" come un errore Token limit reached irreversibile e invia "temperature": 0 (decodifica greedy) in ogni richiesta.
    • Per i modelli in grado di ragionare (come gemma4:31b, che genera token di pensiero interni prima di emettere chiamate di strumenti), configura un limite di token di output di almeno 8,192 token e una penalità di ripetizione moderata. Ad esempio, in Ollama, imposta num_predict: 8192, repeat_penalty: 1.05 e repeat_last_n: 64. Evita penalità per ripetizione eccessivamente aggressive (ad esempio 1.15+ su finestre di grandi dimensioni), in quanto penalizzano il modello quando copia una query ES|QL dal blocco di ragionamento interno nel JSON della chiamata di strumento finale.
  • Accelerazione GPU, Flash Attention e slot di cache KV paralleli (<60 s per turno):
    • Il connettore OpenAI di Kibana applica un timeout HTTP predefinito di 60 secondi per ogni turno di streaming.
    • Poiché un singolo turno dell'agente può attivare chiamate secondarie LLM nidificate (ad esempio, per tradurre le richieste di ricerca in linguaggio naturale in query ES|QL mentre la conversazione principale dell'agente è attiva), l'esecuzione di un server di inferenza con un solo slot di cache KV può causare l'espulsione del contesto memorizzato nella cache della conversazione principale e rallentare i turni successivi.
    • In Ollama, imposta OLLAMA_FLASH_ATTENTION=1 e OLLAMA_NUM_PARALLEL=2 in modo che Flash Attention acceleri la valutazione del contesto lungo e la conversazione principale e le chiamate secondarie nidificate mantengano slot di cache KV separati nella memoria della GPU.

Configura il connettore LLM in Kibana

Una volta che il servizio LLM compatibile con OpenAI è in esecuzione e raggiungibile dal pod Kibana, configura un connettore OpenAI in Kibana utilizzando l'interfaccia a riga di comando o l'interfaccia utente di Kibana.

Opzione 1: crea il connettore tramite l'interfaccia a riga di comando

  1. Esegui il seguente comando POST /api/actions/connector per creare un connettore OpenAI (.gen-ai) che punta al tuo servizio LLM (${LLM_API_URL}) con la chiamata di funzioni nativa abilitata ("enableNativeFunctionCalling": true). Kibana genera e assegna automaticamente un ID connettore univoco:

    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 .
    
  2. Verifica che il connettore ${LLM_CONNECTOR_NAME} esista e non abbia secret mancanti elencando i connettori di Kibana e filtrando per nome:

    curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
      "https://${KIBANA_IP}:5601/api/actions/connectors" \
      -H "kbn-xsrf: true" \
      | jq ".[] | select(.name == \"${LLM_CONNECTOR_NAME}\")"
    

    L'output dovrebbe essere simile al seguente:

    {
      "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
    }
    
  3. Per testare la connettività end-to-end in tempo reale tra Kibana e il servizio LLM dalla CLI, cerca l'ID del connettore generato automaticamente per nome e richiama il relativo endpoint _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 .
    

    L'output dovrebbe essere simile al seguente, che mostra "status": "ok" e la risposta del modello:

    {
      "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>"
    }
    

Opzione 2: crea il connettore tramite l'interfaccia utente di Kibana

  1. Accedi a Kibana (https://${KIBANA_IP}:5601).
  2. Apri il menu di navigazione principale e vai a Gestione > Avvisi e insight > Connettori.
  3. Fai clic su Crea connettore e seleziona OpenAI.
  4. Configura i campi del connettore:
    • Nome connettore: inserisci il valore di ${LLM_CONNECTOR_NAME} (ad esempio Gemma4-31B).
    • Seleziona un provider OpenAI: seleziona Altro (servizio compatibile con OpenAI).
    • URL: inserisci il valore di ${LLM_API_URL} (l'endpoint /v1/chat/completions del servizio LLM).
    • Modello predefinito: inserisci il valore di ${LLM_MODEL_NAME} (ad esempio, gemma4:31b).
    • Chiave API: inserisci il valore di ${LLM_API_KEY} (qualsiasi stringa non vuota se il server non applica l'autenticazione bearer o la chiave API del server).
    • Attiva la chiamata di funzione nativa: attiva/disattiva l'opzione.
  5. Fai clic su Salva e testa per salvare il connettore e aprire immediatamente la scheda Test.
  6. Nella scheda Test, inserisci un prompt di esempio (ad esempio, Reply with the single word Pong.) e fai clic su Esegui per verificare che Kibana riceva una risposta valida dal servizio LLM.

Testare le integrazioni LLM

Con il set di dati kibana_sample_data_logs installato e il connettore LLM verificato, puoi testare le funzionalità di AI di Elastic in Kibana.

Elastic AI Agent

Elastic AI Agent (elastic-ai-agent) è l'agente conversazionale predefinito integrato in Kibana per esplorare e analizzare i dati in Elasticsearch utilizzando il linguaggio naturale. Basato sul connettore LLM configurato e sugli strumenti integrati di Agent Builder, l'agente rileva automaticamente gli indici e i mapping di Elasticsearch (platform.core.list_indices, platform.core.get_index_mapping), genera ed esegue query ES|QL (platform.core.search, platform.core.execute_esql) e sintetizza i risultati senza richiedere di conoscere in anticipo lo schema sottostante. Puoi anche creare i tuoi agenti personalizzati con istruzioni e strumenti su misura. Per maggiori dettagli, consulta Agenti personalizzati con Agent Builder.

Per aprire e configurare Elastic AI Agent nell'interfaccia utente di Kibana:

  1. Apri il menu di navigazione principale in Kibana e seleziona Agente AI (o fai clic sul pulsante Chiedi all'AI nella barra dell'intestazione in alto o vai direttamente a https://${KIBANA_IP}:5601/app/chat).
  2. Assicurati che Elastic AI Agent sia selezionato come agente attivo e che il connettore LLM (${LLM_CONNECTOR_NAME}) sia selezionato come connettore del modello.

Esempio 1: indagine sulle richieste HTTP non riuscite e sui potenziali scanner

Chiedi all'agente AI di Elastic di identificare i principali indirizzi IP client che generano risposte HTTP non 200 in kibana_sample_data_logs, i relativi paesi di origine e se uno di questi IP ha generato anche traffico HTTP 200 riuscito:

  • Opzione 1: tramite l'interfaccia utente di Kibana:inserisci la seguente domanda nel prompt della chat dell'AI Agent:

    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?

  • Opzione 2: tramite l'interfaccia a riga di comando:

    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
      }'
    

Cosa succede dietro le quinte:

  1. L'agente chiama platform.core.get_index_mapping(indices=["kibana_sample_data_logs"]) per ispezionare i campi disponibili (clientip, geo.src, response, bytes, request e così via).
  2. Chiama platform.core.search per esaminare i documenti di esempio e verificare che i codici di stato HTTP siano archiviati in response, quindi esegue le aggregazioni ES|QL per classificare le richieste non 200 in base a clientip e geo.src e controllare quali degli IP principali hanno registrato anche traffico response == "200":

    FROM kibana_sample_data_logs
    | WHERE response != "200"
    | STATS failed_count = COUNT(*) BY clientip, geo.src
    | SORT failed_count DESC
    | LIMIT 10
    
    FROM 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 risposta dell'agente Elastic AI dovrebbe essere simile alla seguente:

L'indirizzo IP client con il maggior numero di richieste HTTP non riuscite (codici di stato non 200) in kibana_sample_data_logs è 30.156.16.164, che ha avuto origine negli Stati Uniti e ha registrato 100 richieste non riuscite.

Altri indirizzi IP principali con richieste non riuscite, tutti provenienti anche dagli Stati Uniti, includono:

  • 213.235.30.99: 6 errori
  • 221.241.228.46: 6 errori
  • 209.94.234.133: 6 errori
  • 7.127.73.138: 5 errori
  • 247.240.202.244: 5 tentativi non riusciti
  • 130.125.65.24: 5 errori
  • 164.85.94.243: 4 errori
  • 81.194.200.150: 4 errori
  • 30.145.143.210: 4 tentativi non riusciti

Per quanto riguarda le richieste riuscite, tutti gli IP elencati sopra hanno ricevuto almeno alcune risposte HTTP 200 riuscite, ad eccezione di 30.156.16.164, che non ha ricevuto richieste riuscite.

Esempio 2: analisi del volume di traffico e dei byte di payload in base al codice di stato HTTP

Chiedi all'agente AI di Elastic la suddivisione dei codici di stato della risposta HTTP per tutte le richieste in kibana_sample_data_logs e i byte totali trasferiti per ogni codice di stato:

  • Opzione 1: tramite la UI di Kibana: avvia una nuova conversazione in AI Agent e inserisci la seguente domanda:

    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?

  • Opzione 2: tramite 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
      }'
    

Cosa succede dietro le quinte:

  1. L'agente ispeziona lo schema kibana_sample_data_logs tramite platform.core.get_index_mapping, identificando i campi response.keyword e bytes.
  2. Richiama platform.core.search, che genera ed esegue la seguente aggregazione ES|QL:

    FROM kibana_sample_data_logs
    | STATS request_count = COUNT(*), total_bytes = SUM(bytes) BY response.keyword
    | LIMIT 100
    

La risposta di Elastic AI Agent dovrebbe essere simile alla seguente:

La suddivisione dei codici di stato della risposta HTTP e dei dati totali trasferiti in kibana_sample_data_logs è la seguente:

Codice di stato Conteggio delle richieste Dati totali trasferiti (byte)
200 12.832 75.681.246
404 801 4.044.443
503 441 0

Agenti personalizzati con Agent Builder

Agent Builder ti consente di creare agenti AI personalizzati su misura per workflow operativi specifici combinando istruzioni di sistema personalizzate con strumenti integrati (come platform.core.cases, platform.core.execute_esql e platform.core.search) e strumenti definiti dall'utente (esql, index_search, workflow e mcp).

L'esempio seguente crea un agente Web Security Incident Responder che opera direttamente sul set di dati kibana_sample_data_logs. In base a un prompt in linguaggio naturale, l'agente:

  1. Esegue uno strumento ES|QL personalizzato (investigate_suspicious_web_clients) per aggregare le risposte HTTP non 200 in kibana_sample_data_logs per IP client.
  2. Distingue le attività di ricognizione e sfruttamento dannose (30.156.16.164) dagli errori di download innocui.
  3. Richiama uno strumento personalizzato Kibana Workflow (create_security_case) per aprire automaticamente un Kibana Security Case strutturato con i risultati e i passaggi di correzione.

Crea strumenti personalizzati per ES|QL e la creazione di richieste

  1. Crea uno strumento esql personalizzato denominato investigate_suspicious_web_clients che esegue query su kibana_sample_data_logs per trovare gli indirizzi IP client principali che generano codici di stato HTTP non 200, insieme ai relativi codici di stato, paese di origine (geo.src), user agent (agent) e URL richiesti (request):

    • Opzione 1: tramite la UI di Kibana:

      1. In Kibana, apri il menu di navigazione principale e vai a Elasticsearch > Agenti, quindi fai clic su Gestisci tutti gli strumenti (o vai direttamente a https://${KIBANA_IP}:5601/app/agent_builder/tools) e fai clic su Nuovo strumento.
      2. Configura i campi dello strumento:

        • ID strumento: investigate_suspicious_web_clients
        • Type: ES|QL
        • Descrizione: 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.
        • Etichette: security, logs
        • Query 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
          
      3. Fai clic su Salva.

    • Opzione 2: tramite l'interfaccia a riga di comando:

      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}'
      
  2. Poiché lo strumento platform.core.cases integrato di Agent Builder è di sola lettura (utilizzato per la ricerca e il recupero di richieste esistenti), crea un flusso di lavoro Kibana (create-security-case) che richiama il passaggio cases.createCase nativo di Kibana e registralo come strumento workflow denominato create_security_case:

    • Opzione 1: tramite la UI di Kibana:

      1. Apri il menu di navigazione principale e vai a Gestione > Workflow (o vai direttamente a https://${KIBANA_IP}:5601/app/workflows), quindi fai clic su Crea un nuovo workflow.
      2. Nell'editor YAML, incolla la seguente definizione di workflow e fai clic su Salva:

        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-logs
        
      3. Torna alla pagina Strumenti di Agent Builder (https://${KIBANA_IP}:5601/app/agent_builder/tools o vai a Elasticsearch > Agent e fai clic su Gestisci tutti gli strumenti) e fai clic su Nuovo strumento.

      4. Configura i campi dello strumento del flusso di lavoro e fai clic su Salva:

        • ID strumento: create_security_case
        • Type: Workflow
        • Workflow: seleziona Crea richiesta di assistenza per la sicurezza (create-security-case)
        • Descrizione: Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical).
        • Etichette: security, cases
    • Opzione 2: tramite l'interfaccia a riga di comando:

      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 l'agente Incident Responder personalizzato

Crea l'agente web-security-incident-responder e assegnagli gli strumenti investigate_suspicious_web_clients, create_security_case e platform.core.cases:

  • Opzione 1: tramite la UI di Kibana

    1. Apri il menu di navigazione principale e vai a Elasticsearch > Agenti (o vai direttamente a https://${KIBANA_IP}:5601/app/agent_builder/agents), quindi fai clic su Nuovo agente.
    2. Configura i dettagli dell'agente:

      • ID agente: web-security-incident-responder
      • Nome visualizzato: Web Security Incident Responder
      • Descrizione visualizzata: Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings.
      • Etichette: security, incident-response
      • Istruzioni personalizzate:

        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.
        
      • Strumenti: seleziona investigate_suspicious_web_clients, create_security_case e platform.core.cases.

    3. Fai clic su Salva.

  • Opzione 2: tramite l'interfaccia a riga di comando:

    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}'
    

Esegui l'agente e verifica la richiesta Kibana

  1. Avvia una conversazione con l'agente Web Security Incident Responder:

    • Opzione 1: tramite la UI di Kibana:

      1. Apri il menu di navigazione principale e vai a Elasticsearch > Agenti (o vai direttamente a https://${KIBANA_IP}:5601/app/agent_builder).
      2. Seleziona Web Security Incident Responder dal selettore degli agenti e assicurati che il connettore LLM (${LLM_CONNECTOR_NAME}) sia selezionato.
      3. Digita il seguente prompt:

        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.

    • Opzione 2: tramite l'interfaccia a riga di comando:

      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
        }'
      

      L'output dovrebbe essere simile al seguente:

      {
        "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."
      }
      
  2. Verifica la richiesta di sicurezza Kibana appena creata:

    • Opzione 1: tramite la UI di Kibana:

      1. Apri il menu di navigazione principale e vai a Sicurezza > Richieste (o vai direttamente a https://${KIBANA_IP}:5601/app/security/cases).
      2. Fai clic su Tentativo di ricognizione e sfruttamento dannoso da 30.156.16.164 per visualizzare il riepilogo dell'incidente in formato Markdown, l'analisi MITRE ATT&CK, gli URL dannosi di esempio e la checklist di correzione.
    • Opzione 2: tramite l'interfaccia a riga di comando:

      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}'
      

      L'output dovrebbe essere simile al seguente:

      {
        "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."
      }
      

Attack Discovery

Attack Discovery è una funzionalità di AI di Elastic Security che analizza gli avvisi di sicurezza aperti, li mappa al framework MITRE ATT&CK e correla avvisi, indirizzi IP e account utente correlati in catene di attacco visive. Poiché Attack Discovery richiede un modello di grandi dimensioni per sintetizzare in modo affidabile output JSON strutturati con più avvisi, utilizza un modello di classe 30B o superiore (come gemma4:31b).

Genera avvisi di sicurezza dai log di esempio

Per impostazione predefinita, il set di dati kibana_sample_data_logs contiene log di accesso web non elaborati e non compila l'indice degli avvisi di Elastic Security (.alerts-security.alerts-default). Inoltre, il generatore di dati di esempio di Kibana assegna valori @timestamp futuri (fino a 30 giorni prima) alle 100 richieste di scansione e sfruttamento provenienti da 30.156.16.164, che le colloca al di fuori della finestra da now-30d a now interrogata dalle regole di rilevamento di Elastic Security.

Per dimostrare il rilevamento degli attacchi con i log di esempio, sposta indietro di 30 giorni i @timestamp record di 30.156.16.164, arricchiscili con i campi Elastic Common Schema (ECS) source.ip e user.name e crea due regole di rilevamento che generano avvisi di sicurezza da questi eventi:

  1. Mappa clientip a source.ip, imposta user.name (www-data) e sposta @timestamp indietro di 30 giorni i record 30.156.16.164 in kibana_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}'
    
  2. Crea due regole di rilevamento di Elastic Security che abbiano come target kibana_sample_data_logs*. La prima regola rileva i tentativi di accesso a Nmap e WordPress (Reconnaissance), mentre la seconda rileva i tentativi di attraversamento del percorso /etc/passwd e di esecuzione di comandi da remoto (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}'
    
  3. Attendi circa 60 secondi per il completamento dell'esecuzione iniziale della regola, quindi verifica che siano stati indicizzati 4 avvisi in .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}'
    

    L'output dovrebbe essere simile al seguente:

    {
      "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"
    }
    

Esegui Attack Discovery

  1. In Kibana, apri il menu di navigazione principale e vai a Sicurezza > Rilevamento attacchi (o vai direttamente a https://${KIBANA_IP}:5601/app/security/attack_discovery).
  2. Seleziona il connettore LLM (${LLM_CONNECTOR_NAME}) dal menu dei connettori nella parte superiore della pagina.
  3. Fai clic su Genera per analizzare gli avvisi aperti in .alerts-security.alerts-default.

Puoi anche attivare e ispezionare il rilevamento degli attacchi dalla 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}"

Al termine della generazione (circa 60-90 secondi), recupera la catena di attacco rilevata:

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]'

Il rilevamento degli attacchi generato correla i quattro avvisi in un unico incidente con IP di origine, account utente e tattiche MITRE ATT&CK collegati:

{
  "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"
}