פריסת תכונות מבוססות-AI גנרטיבי של Elastic Stack ב-GDC עם air gap

סקירה כללית

במדריך הזה מוסבר איך לפרוס את Elastic Stack (Elasticsearch ו-Kibana) מקצה לקצה באשכול משותף של Google Distributed Cloud (GDC) עם air gap, ואיך לשלב אותו עם שירות של מודל שפה גדול (LLM) כדי להעריך את התכונות מבוססות-AI גנרטיבי של Elastic.

הפריסה משתמשת במפעיל הרשמי של Elastic Cloud on Kubernetes ‏(ECK) שזמין ב-Marketplace של GDC עם air gap. האופרטור מותקן במרחב שמות של פרויקט באשכול Kubernetes משותף, והוא הופך את ההקצאה ואת ניהול מחזור החיים של משאבי Elasticsearch ו-Kibana לאוטומטיים. אחרי הפריסה, Kibana מתחבר לנקודת קצה (endpoint) של הסקת LLM (כמו Gemini מנוהל ב-GDC או שירות בניהול עצמי שתואם ל-OpenAI כמו Ollama או vLLM). השילוב הזה מפעיל את יכולות מבוססות-AI גנרטיבי של Elastic, כולל Elastic AI Agent, סוכנים בהתאמה אישית שנוצרו באמצעות Agent Builder ו-Attack Discovery.

המדריך הזה נכתב ל-Elastic v9.4.0, ל-Elastic Kubernetes Operator v3.4.0 ול-GDC v1.16.2. יכול להיות שחלק מההוראות יהיו שונות קצת אם אתם משתמשים בגרסאות אחרות.

ארכיטקטורה

הארכיטקטורה מורכבת מפריסת Elastic Stack שמנוהלת על ידי מפעיל ECK בתוך מרחב שמות של פרויקט באשכול משתמשים משותף של GDC. ‫Kibana מתקשרת דרך הרשת הפנימית של GDC עם נקודת קצה (endpoint) להסקת מסקנות של LLM.

דיאגרמת ארכיטקטורה של Elastic Stack.

הרכיבים העיקריים בארכיטקטורה הזו:

  • אשכול משותף ב-GDC: אשכול Kubernetes משותף ברמת הארגון שמקושר לכמה פרויקטים, ומארח חבילות תוכנה ועומסי עבודה של אפליקציות ב-GDC Marketplace.
  • פרויקט GDC: מרחב השמות של הפרויקט באשכול המשותף שמבודד את מפעיל ה-ECK, אשכול Elasticsearch, מכונת Kibana, ו-Kubernetes Secrets ו-NetworkPolicies המשויכים.
  • מפעיל Elastic Cloud on Kubernetes ‏(ECK): מותקן מ-GDC Marketplace במרחב השמות של הפרויקט. המפעיל מנהל את המשאבים המותאמים אישית Elasticsearch ו-Kibana, ומתאים את רישיון Elastic שנדרש לתכונות מבוססות-AI גנרטיבי ולמחברים חיצוניים של LLM.
  • ‫Elasticsearch: מאחסן נתונים תפעוליים ונתוני אבטחה ומוסיף אותם לאינדקס, ומריץ שאילתות חיפוש ושאילתות ES|QL שנוצרו על ידי סוכנים ותכונות מבוססות-AI של Kibana.
  • ‫Kibana: מארח את תכונות ה-AI הגנרטיבי של Elastic, כולל Elastic AI Agent (לחקירת יומנים בשפה טבעית וליצירת ES|QL), את Agent Builder (ליצירת סוכנים וכלים מותאמים אישית בממשק שיחה על סמך נתוני Elasticsearch) ואת Attack Discovery (למתאם של התראות אבטחה ולניתוח שרשרת התקפות). בנוסף, Kibana מנהל את מחברי הפעולות של ה-LLM, שמתאמים את קריאות ה-API לכלי בכמה שלבים בין המשתמש, Elasticsearch ושירות ה-LLM.
  • שירות LLM: שירות LLM מנוהל (כמו Gemini ב-GDC) או שרת הסקה בניהול עצמי (כמו Ollama או vLLM) שמספק ל-Kibana יכולות של השלמת צ'אט וקריאות API לכלי דרך הרשת הפנימית של GDC.

לפני שמתחילים

לפני שמתחילים את הפריסה, צריך לוודא שסביבת GDC עומדת בדרישות המוקדמות ובהנחות הבאות:

  • אשכול Kubernetes משותף ופרויקט GDC: יש לכם גישה לאשכול משותף קיים ב-GDC ולפרויקט ב-GDC שמקושר לאשכול הזה. הפרויקט משמש כמכל של מרחב השמות לכל המשאבים ב-Kubernetes שנפרסו במדריך הזה. מוודאים שיש באשכול מספיק משאבי מחשוב ואחסון מתמיד כדי לארח את עומסי העבודה של מפעיל ה-ECK, של Elasticsearch ושל Kibana.
  • שירות LLM: יש לכם גישה ברשת לשירות LLM, בין אם זה שירות Gemini מנוהל (אם הוא זמין בסביבת GDC שלכם) או שירות LLM בניהול עצמי שתואם ל-OpenAI (לדוגמה, gemma4:31b שמוגש דרך Ollama או vLLM בפרויקט GPU). הפריסה של תשתית ה-LLM עצמה לא נכללת במדריך הזה, אבל הדרישות הספציפיות למודל ול-API מפורטות בקטע דרישות לשירות ה-LLM.
  • מאחזרים את הגרסה של Elastic שמגיעה עם סביבת 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')
    
  • מגדירים משתני סביבה שישמשו כהפניות לאורך המדריך. משנים את הערכים האלה לפי הצורך כדי להתאים לסביבה הספציפית שלכם:

    # 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"
    
  • הגדרת gdcloud:

    gdcloud config set project ${PROJECT_ID}
    gdcloud config set core/zone ${ZONE}
    
  • מבקשים או מקשרים את תפקידי ה-IAM הנדרשים ברמת הפרויקט למשתמש בפרויקט היעד ב-GDC (${PROJECT_ID}). בהתאם למסמכי התיעוד של מחזור החיים של GDC Marketplace ולדרישות הרשת של GDC, נדרשים לפחות התפקידים הבאים בפרויקט:

    • אדמין של מרחב שמות (namespace-admin): הרשאת אדמין מלאה ליצירה ולניהול של עומסי עבודה, סודות, שירותים ומשאבים בהתאמה אישית של Kubernetes במרחב השמות של הפרויקט באשכול המשותף.
    • צפייה בפרויקט (project-viewer): גישת קריאה כדי לצפות בפרויקט הקיים ובמשאבים שלו במסוף GDC וב-CLI.
    • עריכה ב-Marketplace ‏(marketplace-editor): הרשאה להתקין, לשדרג ולהסיר חבילות תוכנה של GDC Marketplace בפרויקט.
    • אדמין של Project NetworkPolicy‏ (project-networkpolicy-admin): הרשאה להגדיר כללי ProjectNetworkPolicy גלובליים שמאפשרים לתנועה חוצת-פרויקטים ולתנועה חיצונית להגיע לשירותי Kibana ו-Elasticsearch LoadBalancer.

    בקשו ממנהל חשבון ארגוני ב-IAM או מהאדמין של הפרויקט ב-IAM להקצות את התפקידים האלה (או הריצו את הפקודות הבאות אם יש לכם הרשאות אדמין ב-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
    
  • מאחזרים את ה-kubeconfig של האשכול המשותף ומגדירים כינוי מעטפת של ku בשביל kubectl:

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

התקנת חבילת שטחי פרסום ייעודית לזירות מסחר של Elastic

בקטע הזה תתקינו את האופרטור Elastic Cloud on Kubernetes ‏(ECK) במרחב השמות של הפרויקט מ-GDC air-gapped Marketplace, לפי המדריך למחזור החיים של GDC Marketplace.

התקנה באמצעות ה-CLI של gdcloud (או מסוף GDC)

  1. מציגים את חבילות התוכנה הזמינות ב-Marketplace כדי לאשר את שם השירות ואת הגרסה הזמינה:

    gdcloud marketplace services list
    

    הפלט אמור להיראות כך:

    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. מתקינים את חבילת שטחי פרסום ייעודית לזירות מסחר באשכול המשותף של היעד:

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

    *(לחלופין, במסוף GDC, בוחרים את הפרויקט ${PROJECT_ID}, עוברים אל Marketplace, בוחרים את האפליקציה Elastic Cloud on Kubernetes, לוחצים על Install, בוחרים את האשכול ${CLUSTER_NAME} ולוחצים על Install).*

  3. מוודאים שה-StatefulSet וה-pod של elastic-operator פועלים במרחב השמות של הפרויקט (${PROJECT_ID}):

    ku get statefulset,pods -n ${PROJECT_ID}
    

    הפלט אמור להיראות כך:

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

פריסת Elastic Stack

יצירת המניפסט של Elastic Stack

החבילה של GDC Marketplace פורסת את מפעיל ה-ECK שמוגדר מראש לשליפת קובצי אימג' של קונטיינרים של Elasticsearch ו-Kibana מהמרשם המקומי של Harbor עם air gap.

יוצרים את elastic-stack.yaml כדי לפרוס אשכול Elasticsearch עם צומת אחד, מכונה של ממשק המשתמש של Kibana, שירותי LoadBalancer HTTP ו-allow-all NetworkPolicy במרחב השמות של הפרויקט (${PROJECT_ID}). המניפסט כולל גם רישיון Secret שתואם למפעיל ECK, שמוגדר כאן כגרסת ניסיון של 30 יום ל-Enterprise‏ (eck-trial-license) למקרה שעדיין אין לכם רישיון 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

החלה ואימות של הפריסה

מחילים את המניפסט:

ku apply -f elastic-stack.yaml

מחילים ProjectNetworkPolicy גלובלי כדי שכללי חומת האש של GDC יאפשרו תנועה נכנסת לכתובות ה-IP של LoadBalancer וגם תנועה בין פרויקטים (כמו תקשורת עם שירות ה-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

עוקבים אחרי הפודים, השירותים והמשאבים בהתאמה אישית עד ש-Elasticsearch ו-Kibana מגיעים לסטטוס Running ו-Ready ומקבלים כתובות IP של LoadBalancer ב-${CLUSTER_NAME}:

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

כשהמשאבים מוכנים, הפלט אמור להיראות כך:

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

מייצאים את כתובות ה-IP החיצוניות של LoadBalancer שהוקצו ל-Elasticsearch ול-Kibana, כדי להשתמש בהן בשלבים הבאים:

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"

אחזור פרטי הכניסה וגישה לממשק המשתמש של Kibana

אחזור הסיסמה של משתמש-העל ב-elastic

‫ECK יוצר באופן אוטומטי סוד של Kubernetes (elastic-cluster-es-elastic-user) שמכיל את סיסמת ברירת המחדל של משתמש-העל elastic. אפשר לאחזר ולפענח אותו באמצעות:

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

אימות החיבור והתחברות

  1. מוודאים שאפשר לגשת ל-Elasticsearch דרך HTTPS מתחנת העבודה:

    curl -sk -u "elastic:${ELASTIC_PASSWORD}" "https://${ELASTICSEARCH_IP}:9200"
    
  2. פותחים את https://${KIBANA_IP}:5601 בדפדפן, מאשרים את האזהרה לגבי האישור בחתימה עצמית ונכנסים באמצעות:

    • שם משתמש: elastic
    • סיסמה: <output from ELASTIC_PASSWORD above>

אימות הרישיון של Elastic

כדי להשתמש בתכונות מבוססות-AI גנרטיבי של Elastic (כולל סוכן AI של Elastic, ‏ Agent Builder ו-Attack Discovery) ובמחברים חיצוניים של LLM, צריך רישיון Enterprise (או רישיון Enterprise פעיל ל-30 ימי ניסיון).

כשאשכול Elasticsearch מנוהל על ידי מפעיל ה-ECK, ה-license-controller של ECK מתאים באופן רציף את רישיון האשכול ל-Kubernetes Secrets במרחב השמות של המפעיל (${PROJECT_ID}). אם עדיין אין לכם רישיון Enterprise לסביבת ייצור, ה-Secret של eck-trial-license שכלול ב-elastic-stack.yaml (license.k8s.elastic.co/type: enterprise_trial) מורה ל-ECK להקצות ולתחזק באופן אוטומטי רישיון Enterprise לניסיון למשך 30 יום. (שימו לב שאם תתחילו תקופת ניסיון באופן ידני דרך POST /_license/start_trial בלי סוד רישיון ECK,‏ ECK יחזיר את האשכול למצב basic במחזור ההתאמה הבא).

כדי לאמת את הרישיון הפעיל באמצעות ה-CLI:

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

*(לחלופין, ב-Kibana, פותחים את תפריט הניווט ועוברים אל Management > Dev Tools, ואז מריצים את GET /_license בכרטיסייה Console).*

הפלט אמור להיראות כך, עם "status": "active" ו-"type": "enterprise" (או "type": "trial" אם משתמשים ברישיון לניסיון):

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

טעינת מערך נתונים מובנה לדוגמה

כדי להעריך את תכונות ה-AI של Elastic בלי לכתוב צינורות עיבוד נתונים מותאמים אישית להטמעת נתונים או לדרוש גישה לאינטרנט החיצוני, אפשר לטעון את מערך הנתונים המובנה של Kibana,‏ Sample web logs‏ (kibana_sample_data_logs). מכיוון שארכיון הנתונים לדוגמה כלול בקובץ האימג' של קונטיינר Kibana, הוא מותקן תוך שניות בסביבה עם air gap. קבוצת הנתונים מאכלסת 14,074 מסמכים של יומני גישה לאינטרנט שמכילים את כתובות ה-IP של הלקוחות (clientip), קודי סטטוס של HTTP‏ (response), מדינות המקור והיעד (geo.src, geo.dest), בייטים שהועברו (bytes), כתובות URL ומחרוזות של סוכני משתמשים.

במאמרים Add sample data in Kibana ו-elastic-datasets-builtin.md מפורטים מערכי נתונים מובְנים נוספים (flights ו-ecommerce).

טעינת מערך הנתונים

מריצים את הפקודה הבאה כדי לטעון את kibana_sample_data_logs דרך נקודת הקצה הפנימית של נתונים לדוגמה של 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"

אימות מערך הנתונים לדוגמה

מוודאים שקבוצת הנתונים לדוגמה logs מסומנת כ-installed ב-Kibana, ומוודאים שזרם הנתונים התומך מכיל מסמכי 14,074 ב-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"

הפלט אמור להיראות כך:

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

דרישות לגבי שירות LLM

בסביבת GDC עם air gap, אי אפשר להגיע למודלים מסוג LLM שמנוהלים על ידי Elastic ומארחים בענן, ולספקי SaaS LLM ציבוריים חיצוניים. בהתאם לסביבת GDC:

  • ‫Gemini מנוהל ב-GDC: בסביבות GDC שמציעות את Gemini כשירות LLM מנוהל, אתם יכולים לחבר את Kibana ישירות לשירות Gemini המנוהל, אם הוא זמין.
  • שירות LLM בניהול עצמי: לחלופין, אתם יכולים לחבר את Elastic לשירות LLM בניהול עצמי שמתארח בסביבת GDC (לדוגמה, בצומתי GPU של GDC).

המדריך הזה לא עוסק בפריסה של שירות LLM בניהול עצמי. כשמקצים או בוחרים שירות LLM בניהול עצמי ל-Elastic, צריך לוודא שהוא עומד בדרישות שנגזרות מהמדריכים הרשמיים של Elastic:

סיכום הדרישות הטכניות

הנקודות הבאות מסכמות את הדרישות העיקריות של ה-API, יכולות המודל, חלון ההקשר וזמן הריצה שנדרשות כדי ששירות LLM בניהול עצמי יפעל בצורה מהימנה עם סוכן AI,‏ Agent Builder ו-Attack Discovery של Elastic. שימו לב שהשמות של פרמטרים ספציפיים להגדרה שמוזכרים בקטעים הבאים (כמו num_ctx,‏ num_predict,‏ repeat_penalty,‏ repeat_last_n,‏ OLLAMA_FLASH_ATTENTION ו-OLLAMA_NUM_PARALLEL) מופיעים בדוגמה של Ollama. אם אתם משתמשים במנוע הסקה אחר כמו vLLM, צריך להחיל את ההגדרות המקבילות.

  • ‫API להשלמת צ'אט שתואם ל-OpenAI‏ (/v1/chat/completions) עם קריאה להפעלת פונקציות מותאמות:
    • כל שרת הסקה בניהול עצמי (כמו Ollama או vLLM) שחושף נקודת קצה (endpoint) של /v1/chat/completions שתואמת ל-OpenAI (כולל סטרימינג של אירועים שנשלחו מהשרת דרך "stream": true) יכול להשתלב באמצעות מחבר OpenAI של Kibana, כשהספק מוגדר כ-Other (OpenAI Compatible Service).
    • מפעילים קריאה להפעלת פונקציות מבוססות-ענן ("enableNativeFunctionCalling": true) במחבר של Kibana כשמשתמשים במודלים שתומכים בקריאת API לכלי מבוססי-ענן (כמו gemma4:31b). כברירת מחדל ("enableNativeFunctionCalling": false), Kibana משתמש בקריאה לפונקציות מדומות שמבוססות על פרומפטים, שהיא פחות אמינה במהלך בקשות להפעלת פונקציה מקוננות ויכולה להיכשל עם שגיאות ניתוח (כמו Error: Missing name for tool use). הפעלת קריאה להפעלת פונקציות מבוססות-ענן מעבירה את עיצוב קריאת API לכלי ישירות לפרוטוקול OpenAI tools של שרת ההיקש.
  • מודל שעבר כוונון לפי הוראות, עם יכולות מובנות של כלים וקריאה להפעלת פונקציות:
    • ‫Elastic AI Agent (elastic-ai-agent) וסוכנים בהתאמה אישית של Agent Builder מסתמכים על קריאות API לכלי בכמה שלבים (platform.core.list_indices, platform.core.get_index_mapping, platform.core.search, platform.core.execute_esql ו-platform.core.cases). הם משתמשים בכלים האלה כדי לגלות מיפויים של אינדקסים, ליצור ולהריץ שאילתות ES|QL ב-Elasticsearch ולסנתז ממצאים.
    • המודל שנבחר צריך להיות מכוונן לפי הוראות ומסוגל ליצור קריאות תקינות לכלים מבוססי-ענן, פלטים מובְנים של JSON ושאילתות ES|QL. כדי לבדוק את הקריטריונים להערכת המודל, עיינו במאמרים של Elastic בנושא מטריצת הביצועים של LLM ל-Observability ומטריצת הביצועים של LLM ל-Elastic Security.
  • חלון הקשר של לפחות 128,000 טוקנים (לדוגמה, num_ctx: 131072 ב-Ollama):
    • במסמכי התיעוד הכלליים של Elastic בנושא מודל LLM מותאם אישית בניהול עצמי מצוין מינימום של 64,000 טוקנים, אבל מומלץ מאוד להשתמש בחלון ההקשר של 128,000 טוקנים (131,072 טוקנים) בשביל Elastic סוכן AI‏ (elastic-ai-agent).
    • במהלך חקירות בכמה שלבים, הנציג משלב הוראות מערכת והגדרות של כלים עם סכימות של אינדקסים, מסמכי תיעוד של ES|QL ותוצאות חיפוש גולמיות (לדוגמה, אחזור של עד 100 מסמכים לדוגמה כדי לבדוק את ערכי השדות לפני הצבירה). בחקירה אחת, היסטוריית השיחה יכולה לחרוג מ-70,000 טוקנים, מה שגורם לחלון הקשר של 64,000 טוקנים לחתוך את ההנחיה באמצע השיחה.
    • חשוב לשים לב שב-Ollama, הוראת PARAMETER num_ctx שמוטמעת ב-Modelfile של המודל מבטלת את משתנה הסביבה OLLAMA_CONTEXT_LENGTH של הקונטיינר. מוודאים שגם OLLAMA_CONTEXT_LENGTH=131072 וגם PARAMETER num_ctx 131072 מוגדרים ב-Modelfile של המודל (ב-GPU של NVIDIA A100 או H100 בנפח 80GB,‏ gemma4:31b בהקשר של 128K מתאים ל-VRAM).
  • מגבלת טוקנים לפלט וקנס קל על חזרה על מילים (לדוגמה, num_predict: 8192 ו-repeat_penalty: 1.05 ב-Ollama):
    • מתאם הסטרימינג של OpenAI ב-Kibana מתייחס ל-"finish_reason": "length" בתור שגיאת Token limit reached חמורה ושולח "temperature": 0 (פענוח חמדני) בכל בקשה.
    • במודלים עם יכולת הסקת מסקנות (כמו gemma4:31b, שיוצר אסימוני שלבים בתהליך החשיבה פנימיים לפני שהוא מפעיל קריאות API לכלי), צריך להגדיר מגבלת אסימונים של לפחות 8,192 אסימונים וקנס קל על חזרה על מילים. לדוגמה, ב-Ollama, מגדירים את num_predict: 8192, repeat_penalty: 1.05 ו-repeat_last_n: 64. חשוב להימנע מקנסות אגרסיביים מדי על חזרה על מילים (כמו 1.15+ בחלונות גדולים), כי הם פוגעים במודל כשהוא מעתיק שאילתת ES|QL מבלוק החשיבה הפנימי שלו ל-JSON הסופי של קריאת ה-API לכלי.
  • האצת GPU,‏ Flash Attention ומשבצות מקבילות של מטמון KV (פחות מ-60 שניות לכל תור):
    • המחבר של OpenAI ב-Kibana אוכף זמן קצוב לתפוגה של HTTP כברירת מחדל של 60 שניות לכל תור בסטרימינג.
    • בגלל שפנייה אחת לנציג יכולה להפעיל קריאות משנה מקוננות ל-LLM (לדוגמה, כדי לתרגם בקשות חיפוש בשפה טבעית לשאילתות ES|QL בזמן שהשיחה הראשית עם הנציג פעילה), הפעלת שרת הסקה עם משבצת אחת בלבד של מטמון KV יכולה לגרום לקריאות המשנה למחוק את ההקשר שנשמר במטמון של השיחה הראשית ולהאט את הפניות הבאות.
    • ב-Ollama, מגדירים את OLLAMA_FLASH_ATTENTION=1 ו-OLLAMA_NUM_PARALLEL=2 כך ש-Flash Attention יאיץ את הערכת ההקשר הארוך, והשיחה הראשית והקריאות המשניות המקוננות ישמרו על משבצות נפרדות של מטמון KV בזיכרון ה-GPU.

הגדרת המחבר של ה-LLM ב-Kibana

אחרי ששירות ה-LLM שתואם ל-OpenAI פועל ואפשר להגיע אליו מה-Pod של Kibana, מגדירים מחבר OpenAI ב-Kibana באמצעות ה-CLI או ממשק המשתמש של Kibana.

אפשרות 1: יצירת המחבר באמצעות ה-CLI

  1. מריצים את הפקודה הבאה של POST /api/actions/connector כדי ליצור מחבר OpenAI‏ (.gen-ai) שמפנה לשירות ה-LLM‏ (${LLM_API_URL}) עם קריאה להפעלת פונקציות מקורית ("enableNativeFunctionCalling": true). Kibana יוצר ומקצה באופן אוטומטי מזהה מחבר ייחודי:

    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. כדי לוודא שהמחבר ${LLM_CONNECTOR_NAME} קיים ואין בו סודות חסרים, מציגים את המחברים של Kibana ומסננים לפי שם:

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

    הפלט אמור להיראות כך:

    {
      "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. כדי לבדוק את הקישוריות מקצה לקצה בין Kibana לבין שירות ה-LLM מה-CLI, חפשו את מזהה המחבר שנוצר אוטומטית לפי השם שלו והפעילו את נקודת הקצה (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 .
    

    הפלט אמור להיראות כך, עם "status": "ok" והתשובה של המודל:

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

אפשרות 2: יצירת המחבר דרך ממשק המשתמש של Kibana

  1. נכנסים ל-Kibana (https://${KIBANA_IP}:5601).
  2. פותחים את תפריט הניווט הראשי ועוברים אל Management ‏> Alerts and Insights ‏> Connectors.
  3. לוחצים על Create connector ובוחרים באפשרות OpenAI.
  4. מגדירים את השדות של המחבר:
    • ‫Connector name: מזינים את הערך של ${LLM_CONNECTOR_NAME} (לדוגמה, Gemma4-31B).
    • Select an OpenAI provider: בוחרים באפשרות Other (OpenAI Compatible Service).
    • כתובת URL: מזינים את הערך של ${LLM_API_URL} (נקודת הקצה של /v1/chat/completions בשירות ה-LLM).
    • מודל ברירת המחדל: מזינים את הערך של ${LLM_MODEL_NAME} (לדוגמה, gemma4:31b).
    • מפתח API: מזינים את הערך של ${LLM_API_KEY} (כל מחרוזת לא ריקה אם השרת לא אוכף אימות של טוקן למוכ"ז, או את מפתח ה-API של השרת).
    • הפעלת קריאה להפעלת פונקציות מקוריות: מפעילים את המתג.
  5. לוחצים על Save & test כדי לשמור את המחבר ולפתוח מיד את הכרטיסייה Test.
  6. בכרטיסייה Test, מזינים הנחיה לדוגמה (למשל, Reply with the single word Pong.) ולוחצים על Run כדי לוודא ש-Kibana מקבלת תשובה תקינה משירות ה-LLM.

בדיקת השילובים של מודל ה-LLM

אחרי שמתקינים את קבוצת הנתונים kibana_sample_data_logs ומאמתים את מחבר ה-LLM, אפשר לבדוק את יכולות ה-AI של Elastic ב-Kibana.

Elastic AI Agent

‫Elastic AI Agent (elastic-ai-agent) הוא סוכן ברירת המחדל בממשק שיחה שמשולב ב-Kibana, שמאפשר לחקור ולנתח נתונים ב-Elasticsearch באמצעות שפה טבעית. הנציג מופעל באמצעות מחבר ה-LLM שהגדרתם וכלים מובנים של Agent Builder, והוא מגלה באופן אוטומטי אינדקסים ומיפויים של Elasticsearch (platform.core.list_indices, platform.core.get_index_mapping), יוצר ומריץ שאילתות ES|QL (platform.core.search, platform.core.execute_esql) ומסנתז ממצאים בלי שתצטרכו להכיר את הסכימה הבסיסית מראש. אתם יכולים גם ליצור סוכנים בהתאמה אישית עם הוראות וכלים מותאמים. פרטים נוספים זמינים במאמר סוכנים בהתאמה אישית באמצעות Agent Builder.

כדי לפתוח ולהגדיר את Elastic AI Agent בממשק המשתמש של Kibana:

  1. פותחים את תפריט הניווט הראשי ב-Kibana ובוחרים באפשרות סוכן AI (או לוחצים על הכפתור Ask AI בסרגל הכותרת העליון, או עוברים ישירות אל https://${KIBANA_IP}:5601/app/chat).
  2. מוודאים ש-Elastic AI Agent נבחר כסוכן AI הפעיל, ושהמחבר של ה-LLM‏ (${LLM_CONNECTOR_NAME}) נבחר כמחבר המודל.

דוגמה 1: חקירת בקשות HTTP שנכשלו וסורקים פוטנציאליים

מבקשים מסוכן ה-AI של Elastic לזהות את כתובות ה-IP המובילות של הלקוחות שיוצרות תגובות HTTP שאינן 200 ב-kibana_sample_data_logs, את מדינות המקור שלהן, ואם לאחת מכתובות ה-IP האלה הייתה גם תנועת HTTP 200 מוצלחת:

  • אפשרות 1: דרך ממשק המשתמש של Kibana: מזינים את השאלה הבאה בהנחיה לצ'אט של סוכן ה-AI:

    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?

  • אפשרות 2: דרך ה-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
      }'
    

מה קורה מאחורי הקלעים:

  1. הנציג קורא ל-platform.core.get_index_mapping(indices=["kibana_sample_data_logs"]) כדי לבדוק את השדות הזמינים (clientip,‏ geo.src,‏ response,‏ bytes,‏ request וכו').
  2. הוא קורא ל-platform.core.search כדי לבדוק מסמכים לדוגמה ולוודא שקודי הסטטוס של HTTP מאוחסנים ב-response, ואז מפעיל צבירות של ES|QL כדי לדרג בקשות שאינן 200 לפי clientip ו-geo.src ולבדוק אילו מכתובות ה-IP המובילות תיעדו גם תנועה של 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
    

התשובה של Elastic AI Agent אמורה להיראות כך:

כתובת ה-IP של הלקוח עם הכי הרבה בקשות HTTP שנכשלו (קודי סטטוס שאינם 200) ב-kibana_sample_data_logs היא 30.156.16.164, שמקורה בארצות הברית, ובה נרשמו 100 בקשות שנכשלו.

כתובות IP מובילות אחרות עם בקשות שנכשלו, שכולן הגיעו גם מארצות הברית:

  • ‫213.235.30.99: 6 כשלים
  • ‫221.241.228.46: ‏6 כשלים
  • ‫209.94.234.133: ‏6 כשלים
  • thought ‫7.127.73.138: ‏5 כשלים
  • ‫247.240.202.244: ‏5 כשלים
  • ‫130.125.65.24: ‏5 כשלים
  • ‫164.85.94.243: 4 כשלים
  • ‫81.194.200.150: 4 כשלים
  • ‫30.145.143.210: 4 כשלים

לגבי בקשות שהצליחו, כל כתובות ה-IP שרשומות למעלה קיבלו לפחות כמה תגובות HTTP 200 מוצלחות, חוץ מ-30.156.16.164, שלא קיבלה בקשות מוצלחות.

דוגמה 2: ניתוח נפח התנועה והבייטים של המטען הייעודי (payload) לפי קוד סטטוס של HTTP

מבקשים מסוכן ה-AI של Elastic פירוט של קודי הסטטוס של תגובות ה-HTTP בכל הבקשות ב-kibana_sample_data_logs, ואת מספר הבייטים הכולל שהועברו לכל קוד סטטוס:

  • אפשרות 1: דרך ממשק המשתמש של Kibana: מתחילים שיחה חדשה ב-סוכן AI ומזינים את השאלה הבאה:

    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?

  • אפשרות 2: דרך ה-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
      }'
    

מה קורה מאחורי הקלעים:

  1. הנציג בודק את הסכימה של kibana_sample_data_logs באמצעות platform.core.get_index_mapping, ומזהה את השדות response.keyword ו-bytes.
  2. הוא מפעיל את platform.core.search, שיוצר ומריץ את הצבירה הבאה של ES|QL:

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

התשובה של Elastic AI Agent אמורה להיראות כך:

פירוט קודי הסטטוס של תגובות HTTP וסך הנתונים שהועברו ב-kibana_sample_data_logs:

קוד סטטוס מספר הבקשות סה"כ נתונים שהועברו (בייטים)
200 12,832 75,681,246
404 801 4,044,443
503 441 0

סוכנים בהתאמה אישית באמצעות Agent Builder

ב-Agent Builder אתם יכולים ליצור סוכני AI בהתאמה אישית לתהליכי עבודה תפעוליים ספציפיים. כדי לעשות את זה, אתם יכולים לשלב הוראות מערכת בהתאמה אישית עם כלים מובנים (כמו platform.core.cases, platform.core.execute_esql ו-platform.core.search) וכלים שמוגדרים על ידי המשתמש (esql, index_search, workflow ו-mcp).

בדוגמה הבאה אנחנו יוצרים סוכן Web Security Incident Responder שפועל ישירות על מערך הנתונים kibana_sample_data_logs. כשנותנים לסוכן הנחיה בשפה טבעית, הוא:

  1. מריץ כלי ES|QL מותאם אישית (investigate_suspicious_web_clients) כדי לצבור תגובות HTTP שאינן 200 ב-kibana_sample_data_logs לפי כתובת ה-IP של הלקוח.
  2. מבחין בין פעילות זדונית של איסוף מידע וניצול לרעה (30.156.16.164) לבין כשלים תקינים בהורדה.
  3. מפעיל כלי מותאם אישית של Kibana Workflow‏ (create_security_case) כדי לפתוח באופן אוטומטי אירוע אבטחה מובנה ב-Kibana עם הממצאים ושלבי התיקון.

ליצור כלים מותאמים אישית של ES|QL וליצירת פניות

  1. יוצרים כלי esql בהתאמה אישית בשם investigate_suspicious_web_clients ששולח שאילתות ל-kibana_sample_data_logs כדי לקבל את כתובות ה-IP של הלקוחות המובילים שיוצרים קודי סטטוס HTTP שאינם 200, יחד עם קודי הסטטוס שלהם, מדינת המקור (geo.src), סוכני המשתמש (agent) וכתובות ה-URL המבוקשות (request):

    • אפשרות 1: דרך ממשק המשתמש של Kibana:

      1. ב-Kibana, פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents, ואז לוחצים על Manage all tools (או עוברים ישירות אל https://${KIBANA_IP}:5601/app/agent_builder/tools) ולוחצים על New tool.
      2. מגדירים את שדות הכלי:

        • מזהה הכלי: investigate_suspicious_web_clients
        • סוג: ES|QL
        • תיאור: 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.
        • תוויות: security, logs
        • שאילתת 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. לוחצים על Save.

    • אפשרות 2: דרך ה-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. הכלי המובנה platform.core.cases של Agent Builder הוא לקריאה בלבד (משמש לחיפוש ולאחזור של פניות קיימות), לכן צריך ליצור תהליך עבודה ב-Kibana‏ (create-security-case) שמפעיל את השלב המקורי cases.createCase של Kibana, ולרשום אותו ככלי workflow בשם create_security_case:

    • אפשרות 1: דרך ממשק המשתמש של Kibana:

      1. פותחים את תפריט הניווט הראשי ועוברים אל Management > Workflows (או עוברים ישירות אל https://${KIBANA_IP}:5601/app/workflows), ואז לוחצים על Create a new workflow.
      2. בעורך ה-YAML, מדביקים את הגדרת תהליך העבודה הבאה ולוחצים על Save:

        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. חוזרים לדף Tools ב-Agent Builder (https://${KIBANA_IP}:5601/app/agent_builder/tools, או עוברים אל Elasticsearch > Agents ולוחצים על Manage all tools) ולוחצים על New tool.

      4. מגדירים את השדות של כלי תהליך העבודה ולוחצים על שמירה:

        • מזהה הכלי: create_security_case
        • סוג: תהליך עבודה
        • תהליך עבודה: בוחרים באפשרות יצירת בקשת תמיכה בנושא אבטחה (create-security-case)
        • תיאור: Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical).
        • תוויות: security, cases
    • אפשרות 2: דרך ה-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}'
      

יצירת סוכן בהתאמה אישית לטיפול באירועים

יוצרים את הסוכן web-security-incident-responder ומקצים לו את הכלים investigate_suspicious_web_clients,‏ create_security_case ו-platform.core.cases:

  • אפשרות 1: דרך ממשק המשתמש של Kibana:

    1. פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents (או עוברים ישירות אל https://${KIBANA_IP}:5601/app/agent_builder/agents) ולוחצים על New agent.
    2. מגדירים את פרטי הסוכן:

      • מזהה הנציג: web-security-incident-responder
      • שם לתצוגה: Web Security Incident Responder
      • תיאור המסך: Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings.
      • תוויות: security, incident-response
      • הוראות מותאמות אישית:

        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.
        
      • כלים: בוחרים באפשרויות investigate_suspicious_web_clients, create_security_case ו-platform.core.cases.

    3. לוחצים על Save.

  • אפשרות 2: דרך ה-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}'
    

הפעלת הנציג ואימות ה-Kibana Case

  1. מנהלים שיחה עם הסוכן Web Security Incident Responder:

    • אפשרות 1: דרך ממשק המשתמש של Kibana:

      1. פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents (או עוברים ישירות אל https://${KIBANA_IP}:5601/app/agent_builder).
      2. בבורר הנציגים, בוחרים באפשרות Web Security Incident Responder ומוודאים שהמחבר של ה-LLM‏ (${LLM_CONNECTOR_NAME}) נבחר.
      3. מזינים את ההנחיה הבאה:

        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.

    • אפשרות 2: דרך ה-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
        }'
      

      הפלט אמור להיראות כך:

      {
        "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. מאמתים את בקשת התמיכה החדשה בנושא אבטחה ב-Kibana:

    • אפשרות 1: דרך ממשק המשתמש של Kibana:

      1. פותחים את תפריט הניווט הראשי ועוברים אל אבטחה > פניות (או עוברים ישירות אל https://${KIBANA_IP}:5601/app/security/cases).
      2. לוחצים על Malicious Reconnaissance and Exploitation Attempt from 30.156.16.164 כדי לראות את סיכום האירוע בפורמט Markdown, ניתוח MITRE ATT&CK, כתובות URL זדוניות לדוגמה ורשימת משימות לתיקון.
    • אפשרות 2: דרך ה-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}'
      

      הפלט אמור להיראות כך:

      {
        "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 הוא פיצ'ר מבוסס-AI ב-Elastic Security שמנתח התראות אבטחה פתוחות, ממפה אותן למסגרת MITRE ATT&CK ומקשר בין התראות, כתובות IP וחשבונות משתמשים קשורים לשרשראות תקיפה חזותיות. כדי ש-Attack Discovery יסנתז פלטים מובְנים של JSON עם כמה התראות בצורה מהימנה, צריך מודל גדול, אז השתמשו במודל מסוג 30B או גדול יותר (כמו gemma4:31b).

יצירת התראות אבטחה מיומני הדוגמה

כברירת מחדל, קבוצת הנתונים kibana_sample_data_logs מכילה יומני גישה גולמיים לאינטרנט ולא מאכלסת את אינדקס התראות האבטחה של Elastic Security‏ (.alerts-security.alerts-default). בנוסף, מחולל הנתונים לדוגמה של Kibana מקצה ערכי @timestamp עתידיים (עד 30 יום קדימה) ל-100 בקשות הסריקה והניצול מ-30.156.16.164, מה שמציב אותן מחוץ לחלון now-30d עד now שנשאל על ידי כללי הזיהוי של Elastic Security.

כדי להדגים את Attack Discovery באמצעות היומנים לדוגמה, העבירו את @timestamp של הרשומות של 30.156.16.164 30 ימים אחורה, העשירו אותן בשדות source.ip ו-user.name של Elastic Common Schema ‏(ECS) וצרו שני כללי זיהוי שיוצרים התראות אבטחה מהאירועים האלה:

  1. ממפים את clientip ל-source.ip, מגדירים את user.name (www-data) ומזיזים את @timestamp אחורה ב-30 ימים ברשומות של 30.156.16.164 ב-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. יוצרים שני כללי זיהוי של Elastic Security שמטרגטים את kibana_sample_data_logs*. הכלל הראשון מזהה סריקה של Nmap ושל התחברות ל-WordPress‏ (Reconnaissance), והכלל השני מזהה ניסיונות של מעבר נתיב ב-/etc/passwd והרצת פקודות מרחוק (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. ממתינים כ-60 שניות עד שהכלל יופעל בפעם הראשונה, ואז מוודאים שנוספו 4 התראות לאינדקס ב-.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}'
    

    הפלט אמור להיראות כך:

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

הפעלת Attack Discovery

  1. ב-Kibana, פותחים את תפריט הניווט הראשי ועוברים אל Security > Attack Discovery (או מנווטים ישירות אל https://${KIBANA_IP}:5601/app/security/attack_discovery).
  2. בתפריט המחברים שבחלק העליון של הדף, בוחרים את המחבר של ה-LLM‏ (${LLM_CONNECTOR_NAME}).
  3. לוחצים על Generate כדי לנתח את ההתראות הפתוחות ב-.alerts-security.alerts-default.

אפשר גם להפעיל ולבדוק את Attack Discovery מ-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}"

אחרי שהיצירה מסתיימת (בערך 60-90 שניות), מאחזרים את שרשרת ההתקפה שזוהתה:

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

התכונה "זיהוי התקפות" מקשרת בין ארבע ההתראות לאירוע אחד עם כתובת IP מקורית, חשבון משתמש וטקטיקות MITRE ATT&CK:

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