Como implantar os recursos de IA generativa do Elastic Stack no GDC com isolamento físico

Visão geral

Este guia descreve a implantação completa do Elastic Stack (Elasticsearch e Kibana) em um cluster compartilhado com isolamento físico do Google Distributed Cloud (GDC) e demonstra como integrá-lo a um serviço de modelo de linguagem grande (LLM) para avaliar os recursos de IA generativa do Elastic.

A implantação usa o operador oficial do Elastic Cloud no Kubernetes (ECK) disponível no Marketplace do GDC com isolamento físico. O operador é instalado em um namespace de projeto em um cluster compartilhado do Kubernetes e automatiza o provisionamento e o gerenciamento do ciclo de vida dos recursos do Elasticsearch e do Kibana. Depois de implantado, o Kibana se conecta a um endpoint de inferência de LLM (como o Gemini gerenciado no GDC ou um serviço autogerenciado compatível com a OpenAI, como o Ollama ou o vLLM). Essa integração potencializa os recursos de IA generativa da Elastic, incluindo o Agente de IA da Elastic, agentes personalizados criados com o Agent Builder e o Attack Discovery.

Este guia foi escrito para o Elastic v9.4.0, o Elastic Kubernetes Operator v3.4.0 e o GDC v1.16.2. Algumas instruções podem ser um pouco diferentes se você estiver usando versões diferentes.

Arquitetura

A arquitetura consiste em uma implantação do Elastic Stack gerenciada pelo operador do ECK em um namespace de projeto em um cluster de usuário compartilhado do GDC. O Kibana se comunica pela rede interna do GDC com um endpoint de inferência de LLM.

Diagrama de arquitetura do Elastic Stack.

Os principais componentes dessa arquitetura incluem:

  • Cluster compartilhado do GDC: um cluster compartilhado do Kubernetes com escopo de organização anexado a vários projetos, que hospeda pacotes de software e cargas de trabalho de aplicativos do GDC Marketplace.
  • Projeto do GDC: o namespace do projeto no cluster compartilhado que isola o operador do ECK, o cluster do Elasticsearch, a instância do Kibana e os Secrets e as NetworkPolicies do Kubernetes associados.
  • Operador do Elastic Cloud no Kubernetes (ECK): instalado do GDC Marketplace no namespace do projeto, o operador gerencia os recursos personalizados Elasticsearch e Kibana e reconcilia a licença do Elastic necessária para recursos de IA generativa e conectores de LLM externos.
  • Elasticsearch: armazena e indexa dados operacionais e de segurança, além de executar consultas de pesquisa e ES|QL geradas pelos recursos de IA e agentes do Kibana.
  • Kibana: hospeda os recursos de IA generativa da Elastic, incluindo o agente de IA da Elastic (para investigação de registros em linguagem natural e geração de ES|QL), o Agent Builder (para criar agentes e ferramentas de conversação personalizados com base nos dados do Elasticsearch) e o Attack Discovery (para correlação de alertas de segurança e análise de cadeia de ataques). O Kibana também gerencia os conectores de ação de LLM que orquestram a chamada de ferramentas em várias etapas entre o usuário, o Elasticsearch e o serviço de LLM.
  • Serviço de LLM: um serviço de LLM gerenciado (como o Gemini no GDC) ou um servidor de inferência autogerenciado (como Ollama ou vLLM) que fornece recursos de conclusão de chat e chamada de ferramentas para o Kibana pela rede interna do GDC.

Antes de começar

Verifique se o ambiente do GDC atende aos seguintes pré-requisitos e suposições antes de iniciar a implantação:

  • Cluster compartilhado do Kubernetes e projeto do GDC: você tem acesso a um cluster compartilhado do GDC e a um projeto do GDC anexado a esse cluster. O projeto serve como o contêiner de namespace para todos os recursos do Kubernetes implantados neste guia. Verifique se o cluster tem recursos de computação e armazenamento permanente suficientes para hospedar as cargas de trabalho do operador ECK, do Elasticsearch e do Kibana.
  • Serviço de LLM: você tem acesso de rede a um serviço de LLM, seja o serviço gerenciado do Gemini (se disponível no seu ambiente do GDC) ou um serviço de LLM autogerenciado compatível com a OpenAI (por exemplo, gemma4:31b disponibilizado pelo Ollama ou vLLM em um projeto de GPU). A implantação da infraestrutura do LLM em si está fora do escopo deste guia, mas os requisitos específicos do modelo e da API estão detalhados em Requisitos para o serviço de LLM.
  • Recupere a versão do Elastic que vem com o ambiente do 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')
    
  • Configure as variáveis de ambiente que serão referenciadas neste guia. Modifique esses valores conforme necessário para corresponder ao seu ambiente específico:

    # GDC environment configuration
    export PROJECT_ID="your-project-id"
    export CLUSTER_NAME="your-shared-cluster-name"
    export ZONE="your-zone-name"
    export USER_NAME="your-gdc-user-email"
    
    # Marketplace package and Elastic Stack configuration
    export MARKETPLACE_SERVICE_NAME="elastic"
    
    # LLM service configuration
    export LLM_CONNECTOR_NAME="your-llm-connector-name"
    export LLM_API_URL="http://your-llm-service-host:11434/v1/chat/completions"
    export LLM_MODEL_NAME="your-llm-model-name"
    export LLM_API_KEY="your-llm-api-key"
    
  • Configurar gdcloud:

    gdcloud config set project ${PROJECT_ID}
    gdcloud config set core/zone ${ZONE}
    
  • Solicite ou vincule os papéis do IAM necessários para envolvidos no projeto para seu usuário no projeto de destino do GDC (${PROJECT_ID}). Com base na documentação do ciclo de vida do GDC Marketplace e nos requisitos de rede do GDC, pelo menos os seguintes papéis do projeto são necessários:

    • Administrador de namespace (namespace-admin): acesso administrativo total para criar e gerenciar cargas de trabalho, secrets, serviços e recursos personalizados do Kubernetes no namespace do projeto no cluster compartilhado.
    • Leitor do projeto (project-viewer): acesso de leitura para visualizar o projeto e os recursos dele no console e na CLI do GDC.
    • Editor do Marketplace (marketplace-editor): permissão para instalar, fazer upgrade e desinstalar pacotes de software do GDC Marketplace no projeto.
    • Administrador de NetworkPolicy do projeto (project-networkpolicy-admin): permissão para configurar regras globais de ProjectNetworkPolicy que permitem que o tráfego entre projetos e externo chegue aos serviços LoadBalancer do Kibana e do Elasticsearch.

    Peça ao administrador do IAM da organização ou do projeto para conceder esses papéis (ou execute os seguintes comandos se você tiver privilégios de administrador do 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
    
  • Recupere o kubeconfig do cluster compartilhado e configure um alias de shell ku para kubectl:

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

Instalar o pacote do Elastic Marketplace

Nesta seção, você vai instalar o operador do Elastic Cloud no Kubernetes (ECK) no namespace do projeto usando o Marketplace do GDC com isolamento físico, seguindo o guia do ciclo de vida do Marketplace do GDC.

Instalar usando a CLI gdcloud (ou o console do GDC)

  1. Liste os pacotes de software disponíveis do Marketplace para confirmar o nome do serviço e a versão disponível:

    gdcloud marketplace services list
    

    A saída será parecida com esta:

    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. Instale o pacote do marketplace do Elastic no cluster compartilhado de destino:

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

    *(Como alternativa, no console do GDC, selecione o projeto ${PROJECT_ID}, acesse Marketplace, selecione a listagem Elastic Cloud no Kubernetes, clique em Instalar, selecione o cluster ${CLUSTER_NAME} e clique em Instalar).*

  3. Verifique se o StatefulSet elastic-operator e o pod estão em execução no namespace do projeto (${PROJECT_ID}):

    ku get statefulset,pods -n ${PROJECT_ID}
    

    A saída será parecida com esta:

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

Como implantar o Elastic Stack

Criar o manifesto do Elastic Stack

O pacote do GDC Marketplace implanta o operador ECK pré-configurado para extrair imagens de contêiner do Elasticsearch e do Kibana do registro local do Harbor com isolamento físico.

Crie elastic-stack.yaml para implantar um cluster do Elasticsearch de um nó, uma instância UI do Kibana, serviços HTTP do LoadBalancer e um allow-all NetworkPolicy no namespace do projeto (${PROJECT_ID}). O manifesto também inclui uma licença Secret reconciliada pelo operador do ECK, configurada aqui como um teste Enterprise de 30 dias (eck-trial-license), caso você ainda não tenha sua própria licença 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

Aplicar e verificar a implantação

Aplique o manifesto:

ku apply -f elastic-stack.yaml

Aplique um ProjectNetworkPolicy global para que as regras de firewall do GDC permitam o tráfego de entrada para os IPs do LoadBalancer e o tráfego entre projetos (como a comunicação com o serviço de LLM):

kubectl --kubeconfig=kubeconfig-global.yaml apply -f - <<EOF
apiVersion: networking.global.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
  namespace: ${PROJECT_ID}
  name: allow-inbound-traffic-from-external
spec:
  policyType: Ingress
  subject:
    subjectType: UserWorkload
  ingress:
    - from:
        - ipBlock:
            cidr: 0.0.0.0/0
---
apiVersion: networking.global.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
  namespace: ${PROJECT_ID}
  name: allow-inbound-traffic-from-all-projects
spec:
  policyType: Ingress
  subject:
    subjectType: UserWorkload
  ingress:
    - from:
        - projects: {}
EOF

Monitore os pods, serviços e recursos personalizados até que o Elasticsearch e o Kibana alcancem os status Running e Ready e recebam IPs do LoadBalancer em ${CLUSTER_NAME}:

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

Quando os recursos estiverem prontos, a saída será semelhante a esta:

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

Exporte os IPs externos do LoadBalancer atribuídos ao Elasticsearch e ao Kibana para uso nas etapas seguintes:

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"

Recuperar credenciais e acessar a interface do Kibana

Recuperar a senha de superusuário elastic

O ECK gera automaticamente um secret do Kubernetes (elastic-cluster-es-elastic-user) que contém a senha padrão do superusuário elastic. Recupere e decodifique com:

export ELASTIC_PASSWORD=$(ku get secret elastic-cluster-es-elastic-user \
  -n ${PROJECT_ID} \
  -o jsonpath='{.data.elastic}' | base64 -d)
echo "${ELASTIC_PASSWORD}"

Verificar a conectividade e fazer login

  1. Verifique se o Elasticsearch pode ser acessado por HTTPS na sua estação de trabalho:

    curl -sk -u "elastic:${ELASTIC_PASSWORD}" "https://${ELASTICSEARCH_IP}:9200"
    
  2. Abra https://${KIBANA_IP}:5601 no navegador, aceite o aviso de certificado autoassinado e faça login com:

    • Nome de usuário: elastic
    • Senha: <output from ELASTIC_PASSWORD above>

Verificar a licença do Elastic

Os recursos de IA generativa da Elastic (incluindo o Elastic AI Agent, o Agent Builder e o Attack Discovery) e os conectores de LLM externos exigem uma licença Enterprise (ou uma licença de teste Enterprise ativa de 30 dias).

Quando um cluster do Elasticsearch é gerenciado pelo operador do ECK, o license-controller do ECK reconcilia continuamente a licença do cluster com os Secrets do Kubernetes no namespace do operador (${PROJECT_ID}). Se você ainda não tiver uma licença Enterprise de produção, o Secret eck-trial-license incluído em elastic-stack.yaml (license.k8s.elastic.co/type: enterprise_trial) instruirá o ECK a provisionar e manter uma licença de teste Enterprise de 30 dias automaticamente. Iniciar um teste sem custo financeiro manualmente via POST /_license/start_trial sem um Secret de licença do ECK fará com que o ECK reverta o cluster para basic no próximo ciclo de reconciliação.

Para verificar a licença ativa pela CLI:

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

*(Como alternativa, no Kibana, abra o menu de navegação e acesse Gerenciamento > Ferramentas para desenvolvedores e execute GET /_license na guia "Console").*

A saída será semelhante à seguinte, mostrando "status": "active" e "type": "enterprise" (ou "type": "trial" se você estiver usando a licença de teste):

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

Carregar um conjunto de dados de amostra integrado

Para avaliar os recursos de IA da Elastic sem escrever pipelines personalizados de ingestão de dados ou exigir acesso externo à Internet, carregue o conjunto de dados de amostra de registros da Web integrado do Kibana (kibana_sample_data_logs). Como o arquivo de dados de amostra está incluído na imagem do contêiner do Kibana, ele é instalado em segundos em um ambiente com isolamento físico. O conjunto de dados preenche 14.074 documentos de registro de acesso à Web contendo endereços IP do cliente (clientip), códigos de status HTTP (response), países de origem e destino (geo.src, geo.dest), bytes transferidos (bytes), URLs e strings de user agent.

Consulte Adicionar dados de amostra no Kibana e elastic-datasets-builtin.md para mais conjuntos de dados integrados (flights e ecommerce).

Carregar o conjunto de dados

Execute o comando a seguir para carregar kibana_sample_data_logs pelo endpoint de dados de amostra interno do 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"

Verificar o conjunto de dados de amostra

Verifique se o conjunto de dados de amostra logs está marcado como installed no Kibana e confirme se o fluxo de dados de apoio contém documentos 14,074 no 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"

A saída será parecida com esta:

{
  "id": "logs",
  "name": "Sample web logs",
  "status": "installed"
}
health status index                                         uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
yellow open   .ds-kibana_sample_data_logs-2026.09.22-000001 0YivG3oFQQmb-mYqmULr_g   1   1      14074            0      8.7mb          8.7mb        8.7mb

Requisitos para o serviço de LLM

Em um ambiente GDC com isolamento físico, os LLMs gerenciados do Elastic hospedados na nuvem e os provedores externos de LLMs SaaS públicos não podem ser acessados. Dependendo do ambiente do GDC:

  • Gemini gerenciado no GDC: em ambientes do GDC que oferecem o Gemini como um serviço de LLM gerenciado, é possível conectar o Kibana diretamente ao serviço gerenciado do Gemini, se disponível.
  • Serviço de LLM autogerenciado: como alternativa, é possível conectar o Elastic a um serviço de LLM autogerenciado hospedado no ambiente do GDC (por exemplo, em nós de GPU do GDC).

A implantação de um serviço de LLM autogerenciado está fora do escopo deste guia. Ao provisionar ou selecionar um serviço de LLM autogerenciado para o Elastic, verifique se ele atende aos requisitos derivados dos guias oficiais do Elastic:

Resumo dos requisitos técnicos

Os pontos a seguir resumem os principais requisitos de API, capacidade do modelo, janela de contexto e tempo de execução necessários para que um serviço de LLM autogerenciado funcione de maneira confiável com o agente de IA, o criador de agentes e a descoberta de ataques da Elastic. Os nomes de parâmetros de configuração específicos citados nas seções a seguir (como num_ctx, num_predict, repeat_penalty, repeat_last_n, OLLAMA_FLASH_ATTENTION e OLLAMA_NUM_PARALLEL) são fornecidos usando o Ollama como exemplo. Aplique as configurações equivalentes se você usar outro mecanismo de inferência, como o vLLM.

  • API de preenchimento de chat compatível com a OpenAI (/v1/chat/completions) com chamada de função nativa:
    • Qualquer servidor de inferência autogerenciado (como Ollama ou vLLM) que exponha um endpoint /v1/chat/completions compatível com a OpenAI (incluindo streaming de eventos enviados pelo servidor via "stream": true) pode ser integrado usando o conector da OpenAI do Kibana com o provedor definido como Outro (serviço compatível com a OpenAI).
    • Ative a chamada de função nativa ("enableNativeFunctionCalling": true) no conector do Kibana ao usar modelos que oferecem suporte à chamada de ferramenta nativa da nuvem (como gemma4:31b). Por padrão ("enableNativeFunctionCalling": false), o Kibana usa a chamada de função simulada baseada em comandos, que é menos confiável durante chamadas de ferramentas aninhadas e pode falhar com erros de análise (como Error: Missing name for tool use). Ativar a chamada de função nativa delega a formatação da chamada de ferramenta diretamente ao protocolo OpenAI tools do servidor de inferência.
  • Modelo ajustado por instruções com recursos nativos de ferramentas e chamadas de função:
    • O agente de IA do Elastic (elastic-ai-agent) e os agentes de criação personalizados do Agent Builder dependem da chamada de ferramentas em várias etapas (platform.core.list_indices, platform.core.get_index_mapping, platform.core.search, platform.core.execute_esql e platform.core.cases). Eles usam essas ferramentas para descobrir mapeamentos de índice, gerar e executar consultas ES|QL no Elasticsearch e sintetizar descobertas.
    • O modelo escolhido precisa ser ajustado por instruções e capaz de gerar invocações de ferramentas nativas da nuvem válidas, saídas JSON estruturadas e consultas ES|QL. Consulte a matriz de desempenho de LLM para observabilidade e a matriz de desempenho de LLM para Elastic Security (links em inglês) da Elastic para conferir os critérios de avaliação de modelos.
  • Janela de contexto de pelo menos 128,000 tokens (por exemplo, num_ctx: 131072 no Ollama):
    • Embora a documentação geral de LLM personalizado autogerenciado da Elastic cite um mínimo de 64.000 tokens, uma janela de contexto de 128.000 tokens (131,072 tokens) é altamente recomendada para o Elastic agente de IA (elastic-ai-agent).
    • Durante investigações de várias etapas, o agente combina instruções do sistema e definições de ferramentas com esquemas de índice, documentação ES|QL e resultados de pesquisa brutos (por exemplo, recuperando até 100 documentos de amostra para inspecionar valores de campo antes da agregação). Uma única investigação pode exceder 70.000 tokens no histórico de conversas, o que faz com que uma janela de contexto de 64.000 tokens trunque o comando no meio da conversa.
    • No Ollama, uma diretiva PARAMETER num_ctx incorporada a um Modelfile de modelo substitui a variável de ambiente OLLAMA_CONTEXT_LENGTH do contêiner. Verifique se OLLAMA_CONTEXT_LENGTH=131072 e PARAMETER num_ctx 131072 no Modelfile do modelo estão configurados (em uma GPU NVIDIA A100 ou H100 de 80 GB, gemma4:31b no contexto 128K se encaixa confortavelmente na VRAM).
  • Limite de tokens de saída e penalidade de repetição leve (por exemplo, num_predict: 8192 e repeat_penalty: 1.05 no Ollama):
    • O adaptador de stream da OpenAI do Kibana trata "finish_reason": "length" como um erro fatal Token limit reached e envia "temperature": 0 (decodificação gananciosa) em cada solicitação.
    • Para modelos capazes de raciocínio (como o gemma4:31b, que gera tokens de pensamento interno antes de emitir chamadas de ferramentas), configure um limite de tokens de saída de pelo menos 8,192 tokens e uma penalidade de repetição suave. Por exemplo, no Ollama, defina num_predict: 8192, repeat_penalty: 1.05 e repeat_last_n: 64. Evite penalidades de repetição excessivamente agressivas (como 1.15+ em janelas grandes), porque elas penalizam o modelo ao copiar uma consulta ES|QL do bloco de raciocínio interno para o JSON final de chamada de função.
  • Aceleração de GPU, Flash Attention e slots de cache KV paralelos (<60s por turno):
    • O conector da OpenAI do Kibana aplica um tempo limite HTTP padrão de 60 segundos por turno de streaming.
    • Como uma única rodada do agente pode acionar subchamadas de LLM aninhadas (por exemplo, para traduzir solicitações de pesquisa em linguagem natural em consultas ES|QL enquanto a conversa do agente principal está ativa), executar um servidor de inferência com apenas um slot de cache de KV pode fazer com que as subchamadas removam o contexto armazenado em cache da conversa principal e desacelerem as rodadas subsequentes.
    • No Ollama, defina OLLAMA_FLASH_ATTENTION=1 e OLLAMA_NUM_PARALLEL=2 para que a atenção rápida acelere a avaliação de contexto longo, e a conversa principal e as subchamadas aninhadas mantenham slots de cache KV separados na memória da GPU.

Configurar o conector de LLM no Kibana

Depois que o serviço de LLM compatível com a OpenAI estiver em execução e acessível no pod do Kibana, configure um conector da OpenAI no Kibana usando a CLI ou a UI do Kibana.

Opção 1: criar o conector usando a CLI

  1. Execute o seguinte comando POST /api/actions/connector para criar um conector do OpenAI (.gen-ai) que aponta para seu serviço de LLM (${LLM_API_URL}) com a chamada de função nativa ativada ("enableNativeFunctionCalling": true). O Kibana gera e atribui automaticamente um ID de conector exclusivo:

    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. Verifique se o conector ${LLM_CONNECTOR_NAME} existe e não tem secrets ausentes listando os conectores do Kibana e filtrando por nome:

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

    A saída será parecida com esta:

    {
      "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. Para testar a conectividade completa em tempo real entre o Kibana e o serviço de LLM na CLI, procure o ID do conector gerado automaticamente por nome e invoque o 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 .
    

    A saída será semelhante à seguinte, mostrando "status": "ok" e a resposta do modelo:

    {
      "status": "ok",
      "data": {
        "message": "Pong",
        "usage": {
          "prompt_tokens": 23,
          "prompt_tokens_details": {
            "cached_tokens": 0
          },
          "completion_tokens": 38,
          "total_tokens": 61
        }
      },
      "connector_id": "<GENERATED_CONNECTOR_ID>"
    }
    

Opção 2: criar o conector na interface do Kibana

  1. Faça login no Kibana (https://${KIBANA_IP}:5601).
  2. Abra o menu de navegação principal e acesse Gerenciamento > Alertas e insights > Conectores.
  3. Clique em Criar conector e selecione OpenAI.
  4. Configure os campos do conector:
    • Nome do conector: insira o valor de ${LLM_CONNECTOR_NAME} (por exemplo, Gemma4-31B).
    • Selecione um provedor da OpenAI: escolha Outro (serviço compatível com a OpenAI).
    • URL: insira o valor de ${LLM_API_URL} (o endpoint /v1/chat/completions do seu serviço de LLM).
    • Modelo padrão: insira o valor de ${LLM_MODEL_NAME} (por exemplo, gemma4:31b).
    • Chave de API: insira o valor de ${LLM_API_KEY} (qualquer string não vazia, se o servidor não aplicar a autenticação do portador, ou a chave de API do servidor).
    • Ativar chamada de função nativa: ative o botão de alternância.
  5. Clique em Salvar e testar para salvar o conector e abrir imediatamente a guia Teste.
  6. Na guia Teste, insira um comando de amostra (por exemplo, Reply with the single word Pong.) e clique em Executar para verificar se o Kibana recebe uma resposta válida do serviço de LLM.

Testar as integrações de LLM

Com o conjunto de dados kibana_sample_data_logs instalado e o conector de LLM verificado, você pode testar os recursos de IA do Elastic no Kibana.

Agente de IA elástico

O Agente de IA do Elastic (elastic-ai-agent) é o agente de conversação padrão integrado ao Kibana para explorar e analisar dados no Elasticsearch usando linguagem natural. Com o conector de LLM configurado e as ferramentas integradas do Agent Builder, o agente descobre automaticamente índices e mapeamentos do Elasticsearch (platform.core.list_indices, platform.core.get_index_mapping), gera e executa consultas ES|QL (platform.core.search, platform.core.execute_esql) e sintetiza as descobertas sem que você precise conhecer o esquema subjacente com antecedência. Você também pode criar seus próprios agentes personalizados com instruções e ferramentas específicas. Consulte Agentes personalizados com o Agent Builder para mais detalhes.

Para abrir e configurar o agente de IA do Elastic na interface do Kibana:

  1. Abra o menu de navegação principal no Kibana e selecione Agente de IA ou clique no botão Perguntar à IA na barra de cabeçalho superior ou navegue diretamente até https://${KIBANA_IP}:5601/app/chat.
  2. Verifique se o Agente de IA do Elastic está selecionado como o agente ativo e se o conector de LLM (${LLM_CONNECTOR_NAME}) está selecionado como o conector de modelo.

Exemplo 1: como investigar solicitações HTTP com falha e possíveis scanners

Peça ao agente de IA da Elastic para identificar os principais endereços IP de clientes que geram respostas HTTP não 200 em kibana_sample_data_logs, os países de origem e se algum desses IPs também teve tráfego HTTP 200 bem-sucedido:

  • Opção 1: pela interface do Kibana:insira a seguinte pergunta no comando de chat do Agente de IA:

    In kibana_sample_data_logs, which client IP addresses had the most failed HTTP requests (non-200 status codes), what countries did they come from, and did any of those IPs also have successful HTTP 200 requests?

  • Opção 2: pela 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
      }'
    

O que acontece nos bastidores:

  1. O agente chama platform.core.get_index_mapping(indices=["kibana_sample_data_logs"]) para inspecionar os campos disponíveis (clientip, geo.src, response, bytes, request etc.).
  2. Ele chama platform.core.search para inspecionar documentos de amostra e confirmar que os códigos de status HTTP estão armazenados em response. Em seguida, executa agregações ES|QL para classificar solicitações não 200 por clientip e geo.src e verificar quais dos principais IPs também registraram tráfego 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
    

A resposta do agente de IA do Elastic será semelhante a esta:

O endereço IP do cliente com o maior número de solicitações HTTP com falha (códigos de status diferentes de 200) em kibana_sample_data_logs é 30.156.16.164, que se originou nos Estados Unidos e registrou 100 solicitações com falha.

Outros endereços IP principais com solicitações com falha, todos também originados dos Estados Unidos, incluem:

  • 213.235.30.99: 6 falhas
  • 221.241.228.46: 6 falhas
  • 209.94.234.133: 6 falhas
  • 7.127.73.138: 5 falhas
  • 247.240.202.244: 5 falhas
  • 130.125.65.24: 5 falhas
  • 164.85.94.243: 4 falhas
  • 81.194.200.150: 4 falhas
  • 30.145.143.210: 4 falhas

Em relação às solicitações bem-sucedidas, todos os IPs listados acima tiveram pelo menos algumas respostas HTTP 200 bem-sucedidas, exceto 30.156.16.164, que não teve solicitações bem-sucedidas.

Exemplo 2: como analisar o volume de tráfego e os bytes de payload por código de status HTTP

Peça ao agente de IA do Elastic o detalhamento dos códigos de status de resposta HTTP em todas as solicitações em kibana_sample_data_logs e o total de bytes transferidos para cada código de status:

  • Opção 1: pela interface do Kibana:inicie uma nova conversa no Agente de IA e insira a seguinte pergunta:

    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?

  • Opção 2: pela 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
      }'
    

O que acontece nos bastidores:

  1. O agente inspeciona o esquema kibana_sample_data_logs usando platform.core.get_index_mapping, identificando os campos response.keyword e bytes.
  2. Ele invoca platform.core.search, que gera e executa a seguinte agregação ES|QL:

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

A resposta do Elastic agente de IA será semelhante a esta:

A análise dos códigos de status de resposta HTTP e o total de dados transferidos em kibana_sample_data_logs são os seguintes:

Código de status Contagem de solicitações Total de dados transferidos (bytes)
200 12.832 75.681.246
404 801 4.044.443
503 441 0

Agentes personalizados com o Agent Builder

O Agent Builder permite criar agentes de IA personalizados para fluxos de trabalho operacionais específicos, combinando instruções personalizadas do sistema com ferramentas integradas (como platform.core.cases, platform.core.execute_esql e platform.core.search) e ferramentas definidas pelo usuário (esql, index_search, workflow e mcp).

O exemplo a seguir cria um agente de resposta a incidentes de segurança da Web que opera diretamente no conjunto de dados kibana_sample_data_logs. Com um comando de linguagem natural, o agente:

  1. Executa uma ferramenta ES|QL personalizada (investigate_suspicious_web_clients) para agregar respostas HTTP não 200 em kibana_sample_data_logs por IP do cliente.
  2. Distingue atividades maliciosas de reconhecimento e exploração (30.156.16.164) de falhas de download benignas.
  3. Invoca uma ferramenta personalizada de fluxo de trabalho do Kibana (create_security_case) para abrir automaticamente um caso de segurança do Kibana estruturado com as descobertas e as etapas de correção.

Criar ferramentas personalizadas de ES|QL e de criação de casos

  1. Crie uma ferramenta esql personalizada chamada investigate_suspicious_web_clients que consulta kibana_sample_data_logs para os principais endereços IP de clientes que geram códigos de status HTTP não 200, além dos códigos de status, país de origem (geo.src), user agents (agent) e URLs solicitados (request):

    • Opção 1: pela interface do Kibana:

      1. No Kibana, abra o menu de navegação principal e acesse Elasticsearch > Agentes, clique em Gerenciar todas as ferramentas (ou acesse diretamente https://${KIBANA_IP}:5601/app/agent_builder/tools) e clique em Nova ferramenta.
      2. Configure os campos da ferramenta:

        • ID da ferramenta: investigate_suspicious_web_clients
        • Tipo: ES|QL
        • Descrição: 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.
        • Marcadores: security, logs
        • Consulta 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. Clique em Salvar.

    • Opção 2: pela 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}'
      
  2. Como a ferramenta platform.core.cases integrada do Agent Builder é somente leitura (usada para pesquisar e recuperar casos existentes), crie um fluxo de trabalho do Kibana (create-security-case) que invoque a etapa cases.createCase nativa do Kibana e registre-o como uma ferramenta workflow chamada create_security_case:

    • Opção 1: pela interface do Kibana:

      1. Abra o menu de navegação principal e acesse Gerenciamento > Fluxos de trabalho (ou navegue diretamente para https://${KIBANA_IP}:5601/app/workflows) e clique em Criar um novo fluxo de trabalho.
      2. No editor YAML, cole a seguinte definição de fluxo de trabalho e clique em Salvar:

        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. Volte para a página Ferramentas do Agent Builder (https://${KIBANA_IP}:5601/app/agent_builder/tools ou acesse Elasticsearch > Agentes e clique em Gerenciar todas as ferramentas) e clique em Nova ferramenta.

      4. Configure os campos da ferramenta de fluxo de trabalho e clique em Salvar:

        • ID da ferramenta: create_security_case
        • Tipo: Fluxo de trabalho
        • Fluxo de trabalho: selecione Criar caso de segurança (create-security-case)
        • Descrição: Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical).
        • Rótulos: security, cases
    • Opção 2: pela 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}'
      

Criar o agente personalizado de resposta a incidentes

Crie o agente web-security-incident-responder e atribua a ele as ferramentas investigate_suspicious_web_clients, create_security_case e platform.core.cases:

  • Opção 1: pela interface do Kibana

    1. Abra o menu de navegação principal e acesse Elasticsearch > Agentes (ou navegue diretamente para https://${KIBANA_IP}:5601/app/agent_builder/agents) e clique em Novo agente.
    2. Configure os detalhes do agente:

      • ID do agente: web-security-incident-responder
      • Nome de exibição: Web Security Incident Responder
      • Descrição de exibição: Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings.
      • Rótulos: security, incident-response
      • Instruções personalizadas:

        You are an automated Web Security Incident Responder. When asked to investigate suspicious web traffic:
        1. Call the `investigate_suspicious_web_clients` tool to inspect the top client IPs generating non-200 HTTP responses in `kibana_sample_data_logs`.
        2. Identify any client IP exhibiting malicious reconnaissance or exploitation behavior (such as Nmap scans, WordPress login probing, `/etc/passwd` path traversal, or `/level/15/exec` command execution attempts) versus benign download errors.
        3. Call the `create_security_case` tool to open a new Kibana Security Case for the malicious client IP. Set an appropriate `title`, `severity` (`high`), and a well-structured Markdown `description` summarizing the source IP, geolocation, total failed requests, observed attack categories (mapped to MITRE ATT&CK where applicable), sample malicious URLs, and recommended remediation steps.
        4. Return a concise summary of your investigation and confirm the created Kibana Case details.
        
      • Ferramentas: selecione investigate_suspicious_web_clients, create_security_case e platform.core.cases.

    3. Clique em Salvar.

  • Opção 2: pela 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}'
    

Executar o agente e verificar o caso do Kibana

  1. Inicie uma conversa com o agente Web Security Incident Responder:

    • Opção 1: pela interface do Kibana

      1. Abra o menu de navegação principal e acesse Elasticsearch > Agentes (ou navegue diretamente para https://${KIBANA_IP}:5601/app/agent_builder).
      2. Selecione Web Security Incident Responder no seletor de agentes e verifique se o conector de LLM (${LLM_CONNECTOR_NAME}) está selecionado.
      3. Insira o seguinte comando:

        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.

    • Opção 2: pela 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
        }'
      

      A saída será parecida com esta:

      {
        "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. Verifique o caso de segurança do Kibana recém-criado:

    • Opção 1: pela interface do Kibana

      1. Abra o menu de navegação principal e acesse Segurança > Casos (ou acesse diretamente https://${KIBANA_IP}:5601/app/security/cases).
      2. Clique em Tentativa de reconhecimento e exploração maliciosa de 30.156.16.164 para ver o resumo do incidente em Markdown formatado, a análise do MITRE ATT&CK, exemplos de URLs maliciosos e a lista de verificação de correção.
    • Opção 2: pela 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}'
      

      A saída será parecida com esta:

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

Descoberta de ataques

A Descoberta de Ataques é um recurso de IA no Elastic Security que analisa alertas de segurança abertos, os mapeia para o framework MITRE ATT&CK e correlaciona alertas, endereços IP e contas de usuário relacionados em cadeias de ataque visuais. Como a descoberta de ataques requer um modelo grande para sintetizar saídas JSON estruturadas com vários alertas de maneira confiável, use um modelo de classe 30B ou maior (como gemma4:31b).

Gerar alertas de segurança com base nos registros de amostra

Por padrão, o conjunto de dados kibana_sample_data_logs contém registros brutos de acesso à Web e não preenche o índice de alertas de segurança do Elastic Security (.alerts-security.alerts-default). Além disso, o gerador de dados de amostra do Kibana atribui valores futuros de @timestamp (até 30 dias à frente) às 100 solicitações de verificação e exploração de 30.156.16.164, o que as coloca fora da janela de now-30d a now consultada pelas regras de detecção do Elastic Security.

Para demonstrar a Descoberta de Ataques com os registros de amostra, mude o @timestamp dos registros de 30.156.16.164 para 30 dias atrás, enriqueça-os com os campos source.ip e user.name do Elastic Common Schema (ECS) e crie duas regras de detecção que geram alertas de segurança com base nesses eventos:

  1. Mapeie clientip para source.ip, defina user.name (www-data) e mude @timestamp para 30 dias atrás nos registros 30.156.16.164 em 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. Crie duas regras de detecção do Elastic Security direcionadas a kibana_sample_data_logs*. A primeira regra detecta sondagem de login do Nmap e do WordPress (Reconnaissance), e a segunda detecta travessia de caminho do /etc/passwd e tentativas de execução de comando 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. Aguarde cerca de 60 segundos para que a execução inicial da regra seja concluída e verifique se quatro alertas foram indexados em .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}'
    

    A saída será parecida com esta:

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

Executar a Descoberta de Ataques

  1. No Kibana, abra o menu de navegação principal e acesse Segurança > Descoberta de ataques (ou navegue diretamente para https://${KIBANA_IP}:5601/app/security/attack_discovery).
  2. Selecione o conector de LLM (${LLM_CONNECTOR_NAME}) no menu de conectores na parte de cima da página.
  3. Clique em Gerar para analisar os alertas abertos no .alerts-security.alerts-default.

Também é possível acionar e inspecionar a Descoberta de Ataques na 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}"

Quando a geração terminar (aproximadamente 60 a 90 segundos), recupere a cadeia de ataque descoberta:

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

A descoberta de ataque gerada correlaciona os quatro alertas em um único incidente com IP de origem, conta de usuário e táticas do MITRE ATT&CK vinculados:

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