Page 1 sur 1

Et si on traquait les journaux réseau ?

Publié : ven. 15 mai 2026, 11:24
par le Manchot Masqué
Architecture du projet :

Suricata ─┐
├──> FluentBit ───> OpenSearch ───> Dashboards
Backend ─┘

└── WebSocket ───> Frontend temps réel

Suricata est un moteur de :

IDS (Intrusion Detection System),
IPS (Intrusion Prevention System),
NSM (Network Security Monitoring).

Son fonctionnement

Capture des paquets > Décodage > Inspection profonde (DPI) > Correspondances de règles (SI trafic correspond à signature → déclencher alerte JSON)

/opt/netsoc/install.sh

Code : Tout sélectionner

#!/bin/bash

set -euo pipefail

BASE="/opt/netsoc"
FORCE=false

usage() {
  cat <<USAGE
Usage: $0 [-f|--force]

Options:
  -f, --force   Réécrit les fichiers générés et retélécharge les ressources distantes
  -h, --help    Affiche cette aide
USAGE
}

for arg in "$@"; do
  case "$arg" in
    -f|--force)
      FORCE=true
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "[ERREUR] Option inconnue: $arg" >&2
      usage
      exit 1
      ;;
  esac
done

log()  { echo "[INFO] $*"; }
ok()   { echo "[ OK ] $*"; }
skip() { echo "[SKIP] $*"; }
warn() { echo "[WARN] $*"; }

write_file() {
  local file="$1"
  local tmp
  tmp="$(mktemp)"
  cat > "$tmp"

  if [ "$FORCE" = true ] || [ ! -f "$file" ]; then
    mkdir -p "$(dirname "$file")"
    cp "$tmp" "$file"
    ok "Fichier écrit: $file"
  else
    skip "Fichier existant conservé: $file"
  fi

  rm -f "$tmp"
}

need_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "[ERREUR] Commande manquante: $1" >&2
    exit 1
  fi
}

need_cmd docker
need_cmd ip
need_cmd awk

if ! command -v wget >/dev/null 2>&1; then
  log "Installation de wget"
  apt update
  apt install -y wget
fi

if ! command -v curl >/dev/null 2>&1; then
  log "Installation de curl"
  apt update
  apt install -y curl
fi

if ! docker compose version >/dev/null 2>&1; then
  echo "[ERREUR] docker compose n'est pas disponible" >&2
  exit 1
fi

echo "=== NETSOC INSTALLER ==="
[ "$FORCE" = true ] && warn "Mode FORCE activé: fichiers générés et téléchargements seront remplacés si nécessaire"

########################################
# Dossiers
########################################

for dir in \
  "$BASE" \
  "$BASE/suricata" \
  "$BASE/suricata/rules" \
  "$BASE/backend" \
  "$BASE/frontend" \
  "$BASE/zeek/logs" \
  "$BASE/zeek/site" \
  "$BASE/zeek/share/GeoIP" \
  "$BASE/fluentbit" \
  "$BASE/dashboards"
do
  if [ -d "$dir" ]; then
    skip "Dossier existant: $dir"
  else
    mkdir -p "$dir"
    ok "Dossier créé: $dir"
  fi
done

# Droits nécessaires aux conteneurs
chmod 755 "$BASE"
chmod 777 "$BASE/suricata"
chmod 777 "$BASE/zeek/logs"
chmod 777 "$BASE/fluentbit"

cd "$BASE"

if [ ! -f suricata/eve.json ]; then
  touch suricata/eve.json
  ok "Fichier créé: suricata/eve.json"
else
  skip "Fichier existant: suricata/eve.json"
fi

# Les conteneurs Suricata / backend / Fluent Bit n'utilisent pas forcément
# le même UID/GID que l'hôte. On rend donc les logs accessibles.
chown -R root:root "$BASE/suricata"
chmod 777 "$BASE/suricata"
chmod 666 "$BASE/suricata/eve.json"

########################################
# Interfaces
########################################

echo
echo "Interfaces réseau disponibles :"
ip -o link show | awk -F': ' '{print $2}' | grep -v '^lo$' || true

echo
read -r -p "Interfaces à surveiller (séparées par espace) : " IFACES

if [ -z "${IFACES// /}" ]; then
  echo "[ERREUR] Aucune interface fournie" >&2
  exit 1
fi

########################################
# Construction config Suricata
########################################

SURICATA_AF=""
ID=90

for IFACE in $IFACES; do
  SURICATA_AF="$SURICATA_AF
  - interface: $IFACE
    cluster-id: $ID
    cluster-type: cluster_flow
    defrag: yes
"
  ID=$((ID+1))
done

SURICATA_CMD="-c /etc/suricata/suricata.yaml"
for IFACE in $IFACES; do
  SURICATA_CMD="$SURICATA_CMD --af-packet=$IFACE"
done

########################################
# Construction commande Zeek
########################################

ZEEK_CMD=""
for IFACE in $IFACES; do
  ZEEK_CMD="$ZEEK_CMD -i $IFACE"
done

########################################
# Docker Compose
########################################

write_file docker-compose.yml <<EOF_DOCKER
services:

  opensearch:
    image: opensearchproject/opensearch:2.11.0
    container_name: opensearch
    environment:
      - discovery.type=single-node
      - DISABLE_SECURITY_PLUGIN=true
      - DISABLE_INSTALL_DEMO_CONFIG=true
      - OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g
    ulimits:
      memlock:
        soft: -1
        hard: -1
    ports:
      - "9200:9200"
    restart: unless-stopped

  dashboards:
    image: opensearchproject/opensearch-dashboards:2.11.0
    container_name: dashboards
    environment:
      OPENSEARCH_HOSTS: '["http://opensearch:9200"]'
      DISABLE_SECURITY_DASHBOARDS_PLUGIN: "true"
    depends_on:
      - opensearch
    ports:
      - "5601:5601"
    restart: unless-stopped

  suricata:
    image: jasonish/suricata:latest
    container_name: suricata
    network_mode: host
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - SYS_NICE
    command: $SURICATA_CMD
    volumes:
      - ./suricata:/logs
      - ./suricata/suricata.yaml:/etc/suricata/suricata.yaml
      - ./suricata/rules:/etc/suricata/rules
    restart: unless-stopped

  zeek:
    image: blacktop/zeek
    container_name: zeek
    network_mode: host
    command: $ZEEK_CMD
    volumes:
      - ./zeek/logs:/usr/local/zeek/logs
      - ./zeek/site:/usr/local/zeek/share/zeek/site
      - ./zeek/share/GeoIP:/usr/local/zeek/share/GeoIP
    restart: unless-stopped

  backend:
    build: ./backend
    container_name: backend
    ports:
      - "18000:8000"
    volumes:
      - ./suricata:/logs
    restart: unless-stopped

  frontend:
    image: nginx:alpine
    container_name: frontend
    volumes:
      - ./frontend:/usr/share/nginx/html
    ports:
      - "18080:80"
    restart: unless-stopped

  fluentbit:
    depends_on:
      - opensearch
    image: fluent/fluent-bit:latest
    container_name: fluentbit
    volumes:
      - ./suricata:/logs
      - ./fluentbit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
      - ./fluentbit/parsers.conf:/fluent-bit/etc/parsers.conf
    command: /fluent-bit/bin/fluent-bit -c /fluent-bit/etc/fluent-bit.conf
    restart: unless-stopped
EOF_DOCKER

########################################
# Fluent Bit
########################################

write_file fluentbit/fluent-bit.conf <<'EOF_FLUENT'
[SERVICE]
    Flush        1
    Log_Level    info
    Parsers_File parsers.conf

[INPUT]
    Name         tail
    Path         /logs/eve.json
    Parser       json
    Tag          suricata
    Refresh_Interval 1
    Read_From_Head true

[OUTPUT]
    Name            opensearch
    Match           *
    Host            opensearch
    Port            9200
    Index           netsoc
    Suppress_Type_Name On
    tls             Off
EOF_FLUENT

write_file fluentbit/parsers.conf <<'EOF_FLUENT'
[PARSER]
    Name         json
    Format       json
    Time_Key     timestamp
    Time_Format  %Y-%m-%dT%H:%M:%S.%L%z
EOF_FLUENT

########################################
# Zeek config
########################################

GEOIP_FILE="zeek/share/GeoIP/GeoLite2-City.mmdb"
GEOIP_URL="https://raw.githubusercontent.com/P3TERX/GeoLite.mmdb/download/GeoLite2-City.mmdb"

if [ "$FORCE" = true ] || [ ! -s "$GEOIP_FILE" ]; then
  log "Téléchargement GeoIP: $GEOIP_FILE"
  wget -O "$GEOIP_FILE" "$GEOIP_URL"
else
  skip "GeoIP déjà présent: $GEOIP_FILE"
fi

write_file zeek/site/local.zeek <<'EOF_ZEEK'
@load policy/tuning/json-logs

redef LogAscii::use_json = T;

@load base/protocols/conn
@load base/protocols/dns
@load base/protocols/http
@load base/protocols/ssl
@load base/protocols/ssh

@load policy/protocols/conn/detect-scan
@load policy/protocols/ssh/detect-bruteforce

@load policy/frameworks/geoip/host
redef Conn::log_add_geolocation = T;

redef ignore_checksums = T;
EOF_ZEEK

########################################
# Suricata config
########################################

write_file suricata/suricata.yaml <<EOF_SURICATA
%YAML 1.1
---

outputs:
  - eve-log:
      enabled: yes
      filetype: regular
      filename: /logs/eve.json
      types:
        - alert
        - flow
        - dns
        - http
        - tls
        - ssh
        - stats

af-packet:
$SURICATA_AF

default-rule-path: /etc/suricata/rules

rule-files:
  - suricata.rules
  # Décommente les fichiers ci-dessous si tu veux activer plus de catégories ET Open.
  #- emerging-malware.rules
  #- emerging-scan.rules
  #- emerging-dos.rules
  #- emerging-web_server.rules
  #- emerging-web_client.rules
  #- emerging-trojan.rules
  #- emerging-shellcode.rules
  #- emerging-exploit.rules
  #- emerging-botcc.rules
  #- emerging-attack_response.rules
  #- emerging-dns.rules
  #- emerging-icmp.rules
  #- emerging-smtp.rules
  #- emerging-ftp.rules
  #- emerging-sql.rules
  #- emerging-policy.rules
EOF_SURICATA

########################################
# Règles Suricata
########################################

RULES_ARCHIVE="suricata/emerging.rules.tar.gz"
RULES_URL="https://rules.emergingthreats.net/open/suricata-8.0/emerging.rules.tar.gz"
RULES_MARKER="suricata/rules/.downloaded"

if [ "$FORCE" = true ] || [ ! -f "$RULES_MARKER" ] || [ ! -s "suricata/rules/suricata.rules" ]; then
  log "Téléchargement des règles Suricata Emerging Threats"
  wget -O "$RULES_ARCHIVE" "$RULES_URL"
  tar xzf "$RULES_ARCHIVE" -C suricata/
  touch "$RULES_MARKER"
  chmod -R a+rX suricata/rules
  ok "Règles Suricata installées"
else
  skip "Règles Suricata déjà présentes. Utilise -f pour forcer."
fi

########################################
# Dashboards distants optionnels
########################################
# Mets ici l'URL réelle de ton fichier NDJSON si tu en as un.
# Sans URL, le script ne télécharge rien et ne bloque pas l'installation.

DASHBOARD_URL="${DASHBOARD_URL:-}"
DASHBOARD_FILE="$BASE/dashboards/netsoc-dashboard.ndjson"
DASHBOARD_DOWNLOAD_MARKER="$BASE/dashboards/.downloaded"
DASHBOARD_IMPORT_MARKER="$BASE/dashboards/.imported"

if [ -n "$DASHBOARD_URL" ]; then
  if [ "$FORCE" = true ] || [ ! -s "$DASHBOARD_FILE" ] || [ ! -f "$DASHBOARD_DOWNLOAD_MARKER" ]; then
    log "Téléchargement dashboard distant: $DASHBOARD_FILE"
    wget -O "$DASHBOARD_FILE" "$DASHBOARD_URL"
    touch "$DASHBOARD_DOWNLOAD_MARKER"
  else
    skip "Dashboard distant déjà téléchargé: $DASHBOARD_FILE"
  fi
else
  skip "Aucune variable DASHBOARD_URL définie: pas de téléchargement de dashboard distant"
fi

########################################
# Backend
########################################

write_file backend/Dockerfile <<'EOF_DOCKERFILE'
FROM python:3.12-slim

WORKDIR /app

RUN pip install fastapi uvicorn websockets aiofiles requests

COPY app.py .

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
EOF_DOCKERFILE

write_file backend/app.py <<'EOF_BACKEND'
import asyncio
import json
from fastapi import FastAPI, WebSocket
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()
clients = []

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

LOG_FILE = "/logs/eve.json"

def normalize(data):
    return {
        "event_type": data.get("event_type") or "zeek",
        "src_ip": data.get("src_ip") or data.get("id.orig_h"),
        "dest_ip": data.get("dest_ip") or data.get("id.resp_h"),
        "proto": data.get("proto") or data.get("protocol")
    }

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket):
    await ws.accept()
    clients.append(ws)

    try:
        while True:
            await asyncio.sleep(1)
    except Exception:
        if ws in clients:
            clients.remove(ws)

async def tail_log():
    await asyncio.sleep(5)

    with open(LOG_FILE, "r") as f:
        f.seek(0, 2)

        while True:
            line = f.readline()

            if not line:
                await asyncio.sleep(0.2)
                continue

            try:
                data = json.loads(line)
                data = normalize(data)

                for c in list(clients):
                    try:
                        await c.send_text(json.dumps(data))
                    except Exception:
                        if c in clients:
                            clients.remove(c)

            except Exception:
                pass

@app.on_event("startup")
async def startup():
    asyncio.create_task(tail_log())
EOF_BACKEND

########################################
# Frontend
########################################

write_file frontend/index.html <<'EOF_FRONTEND'
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>NETSOC</title>

<style>
body{
background:#111;
color:#0f0;
font-family:monospace;
}

.alert{
padding:10px;
border-bottom:1px solid #333;
}

.status{
color:#aaa;
margin-bottom:12px;
}
</style>

</head>

<body>

<h1>NETSOC</h1>
<div id="status" class="status">Connexion WebSocket...</div>
<div id="alerts"></div>

<script>
const statusDiv = document.getElementById("status");
const ws = new WebSocket("ws://" + location.hostname + ":18000/ws");

ws.onopen = () => {
  statusDiv.textContent = "WebSocket connecté";
};

ws.onerror = () => {
  statusDiv.textContent = "Erreur WebSocket";
};

ws.onclose = () => {
  statusDiv.textContent = "WebSocket fermé";
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  const div = document.createElement("div");

  div.className = "alert";
  div.innerHTML = "<b>" + data.event_type + "</b><br>" +
    "SRC: " + data.src_ip + "<br>" +
    "DST: " + data.dest_ip + "<br>" +
    "PROTO: " + data.proto;

  document.getElementById("alerts").prepend(div);
};
</script>

</body>
</html>
EOF_FRONTEND

########################################
# SYSCTL
########################################

if grep -q '^vm.max_map_count=' /etc/sysctl.conf; then
  skip "vm.max_map_count déjà présent dans /etc/sysctl.conf"
else
  echo "vm.max_map_count=262144" >> /etc/sysctl.conf
  ok "vm.max_map_count ajouté à /etc/sysctl.conf"
fi

sysctl -p

########################################
# Docker restart
########################################

docker compose down || true
docker compose up -d --build

########################################
# Import dashboard optionnel
########################################

if [ -s "$DASHBOARD_FILE" ]; then
  if [ "$FORCE" = true ] || [ ! -f "$DASHBOARD_IMPORT_MARKER" ]; then
    log "Attente courte de OpenSearch Dashboards avant import éventuel"
    for i in $(seq 1 30); do
      code="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:5601/ || true)"
      if [ "$code" = "200" ] || [ "$code" = "302" ]; then
        break
      fi
      sleep 2
    done

    log "Import du dashboard NDJSON dans OpenSearch Dashboards"
    if curl -sS -X POST "http://127.0.0.1:5601/api/saved_objects/_import?overwrite=true" \
      -H "osd-xsrf: true" \
      --form file=@"$DASHBOARD_FILE" >/tmp/netsoc-dashboard-import.log; then
      touch "$DASHBOARD_IMPORT_MARKER"
      ok "Dashboard importé"
    else
      warn "Import dashboard échoué. Voir /tmp/netsoc-dashboard-import.log"
    fi
  else
    skip "Dashboard déjà importé. Utilise -f pour réimporter."
  fi
fi

echo
echo "======================================="
echo "NETSOC démarré"
echo "Dashboard : http://$(hostname -I | awk '{print $1}'):18080"
echo "OpenSearch : http://$(hostname -I | awk '{print $1}'):9200"
echo "OpenSearch Dashboards : http://$(hostname -I | awk '{print $1}'):5601"
echo "======================================="

Debugage

/opt/netsoc/debug.sh

Code : Tout sélectionner

#!/usr/bin/env bash

# NETSOC debug helper
# Usage: sudo ./debug.sh [BASE]
# Default BASE=/opt/netsoc

set +e

BASE="${1:-/opt/netsoc}"
COMPOSE="$BASE/docker-compose.yml"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

ERRORS=0
WARNINGS=0

ok()   { echo -e "${GREEN}[ OK ]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; WARNINGS=$((WARNINGS+1)); }
fail() { echo -e "${RED}[FAIL]${NC} $*"; ERRORS=$((ERRORS+1)); }
info() { echo -e "${BLUE}[INFO]${NC} $*"; }

hr() { echo; echo "============================================================"; echo "$*"; echo "============================================================"; }

need_cmd() {
    command -v "$1" >/dev/null 2>&1 && ok "Commande disponible: $1" || fail "Commande manquante: $1"
}

check_port() {
    local port="$1"
    local name="$2"
    if ss -ltnp 2>/dev/null | awk '{print $4}' | grep -Eq "(:|\.)${port}$"; then
        ok "$name écoute sur le port TCP $port"
        ss -ltnp 2>/dev/null | grep -E "(:|\.)${port}\s" || true
    else
        fail "$name n'écoute pas sur le port TCP $port"
    fi
}

curl_check() {
    local url="$1"
    local name="$2"
    local expected_regex="${3:-.*}"
    local tmp
    tmp="$(mktemp)"
    local code
    code="$(curl -k -sS --max-time 5 -o "$tmp" -w '%{http_code}' "$url" 2>/tmp/netsoc_curl_err || echo ERR)"
    if [[ "$code" =~ ^2|3 ]]; then
        if grep -Eiq "$expected_regex" "$tmp"; then
            ok "$name répond: $url HTTP $code"
        else
            warn "$name répond HTTP $code mais le contenu ne correspond pas à '$expected_regex': $url"
            head -c 300 "$tmp" | sed 's/^/      /'; echo
        fi
    else
        fail "$name ne répond pas correctement: $url code=$code"
        sed 's/^/      curl: /' /tmp/netsoc_curl_err 2>/dev/null || true
    fi
    rm -f "$tmp" /tmp/netsoc_curl_err
}

container_running() {
    local c="$1"
    if docker ps --format '{{.Names}}' | grep -qx "$c"; then
        ok "Conteneur actif: $c"
    else
        fail "Conteneur non actif: $c"
        docker ps -a --filter "name=^/${c}$" --format '      {{.Names}}  {{.Status}}  {{.Image}}'
    fi
}

container_logs_errors() {
    local c="$1"
    if docker ps -a --format '{{.Names}}' | grep -qx "$c"; then
        local hits
        hits="$(docker logs --tail 200 "$c" 2>&1 | grep -Ei 'error|exception|failed|fatal|panic|traceback|permission denied|connection refused|unable|invalid|yaml' | tail -30)"
        if [ -n "$hits" ]; then
            warn "Messages suspects dans les logs de $c:"
            echo "$hits" | sed 's/^/      /'
        else
            ok "Pas d'erreur évidente dans les 200 dernières lignes de logs de $c"
        fi
    fi
}

hr "Pré-requis locaux"
[ "$(id -u)" -eq 0 ] || warn "Script non lancé en root. Certaines vérifications réseau/Docker peuvent être incomplètes."
need_cmd docker
need_cmd curl
need_cmd ss
need_cmd awk
need_cmd grep

if docker compose version >/dev/null 2>&1; then
    ok "docker compose disponible"
else
    fail "docker compose indisponible"
fi

hr "Arborescence NETSOC"
[ -d "$BASE" ] && ok "Répertoire base présent: $BASE" || fail "Répertoire base absent: $BASE"
[ -f "$COMPOSE" ] && ok "docker-compose.yml présent" || fail "docker-compose.yml absent: $COMPOSE"
for d in suricata backend frontend zeek/logs zeek/site zeek/share/GeoIP fluentbit; do
    [ -d "$BASE/$d" ] && ok "Répertoire présent: $BASE/$d" || fail "Répertoire absent: $BASE/$d"
done

for f in \
    suricata/eve.json \
    suricata/suricata.yaml \
    backend/Dockerfile \
    backend/app.py \
    frontend/index.html \
    fluentbit/fluent-bit.conf \
    zeek/site/local.zeek
    do
    [ -f "$BASE/$f" ] && ok "Fichier présent: $BASE/$f" || fail "Fichier absent: $BASE/$f"
done

hr "Détection d'incohérences connues dans install.sh généré"
if [ -f "$BASE/frontend/index.html" ]; then
    if grep -q ':8000/ws' "$BASE/frontend/index.html"; then
        fail "frontend/index.html tente WebSocket sur :8000/ws, mais le backend est publié sur le port hôte 18000. Corriger en :18000/ws ou passer par un reverse proxy nginx."
    elif grep -q ':18000/ws' "$BASE/frontend/index.html"; then
        ok "Le frontend pointe vers le port WebSocket hôte 18000"
    else
        warn "Impossible de confirmer le port WebSocket utilisé par le frontend."
    fi
fi

if [ -f "$COMPOSE" ]; then
    grep -q '18080:80' "$COMPOSE" && ok "Frontend publié sur 18080:80" || warn "Mapping frontend 18080:80 non trouvé"
    grep -q '18000:8000' "$COMPOSE" && ok "Backend publié sur 18000:8000" || warn "Mapping backend 18000:8000 non trouvé"
    if grep -q '5601:5601' "$COMPOSE"; then ok "Dashboards publié sur 5601"; else warn "Mapping dashboards 5601:5601 non trouvé"; fi
fi

if [ -f "$BASE/frontend/index.html" ] && grep -Eiq 'NETCAT|netcat' "$BASE/frontend/index.html"; then
    fail "Le fichier frontend/index.html contient NETCAT. Le fichier servi n'est probablement pas celui attendu par NETSOC."
fi

hr "État Docker Compose"
if [ -f "$COMPOSE" ]; then
    (cd "$BASE" && docker compose ps)
fi
for c in opensearch dashboards suricata zeek backend frontend fluentbit; do
    container_running "$c"
done

hr "Ports TCP attendus"
check_port 18080 "Frontend nginx"
check_port 18000 "Backend FastAPI"
check_port 5601  "OpenSearch Dashboards"
check_port 9200  "OpenSearch"

hr "Tests HTTP locaux"
curl_check "http://127.0.0.1:18080/" "Frontend NETSOC" "NETSOC|html|NETCAT"
curl_check "http://127.0.0.1:18000/docs" "Backend FastAPI docs" "Swagger|FastAPI|openapi"
curl_check "http://127.0.0.1:9200/" "OpenSearch" "cluster_name|opensearch|version"
curl_check "http://127.0.0.1:5601/" "OpenSearch Dashboards" "OpenSearch|Dashboards|kbn|kibana"

hr "Tests depuis les conteneurs"
if docker ps --format '{{.Names}}' | grep -qx dashboards; then
    #docker exec dashboards sh -c 'node -e "fetch(\"http://opensearch:9200\").then(r=>console.log(r.status)).catch(e=>{console.error(e);process.exit(1)})"'
    docker exec dashboards bash -c 'cat < /dev/null > /dev/tcp/opensearch/9200'
    #docker exec dashboards sh -c 'wget -qO- --timeout=5 http://opensearch:9200/ | head -c 300' >/tmp/netsoc_dash_to_os 2>/tmp/netsoc_dash_to_os_err
    if [ $? -eq 0 ] && grep -Eiq 'cluster_name|opensearch|version' /tmp/netsoc_dash_to_os; then
        ok "dashboards joint opensearch:9200 depuis le réseau Docker"
    else
        fail "dashboards ne joint pas opensearch:9200"
        sed 's/^/      /' /tmp/netsoc_dash_to_os_err 2>/dev/null
        cat /tmp/netsoc_dash_to_os 2>/dev/null | sed 's/^/      /'
    fi
fi

if docker ps --format '{{.Names}}' | grep -qx fluentbit; then
    docker exec fluentbit sh -c 'test -r /logs/eve.json' && ok "fluentbit lit /logs/eve.json" || fail "fluentbit ne lit pas /logs/eve.json"
fi

if docker ps --format '{{.Names}}' | grep -qx backend; then
    docker exec backend sh -c 'test -r /logs/eve.json' && ok "backend lit /logs/eve.json" || fail "backend ne lit pas /logs/eve.json"
fi

hr "Suricata / Zeek / logs"
if [ -f "$BASE/suricata/eve.json" ]; then
    size=$(stat -c '%s' "$BASE/suricata/eve.json" 2>/dev/null || echo 0)
    lines=$(wc -l < "$BASE/suricata/eve.json" 2>/dev/null || echo 0)
    info "eve.json: ${size} octets, ${lines} lignes"
    if [ "$size" -gt 0 ]; then
        ok "Suricata écrit dans eve.json"
        tail -n 3 "$BASE/suricata/eve.json" | sed 's/^/      /'
    else
        warn "eve.json est vide. Causes possibles: interface non valide, aucun trafic, Suricata non démarré, droits/capabilities, config YAML invalide."
    fi
fi

if [ -d "$BASE/zeek/logs" ]; then
    zcount=$(find "$BASE/zeek/logs" -type f | wc -l)
    if [ "$zcount" -gt 0 ]; then
        ok "Zeek produit des logs ($zcount fichiers)"
        find "$BASE/zeek/logs" -type f | head -10 | sed 's/^/      /'
    else
        warn "Aucun log Zeek trouvé dans $BASE/zeek/logs"
    fi
fi

hr "OpenSearch indices"
idx="$(curl -sS --max-time 5 http://127.0.0.1:9200/_cat/indices?v 2>/tmp/netsoc_idx_err)"
if [ $? -eq 0 ] && [ -n "$idx" ]; then
    echo "$idx" | sed 's/^/      /'
    if echo "$idx" | grep -q 'netsoc'; then
        ok "Index OpenSearch netsoc présent"
        count="$(curl -sS --max-time 5 http://127.0.0.1:9200/netsoc/_count 2>/dev/null)"
        info "Documents netsoc: $count"
    else
        warn "Index netsoc absent. Fluent Bit n'a peut-être rien envoyé ou eve.json est vide."
    fi
else
    fail "Impossible de lister les indices OpenSearch"
    sed 's/^/      /' /tmp/netsoc_idx_err 2>/dev/null
fi

hr "Logs Docker suspects"
for c in opensearch dashboards suricata zeek backend frontend fluentbit; do
    container_logs_errors "$c"
done

hr "Diagnostic synthétique"
if [ -f "$BASE/frontend/index.html" ] && grep -q ':8000/ws' "$BASE/frontend/index.html"; then
    echo -e "${RED}Correction prioritaire:${NC} dans $BASE/frontend/index.html, remplacer :8000/ws par :18000/ws."
    echo "Commande possible:"
    echo "  sed -i 's/:8000\/ws/:18000\/ws/g' '$BASE/frontend/index.html'"
    echo "  cd '$BASE' && docker compose restart frontend"
fi

if [ -f "$COMPOSE" ] && grep -q '18080:80' "$COMPOSE"; then
    echo -e "${YELLOW}Note:${NC} l'URL frontend correcte est http://<IP>:18080/ et non :8080."
fi

if [ "$ERRORS" -eq 0 ]; then
    ok "Diagnostic terminé sans erreur bloquante détectée. Avertissements: $WARNINGS"
else
    fail "Diagnostic terminé avec $ERRORS erreur(s) et $WARNINGS avertissement(s)."
fi

exit "$ERRORS"

Interfaces
| Service | URL |
| -------------- | ---------------------------------------------- |
| Dashboard HTML | [http://localhost:18080](http://localhost:18080) |
| API/WebSocket | [http://localhost:18000](http://localhost:18000) |
| OpenSearch | [http://localhost:9200](http://localhost:9200) |
| Dashboards | [http://localhost:5601](http://localhost:5601) |

Code : Tout sélectionner


echo "Ajouter des règles Suricata..."
mkdir suricata/rules
cat > suricata/rules/local.rules <<EOF
alert icmp any any -> any any (
    msg:"PING DETECTED";
    sid:1000001;
    rev:1;
)
EOF
apt install docker.io docker-compose
docker-compose up -d