פריסת תכונות מבוססות-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 עם נקודת קצה להסקת LLM.

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

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

  • אשכול משותף של GDC: אשכול Kubernetes משותף בהיקף הארגון שמצורף לכמה פרויקטים ומארח חבילות תוכנה ועומסי עבודה של אפליקציות מ-GDC Marketplace.
  • פרויקט GDC: מרחב השמות של הפרויקט באשכול המשותף שמבודד את מפעיל ה-ECK, אשכול Elasticsearch, מכונת Kibana, ו-Kubernetes Secrets ו-NetworkPolicies המשויכים.
  • ‫Elastic Cloud on Kubernetes (ECK) operator: מותקן מ-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, שמתאמים את הקריאות לפונקציות בכמה שלבים בין המשתמש, Elasticsearch ושירות ה-LLM.
  • שירות LLM: שירות LLM מנוהל (כמו Gemini ב-GDC) או שרת הסקה בניהול עצמי (כמו Ollama או vLLM) שמספק ל-Kibana יכולות של השלמת צ'אט וקריאה לכלים דרך הרשת הפנימית של 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 בפרויקט.
    • אדמין של NetworkPolicy בפרויקט (project-networkpolicy-admin): הרשאה להגדיר כללי ProjectNetworkPolicy גלובליים שמאפשרים לתנועה מפרויקטים אחרים ומחוץ לפרויקט להגיע לשירותי LoadBalancer של Kibana ו-Elasticsearch.

    בקשו מאדמין ה-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) במרחב השמות של הפרויקט מ-Marketplace של GDC עם air gap, לפי המדריך למחזור החיים של 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 של איזון העומסים, וגם תעבורה בין פרויקטים (למשל, תקשורת עם שירות ה-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 ימי ניסיון).

כשמפעיל ECK מנהל אשכול Elasticsearch, ‏ ECK‏ license-controller מבצע באופן רציף תהליך של התאמת רישיון האשכול ל-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 (kibana_sample_data_logs). מכיוון שארכיון הנתונים לדוגמה מקובץ בתוך קובץ האימג' של הקונטיינר של Kibana, הוא מותקן תוך שניות בסביבה עם air gap. קבוצת הנתונים מכילה 14,074 מסמכי יומן גישה לאינטרנט עם כתובות ה-IP של הלקוח (clientip), קודי סטטוס של HTTP‏ (response), מדינות המקור והיעד (geo.src,‏ geo.dest), בייטים שהועברו (bytes), כתובות URL ומחרוזות של סוכן משתמש.

אפשר לעיין במאמרים הוספת נתוני דוגמה ב-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 Managed שמתארחים בענן ולספקי מודלים מסוג LLM של SaaS ציבורי חיצוניים. בהתאם לסביבת GDC:

  • ‫Managed 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, אתם צריכים להחיל את ההגדרות המקבילות.

  • OpenAI-compatible chat completions API (/v1/chat/completions) with native function calling:
    • אפשר לשלב כל שרת הסקה בניהול עצמי (כמו Ollama או vLLM) שחושף נקודת קצה /v1/chat/completions שתואמת ל-OpenAI (כולל סטרימינג של אירועים שנשלחים מהשרת באמצעות "stream": true) באמצעות מחבר OpenAI של Kibana, כשהספק מוגדר לאחר (שירות שתואם ל-OpenAI).
    • מפעילים קריאה להפעלת פונקציות מקוריות ("enableNativeFunctionCalling": true) במחבר של Kibana כשמשתמשים במודלים שתומכים בקריאה להפעלת כלים מבוססי-ענן (כמו gemma4:31b). כברירת מחדל ("enableNativeFunctionCalling": false), Kibana משתמש בקריאה להפעלת פונקציות מדומות שמבוססות על הנחיות, שהיא פחות אמינה במהלך קריאות להפעלת כלים בתצוגת עץ ויכולה להיכשל עם שגיאות ניתוח (כמו Error: Missing name for tool use). הפעלת קריאה להפעלת פונקציות מקוריות מעבירה את העיצוב של הקריאה להפעלת כלים ישירות לפרוטוקול OpenAI tools של שרת ההסקה.
  • מודל שעבר כוונון לפי הוראות עם יכולות מובנות של שימוש בכלים וקריאה לפונקציות:
    • סוכני AI גמישים (elastic-ai-agent) וסוכנים מותאמים אישית ב-Agent Builder מסתמכים על הפעלת כלים מרובת-שלבים (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 Agent‏ (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):
    • המתאם של Kibana לסטרימינג של OpenAI מתייחס אל "finish_reason": "length" כשגיאה חמורה Token limit reached ושולח "temperature": 0 (פענוח חמדני) בכל בקשה.
    • במודלים עם יכולת הסקת מסקנות (כמו gemma4:31b, שיוצר אסימוני חשיבה פנימיים לפני שהוא מפעיל קריאות לפונקציות), צריך להגדיר מגבלת אסימונים של לפחות 8,192 אסימונים וקנס קל על חזרה על מילים. לדוגמה, ב-Ollama, מגדירים את num_predict: 8192, repeat_penalty: 1.05 ו-repeat_last_n: 64. הימנעו מקנסות אגרסיביים מדי על חזרות (כמו 1.15+ על חלונות גדולים), כי הם מקשים על המודל להעתיק שאילתת ES|QL מבלוק החשיבה הפנימי שלו ל-JSON הסופי של הקריאה לכלי.
  • האצת 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 פועל ואפשר להגיע אליו מ-Kibana pod, מגדירים מחבר 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. לוחצים על יצירת מחבר ובוחרים באפשרות 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} (כל מחרוזת לא ריקה אם השרת לא אוכף אימות bearer, או מפתח ה-API של השרת).
    • הפעלת קריאה להפעלת פונקציות מקוריות: מפעילים את המתג.
  5. לוחצים על שמירה ובדיקה כדי לשמור את המחבר ולפתוח מיד את הכרטיסייה בדיקה.
  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 נבחר כסוכן הפעיל, ושהמחבר של מודל השפה הגדול (${LLM_CONNECTOR_NAME}) נבחר כמחבר המודל.

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

מבקשים מ-Elastic AI Agent לזהות את כתובות ה-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 כשלים
  • ‫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) כדי לצבור תגובות שאינן200 HTTP ב-kibana_sample_data_logs לפי כתובת IP של הלקוח.
  2. מבחין בין פעילות זדונית של איסוף מידע וניצול לרעה (30.156.16.164) לבין כשלים תקינים בהורדה.
  3. מפעיל כלי מותאם אישית של Kibana Workflow ‏ (create_security_case) כדי לפתוח באופן אוטומטי Kibana Security Case מובנה עם הממצאים ושלבי התיקון.

יצירת כלים מותאמים אישית של 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 Workflow‏ (create-security-case) שמפעיל את השלב המקורי cases.createCase של Kibana, ולרשום אותו ככלי workflow בשם create_security_case:

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

      1. פותחים את תפריט הניווט הראשי ועוברים אל ניהול > תהליכי עבודה (או עוברים ישירות אל https://${KIBANA_IP}:5601/app/workflows), ואז לוחצים על יצירת תהליך עבודה חדש.
      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

  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 Security Case שנוצר לאחרונה:

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

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