Et si on traquait les journaux réseau ?
Publié : ven. 15 mai 2026, 11:24
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
Debugage
/opt/netsoc/debug.sh
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) |
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 "======================================="
/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"
| 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