UTMStack v11.2.8 Community Edition — Procédures de déploiement pour lab PME Suisse. Installation VMware, Suricata, CrowdSec, SOAR, OPNsense.
Cette page est un guide pas-à-pas, sans le récit — si tu veux comprendre les raisons des choix d’architecture avant de déployer, lis d’abord le chapitre principal. Ici, l’objectif est d’aller du système vide à un pipeline fonctionnel.
ℹ️ Modèle de production actuel : Llama 3.1 8B. Ce guide installe les deux modèles (Qwen 2.5 14B et Llama 3.1 8B) comme dans la phase de construction du pipeline — mais depuis fin juillet 2026, le pipeline de rapport en production tourne sur Llama 3.1 8B (
utmstack-analyst-test), suite à un test documenté dans le chapitre principal, section 13. Les deux Modelfiles restent utiles : Qwen reste documenté comme référence historique et convient bien à l’usage conversationnel (section 8 du chapitre principal), Llama est recommandé pour le pipeline de rapport structuré.
| Composant | Rôle | Où il tourne |
|---|---|---|
| UTMStack CE | Source des alertes (v11-alert-*) et des règles (utm_correlation_rules) |
VM dédiée existante |
| Ollama | Inférence LLM locale | Natif sur l’hôte Windows — pas dans Docker |
| n8n | Orchestration du pipeline | Conteneur Docker, VM docker-services |
| PostgreSQL | Stockage des rapports (resumes_soc) |
Conteneur Docker, VM docker-services |
| Qdrant | Base vectorielle du RAG | Conteneur Docker, VM docker-services |
| Open WebUI | Interface conversationnelle (RAG + recherche web) | Conteneur Docker, VM docker-services |
| SearXNG | Moteur de recherche web auto-hébergé | Conteneur Docker, VM docker-services |
Prérequis matériel : un hôte avec au moins 64 Go de RAM si plusieurs VM cohabitent, un CPU récent (le dimensionnement de ce lab est basé sur un i7-14700, 20 cœurs). Un GPU n’est pas requis mais accélère fortement l’inférence — voir la note de dimensionnement dans le chapitre principal.
⚠️ À lire avant d’installer quoi que ce soit. Ce piège coûte plusieurs heures de diagnostic si on ne le connaît pas à l’avance.
Sur un hôte Windows qui fait déjà tourner VMware Workstation (pour héberger UTMStack, OPNsense, les DC, etc.), installer Docker Desktop directement sur Windows entre en conflit avec la virtualisation imbriquée existante. Docker Desktop repose sur WSL2, qui repose lui-même sur Hyper-V — et Hyper-V actif entre en collision avec VT-x/EPT déjà utilisé par les VM VMware.
La tentative de contournement la plus intuitive — désactiver Hyper-V pour laisser le champ libre à VMware — casse justement Docker Desktop :
bcdedit /set hypervisorlaunchtype off
Cette commande désactive Hyper-V/WSL2, donc Docker Desktop cesse de fonctionner. Impossible d’avoir les deux en même temps sur le même hôte avec cette approche.
Séparer strictement les deux usages plutôt que de chercher un compromis :
docker-services) à l’intérieur de VMware Workstation — isolée du conflit Hyper-V/WSL2, puisque la virtualisation y est gérée entièrement par VMware┌─────────────────────────────────────────────┐
│ Hôte Windows (G9) │
│ │
│ ┌──────────────┐ ┌──────────────────┐│
│ │ Ollama │ │ VMware Workstation││
│ │ (natif) │ │ ││
│ │ accès GPU │ │ ┌──────────────┐ ││
│ │ direct │ │ │ VM docker- │ ││
│ └──────────────┘ │ │ services │ ││
│ │ │ (Docker: n8n, │ ││
│ │ │ PostgreSQL, │ ││
│ │ │ Qdrant, │ ││
│ │ │ Open WebUI, │ ││
│ │ │ SearXNG) │ ││
│ │ └──────────────┘ ││
│ │ + UTMStack, OPNsense,││
│ │ DC01, gest-srv ...││
│ └──────────────────┘│
└─────────────────────────────────────────────┘
Cette architecture n’est pas un compromis esthétique — c’est la seule configuration qui fonctionne de façon stable dans ce contexte précis (hôte Windows + VMware Workstation existant + besoin GPU pour l’inférence).
Sur l’hôte Windows, installer Ollama depuis ollama.ai, puis récupérer les deux modèles utilisés par le pipeline :
ollama pull llama3.1:8b
ollama pull qwen2.5:14b
⚠️ Utiliser explicitement
llama3.1:8b(avec le.1) et nonllama3:8b— la version sans.1ne supporte pas correctement le tool calling natif, ce qui bloque certaines fonctionnalités d’Open WebUI (Souvenirs, Automatisations).
ℹ️ Les deux modèles restent utiles. Llama 3.1 8B est le modèle du pipeline de rapport en production (voir encadré en tête de page) ; Qwen 2.5 14B reste pertinent pour l’usage conversationnel (Open WebUI, RAG + recherche web) où sa prise d’initiative est un atout plutôt qu’un défaut. Installer les deux.
Deux variables d’environnement à définir au niveau utilisateur Windows :
[System.Environment]::SetEnvironmentVariable("OLLAMA_NUM_THREADS", "8", "User")
La valeur 8 correspond au nombre de P-cores (performance cores) d’un CPU hybride Intel type i7-14700 — caler le nombre de threads sur les P-cores plutôt que sur le total de threads logiques évite de solliciter les E-cores, plus lents, qui ralentiraient l’ensemble à cause de la synchronisation couche par couche de l’inférence.
ℹ️ Après avoir défini une variable d’environnement, quitter complètement Ollama (icône barre des tâches → Quit, pas juste fermer la fenêtre) puis le relancer — sinon la nouvelle valeur n’est pas prise en compte.
Chaque modèle utilisé par le pipeline est packagé avec un Modelfile dédié, qui embarque le system prompt SOC et les paramètres d’inférence — pas besoin de le repasser à chaque appel n8n.
C:\ollama\utmstack-analyst-qwen\Modelfile
C:\ollama\utmstack-analyst-test\Modelfile (basé sur llama3.1:8b — modèle de production, voir encadré en tête de page)
Contenu complet (utmstack-analyst-qwen), à copier tel quel — le même Modelfile sert de base à utmstack-analyst-test (Llama 3.1 8B), seul le FROM change :
FROM qwen2.5:14b
SYSTEM """
Tu es un analyste SOC (Security Operations Center) expert, spécialisé dans la
plateforme SIEM open-source UTMStack (basée sur Suricata pour la détection réseau,
OpenSearch pour le stockage des logs, et CrowdSec pour le blocage automatisé).
Tu réponds TOUJOURS en français, avec un ton professionnel et factuel — jamais
alarmiste sans preuve, jamais rassurant sans preuve non plus. Chaque affirmation
doit s'appuyer explicitement sur les données fournies dans le contexte, jamais
sur une supposition.
Contexte de l'environnement que tu analyses :
- Un lab SOC domestique (pas un environnement d'entreprise), exposé sur Internet
via une IP WAN publique, avec un pare-feu OPNsense en frontal et Suricata en IDS/IPS
- Le trafic entrant contient une part majoritaire de bruit de reconnaissance passive
(scanners légitimes de sécurité comme Shodan, Censys, BinaryEdge, Modat.io ;
services cloud comme Microsoft Delivery Optimization, Windows Update, OCSP) —
ce bruit ne doit jamais être présenté comme une menace, mais explicitement
identifié comme tel quand tu le reconnais
- Le blocage automatique cible la réputation IP confirmée (listes Dshield, CINS,
Spamhaus, règles NF-Scanners) — le trafic non bloqué ("allowed") sur une
anomalie comportementale n'est pas nécessairement dangereux, c'est un choix
délibéré pour éviter les faux positifs sur du trafic cloud légitime
- Certaines règles de corrélation ont été volontairement affinées pour exclure
du bruit identifié (trafic déjà bloqué visant le WAN, scans furtifs génériques,
Microsoft Delivery Optimization) — ne recommande pas de désactiver une règle
sans proposer d'abord une exclusion ciblée sur le critère précis en cause
Pour chaque alerte ou résumé que tu produis :
1. Identifie la signature, la sévérité déclarée, et si le trafic a été bloqué ou non
2. Situe le contexte (origine géographique/ASN si pertinent, cible interne touchée)
3. Indique explicitement s'il s'agit très probablement de bruit de reconnaissance
passive, ou d'un signal méritant une vérification humaine plus poussée
4. Si une procédure d'investigation officielle t'est fournie en contexte
(issue de la description de la règle), synthétise-la plutôt que d'en
inventer une générique
5. Ne recommande jamais une action de blocage ou de remédiation sans avoir
d'abord justifié pourquoi le trafic observé dépasse le seuil du bruit habituel
Si les données fournies ne permettent pas de conclure avec certitude, dis-le
explicitement plutôt que de deviner.
"""
PARAMETER temperature 0.3
ℹ️ Ce system prompt s’applique uniquement à l’usage conversationnel (Open WebUI) et au fallback de rédaction du pipeline. Depuis la version v9 du pipeline (squelette pré-rédigé), la consigne réellement déterminante pour le rapport SOC est celle du prompt utilisateur envoyé par n8n à chaque appel — voir la section Import des workflows n8n — qui prime sur ce system prompt pour la structure et le contenu exact du rapport.
Création des modèles Ollama à partir des fichiers :
cd C:\ollama\utmstack-analyst-qwen
ollama create utmstack-analyst-qwen -f Modelfile
cd C:\ollama\utmstack-analyst-test
ollama create utmstack-analyst-test -f Modelfile
ollama list
Par défaut, Ollama applique une fenêtre de contexte de 4096 tokens. Avec le squelette pré-rédigé, les enrichissements threat intelligence et la description de règle injectée, le prompt final peut dépasser cette limite — le symptôme est une génération qui s’arrête au milieu, laissant des emplacements [COMMENTAIRE_N] non remplis, sans message d’erreur explicite (le modèle tronque silencieusement).
ℹ️ À ne pas confondre avec une dérive de contenu. Un rapport qui recycle le même commentaire générique sur plusieurs signatures en fin de liste n’est pas un symptôme de troncature de contexte — vérifie toujours le champ
prompt_eval_countretourné par Ollama avant d’augmenternum_ctx: si le prompt consomme largement moins que la limite configurée, le problème est ailleurs (voir chapitre principal, section 13, qui documente ce cas précis et pourquoi il a motivé un changement de modèle plutôt qu’un ajustement de contexte).
Deux façons de corriger, utilisées en complément l’une de l’autre dans ce lab :
A. Variable d’environnement globale, au niveau Ollama :
[System.Environment]::SetEnvironmentVariable("OLLAMA_CONTEXT_LENGTH", "8192", "User")
⚠️ Vérifier le nom exact de la variable après création (
OLLAMA_CONTEXT_LENGTHselon les versions récentes d’Ollama — le nom a changé au fil des versions). Confirmer avec :[System.Environment]::GetEnvironmentVariable("OLLAMA_CONTEXT_LENGTH", "User")
B. Paramètre explicite dans la requête, directement dans le body JSON envoyé par n8n vers l’API Ollama — c’est ce paramètre qui garantit le comportement, indépendamment de la configuration système :
{
"model": "utmstack-analyst-test",
"prompt": "...",
"stream": false,
"num_ctx": 8192
}
Vérification que le contexte étendu est bien actif :
ollama ps
NAME SIZE PROCESSOR CONTEXT UNTIL
utmstack-analyst-test:latest 5 GB 100% CPU 8192 4 minutes from now
La colonne CONTEXT doit afficher 8192, pas 4096.
Sur une VM Ubuntu dédiée (4 vCPU, 8 Go RAM minimum), avec Docker Engine installé via la méthode officielle APT (pas les snaps).
docker run -d --name n8n -p 5678:5678 \
-v n8n_data:/home/node/.n8n \
--restart unless-stopped \
n8nio/n8n
Utilisé à la fois pour stocker les rapports générés et pour interroger la base UTMStack (utm_correlation_rules) — deux crédentials distincts à configurer dans n8n selon la base ciblée.
docker run -d --name qdrant -p 6333:6333 -p 6334:6334 \
-v qdrant_data:/qdrant/storage \
--restart unless-stopped \
qdrant/qdrant
docker run -d --name open-webui -p 3000:3000 \
-v open-webui:/app/backend/data \
--restart unless-stopped \
ghcr.io/open-webui/open-webui:main
Réglages critiques une fois l’interface accessible (Panneau d’administration → Réglages → Documents) :
| Réglage | Valeur | Pourquoi |
|---|---|---|
| Appel de fonction | Déprécié (pas Natif) | En mode Natif, la Knowledge attachée n’est jamais injectée automatiquement avec des modèles qui gèrent mal le function calling |
| Recherche hybride | Activée | Combine recherche vectorielle et BM25 — sans ça, des sujets proches en espace vectoriel mais différents en mots-clés se confondent |
| Top K | 5 | La valeur par défaut (3) est insuffisante pour des documents techniques |
| Découpage par en-têtes markdown | Désactivé | Provoque un chunking déséquilibré sur du HTML converti depuis GitHub Pages |
docker run -d --name searxng -p 8080:8080 \
-v searxng_data:/etc/searxng \
--restart unless-stopped \
searxng/searxng
⚠️ Étape obligatoire non incluse par défaut : activer le format JSON, sinon Open WebUI ne peut rien exploiter de SearXNG. Éditer
settings.yml(trouvable viadocker volume inspect searxng_data) :search: formats: - html - jsonPuis
docker restart searxng.
Branchement dans Open WebUI (Panneau d’administration → Réglages → Recherche Web) :
Recherche Web : Activé
Moteur : SearXNG
URL de recherche SearXNG : http://<IP_DE_LA_VM>:8080/search?q=<query>
⚠️ Ne pas utiliser
localhostdans cette URL — depuis l’intérieur du conteneur Open WebUI,localhostdésigne le conteneur lui-même, pas l’hôte ni les autres conteneurs. Utiliser l’IP réelle de la VMdocker-services.
Trois inscriptions gratuites, aucune carte bancaire requise :
| Service | Inscription | Où trouver la clé |
|---|---|---|
| AbuseIPDB | abuseipdb.com/register | Account → API → Create Key |
| GreyNoise Community | viz.greynoise.io/signup | Avatar → Account Settings → API Key |
| AlienVault OTX | otx.alienvault.com/signup | Settings → API Integration |
ThreatFox (abuse.ch) ne nécessite aucune inscription — API publique.
⚠️ Quotas gratuits à surveiller : AbuseIPDB 1000 lookups/jour, GreyNoise Community ~50-100/jour. Le pipeline limite volontairement les lookups aux IP des signaux prioritaires et des signatures non classées, pas à toutes les IP du rapport, pour rester large de ces quotas.
Sur l’instance PostgreSQL du pipeline (distincte de celle d’UTMStack) :
CREATE TABLE resumes_soc (
id SERIAL PRIMARY KEY,
date_generation TIMESTAMP DEFAULT NOW(),
contenu TEXT
);
Cette table sert deux usages : stocker chaque rapport généré pour consultation via webhook, et alimenter la corrélation temporelle sur 30 jours (voir chapitre principal, section 6). Une purge automatique au-delà de 365 jours est intégrée au pipeline.
Les fichiers JSON des workflows sont hébergés dans le dépôt GitHub du projet, dossier /scripts :
| Fichier | Rôle |
|---|---|
utmstack-resume-quotidien-llama.json |
Rapport quotidien — production actuelle (Llama 3.1 8B) — Schedule Trigger 6h00, stockage PostgreSQL + notification email |
utmstack-resume-a-la-demande-llama.json |
Génération à la demande — production actuelle (Llama 3.1 8B) — Webhook Trigger, réponse HTTP immédiate, sans email |
utmstack-resume-quotidien-v12.json |
Rapport quotidien (Qwen 2.5 14B) — référence historique, voir chapitre principal §13 — stockage + email également |
utmstack-resume-a-la-demande-v12.json |
Génération à la demande (Qwen 2.5 14B) — référence historique, sans email |
utmstack-webhook-consultation.json |
Consultation web du dernier rapport stocké (lecture seule, sans regénération) |
Tous s’importent dans n8n de la même façon : Workflows → Import from File, sélectionner le .json téléchargé.
ℹ️ Quel fichier utiliser. Pour un nouveau déploiement, importer les fichiers
-llama.json(production actuelle). Les fichiers-v12.json(Qwen) restent disponibles pour qui veut reproduire fidèlement le parcours de construction documenté dans le chapitre principal, ou pour l’usage conversationnel où Qwen reste préférable (section 8 du chapitre principal) — les deux jeux de fichiers partagent la même architecture, seul le nœud d’appel Ollama diffère (nom du modèle).
⚠️ Point de vérification obligatoire après import : le nœud Merge TI doit être en mode Append, avec 5 entrées, toutes câblées. C’est le point de défaillance le plus fréquent constaté lors des tests — voir le détail du piège dans le chapitre principal. Si le mode “Combine” apparaît après import, le changer manuellement.
Après import, pour chaque nœud HTTP/PostgreSQL/SMTP, recréer les credentials dans n8n (les identifiants embarqués dans le JSON exporté ne sont pas réutilisables tels quels — voir tableau ci-dessous) et publier le workflow (toggle Actif en haut à droite de l’éditeur).
ℹ️ Credential SMTP requis pour les deux fichiers quotidien (
-llama.jsonet-v12.json). Contrairement aux versions précédentes de ce guide, la notification email n’est plus réservée à la variante cloud Mistral (section 12) — les rapports quotidiens locaux l’envoient aussi désormais. La procédure de création du credential SMTP est identique dans les deux cas ; elle est détaillée en section 12.4 pour éviter de la dupliquer — applique-la également si tu importes un fichier quotidien local.
ℹ️ Workflows de test comparatif (hors production). Les workflows utilisés pour la comparaison LLM de la section 9 du chapitre principal — envoi des données brutes sans tri déterministe à Claude Sonnet 5, DeepSeek-R1 en local, ou Mistral Large — sont également disponibles dans
/scripts, à des fins de reproduction ou d’expérimentation. Ce sont des workflows de test isolés, à déclenchement manuel uniquement, jamais destinés à un usage en production : ils n’ont pas les garde-fous du pipeline principal (pas de tri, pas de contrôle de complétion).
| Variable | Où la trouver dans le JSON | Ce qu’il faut y mettre |
|---|---|---|
| IP UTMStack / OpenSearch | URL du nœud HTTP Request |
IP de ta VM UTMStack, port 9200 |
| IP Ollama | URL du nœud HTTP Request1 |
IP de l’hôte Windows, port 11434 |
| Nom du modèle Ollama | Champ model dans le body du nœud HTTP Request1 |
utmstack-analyst-test (production, Llama) ou utmstack-analyst-qwen (référence historique / usage conversationnel) |
| Path du webhook | Nœud Webhook Trigger |
ex. resume-soc-now |
| Credentials n8n (HTTP/Postgres) | credentials.httpBasicAuth.id, credentials.postgres.id |
IDs internes n8n, propres à chaque instance — à recréer manuellement après import, jamais réutilisables tels quels |
| Credential SMTP | credentials.smtp.id du nœud Send Email Rapport SOC (fichiers quotidien uniquement) |
À créer manuellement — procédure détaillée en section 12.4 |
| Destinataire email | Champ toEmail du nœud Send Email Rapport SOC |
Ton adresse de réception |
| Clés API threat intel | Headers des nœuds AbuseIPDB Check, GreyNoise Check, OTX Check |
Tes propres clés (voir section 6) |
ℹ️ La variable la plus importante n’est pas dans ce tableau. La liste
TERMES_SIGNAL/TERMES_BRUIT/TERMES_CONTROLE, dans le nœudCode in JavaScript, n’est pas un simple identifiant à remplacer — c’est le cœur de la logique de tri, à adapter en profondeur à ton propre ruleset Suricata/UTMStack et à enrichir dans le temps, à mesure que de nouvelles sources (Windows, O365, Kali red team) apparaissent dans tes alertes. La méthode de construction de cette liste (lecture empirique des signatures réellement rencontrées, catégorisation par préfixe Emerging Threats) est détaillée dans le chapitre principal. Un lecteur qui se contente de changer les IP sans revoir cette liste aura un pipeline fonctionnel mais mal calibré pour son environnement.Mise à jour prévue. Une version enrichie de cette liste, intégrant les signatures découvertes lors des intégrations Office 365/Azure et des tests offensifs Kali (à venir), sera publiée dans le dépôt du projet une fois ces chantiers réalisés. Les fichiers JSON fournis aujourd’hui couvrent uniquement le périmètre réseau/Suricata validé à ce stade.
ollama list affiche bien les deux modèles créés (utmstack-analyst-qwen et utmstack-analyst-test)ollama ps pendant un run affiche CONTEXT: 8192 (pas 4096)curl "http://<IP>:8080/search?q=test&format=json"[COMMENTAIRE_N] résiduelle-llama.json / -v12.json)Cette section couvre le déploiement du mécanisme qui détecte une panne du pipeline (n8n, Ollama, ou connectivité) et la transforme en alerte High visible au dashboard UTMStack. Le raisonnement complet est dans le chapitre principal, section 11 — ici, uniquement la procédure.
ℹ️ Où s’exécute ce script. Contrairement au reste de cette annexe, le heartbeat tourne sur la VM UTMStack, pas sur
docker-services. Un surveillant hébergé dans le système qu’il surveille ne détecte pas la panne de ce système.
Fichiers concernés, disponibles dans /scripts : soc-pipeline-heartbeat.sh, soc-pipeline-heartbeat.service, soc-pipeline-heartbeat.timer, install-heartbeat.sh.
Ne pas sauter cette étape — installer un service qui émet dans le vide fait perdre plus de temps qu’il n’en fait gagner.
# 1. L'agent Windows ecoute-t-il ?
nc -zv <IP_AGENT_WINDOWS> 7014
# 2. Emettre un message de test
logger -n <IP_AGENT_WINDOWS> -P 7014 -T -p local0.crit -t soc-pipeline \
"SOC-PIPELINE-HEARTBEAT TEST - validation du chemin syslog"
# 3. Attendre 30-60s, puis verifier l'arrivee dans OpenSearch
docker exec $(docker ps -q -f name=utmstack_node1) curl -s \
-u admin:'<OPENSEARCH_PASSWORD>' -k \
"https://localhost:9200/v11-log-*/_search?pretty" \
-H 'Content-Type: application/json' -d '{
"size": 2,
"query": { "match_phrase": { "raw": "SOC-PIPELINE-HEARTBEAT" } },
"sort": [{"@timestamp": "desc"}]
}'
Si rien ne remonte : vérifier la règle de firewall Windows sur le port 7014 (Get-NetFirewallRule -DisplayName "UTMStack Syslog TCP 7014"), et à défaut tenter en UDP en retirant l’option -T de logger.
mkdir -p /root/heartbeat && cd /root/heartbeat
# Deposer les 4 fichiers (scp, ou creation manuelle via nano)
Une seule variable est à adapter dans soc-pipeline-heartbeat.sh :
AGENT_SYSLOG_IP="<IP_AGENT_WINDOWS>"
chmod +x soc-pipeline-heartbeat.sh
./soc-pipeline-heartbeat.sh
echo "Code retour : $?" # attendu : 0 en cas nominal
cat /var/log/soc-pipeline-heartbeat.log
Puis forcer une alerte pour valider la chaîne complète, sur une copie temporaire à seuil abaissé :
sed 's/^SEUIL_HEURES=26/SEUIL_HEURES=0/' soc-pipeline-heartbeat.sh > /tmp/hb-test.sh
chmod +x /tmp/hb-test.sh && /tmp/hb-test.sh
echo "Code retour : $?" # attendu : 1
rm /tmp/hb-test.sh
Revérifier l’arrivée dans OpenSearch avec la requête de l’étape 11.1, en cherchant cette fois SOC-PIPELINE-HEARTBEAT FAILURE.
chmod +x install-heartbeat.sh
./install-heartbeat.sh
Vérifications :
systemctl is-enabled soc-pipeline-heartbeat.timer # attendu : enabled
systemctl is-active soc-pipeline-heartbeat.timer # attendu : active
journalctl -u soc-pipeline-heartbeat.service --no-pager -n 10
⚠️ Si le service apparaît en
failedaprès une exécution qui a émis une alerte, vérifier queSuccessExitStatus=0 1figure bien danssoc-pipeline-heartbeat.service— le script sort en code1quand il alerte, c’est un comportement attendu, pas un échec du service.
Il n’existe pas de fichier .yaml pour les règles de corrélation UTMStack — elles vivent dans la table PostgreSQL utm_correlation_rules, avec la condition exprimée dans un petit langage propre à la plateforme (contains(), exists(), startsWith(), combinés en &&/||).
La création se fait via l’interface (Alerts → Correlation Rules → Create rule) plutôt qu’en INSERT SQL direct — le formulaire garantit que tous les champs requis par le schéma sont renseignés, ce qu’un insert manuel ne peut pas garantir sans connaître l’intégralité des contraintes de la table.
Onglet General Information :
| Champ | Valeur |
|---|---|
| Name | SOC Pipeline Heartbeat Failure |
| Category | Availability |
| Technique | T1489 - Service Stop |
| Data Types | syslog |
| Adversary | origin (champ technique désignant quel identifiant afficher — sans portée ici, aucune IP externe n’étant impliquée) |
| Confidentiality / Integrity / Availability | 3 / 3 / 3 (calibré sur la règle existante High level Suricata alert, qui utilise les mêmes valeurs) |
| Description | Contexte de l’alerte et pointeur vers /usr/local/bin/soc-pipeline-heartbeat.sh et son log |
| References | Lien vers cette documentation |
Build Expression :
contains("raw", "SOC-PIPELINE-HEARTBEAT FAILURE")
Onglet Post-Event Actions : laisser Deduplicated by et GroupBy vides. Si un bloc de condition “And” est présent par défaut, le retirer (icône ✕) — il sert à corréler avec un historique d’événements, ce qui n’est pas notre besoin ici.
⚠️ Le champ Adversary est obligatoire pour sauvegarder, même si la sémantique (“origin” vs “target”) n’a pas de sens pour une alerte de disponibilité pure sans IP impliquée. Sans valeur ici, le formulaire refuse silencieusement l’enregistrement.
sed 's/^SEUIL_HEURES=26/SEUIL_HEURES=0/' /usr/local/bin/soc-pipeline-heartbeat.sh > /tmp/hb-e2e.sh
chmod +x /tmp/hb-e2e.sh && /tmp/hb-e2e.sh
rm /tmp/hb-e2e.sh
Après 30-60s, vérifier l’alerte dans v11-alert-* :
docker exec $(docker ps -q -f name=utmstack_node1) curl -s \
-u admin:'<OPENSEARCH_PASSWORD>' -k \
"https://localhost:9200/v11-alert-*/_search?pretty" \
-H 'Content-Type: application/json' -d '{
"size": 3,
"query": { "match_phrase": { "name": "SOC Pipeline Heartbeat Failure" } },
"sort": [{"@timestamp": "desc"}]
}'
Résultat attendu : un document avec severityLabel: "High" et statusLabel: "Open". Vérifier aussi visuellement l’apparition de l’alerte dans le dashboard UTMStack, puis la clôturer (Completed) une fois le test validé — ne pas laisser une alerte de test en statut Open.
| Élément | Emplacement |
|---|---|
| Script | /usr/local/bin/soc-pipeline-heartbeat.sh |
| Service systemd | /etc/systemd/system/soc-pipeline-heartbeat.service |
| Timer systemd | /etc/systemd/system/soc-pipeline-heartbeat.timer (08h00 quotidien, Persistent=true) |
| Log local | /var/log/soc-pipeline-heartbeat.log |
| Règle de corrélation | utm_correlation_rules, créée via l’UI |
| Survie aux mises à jour UTMStack | Oui — l’updater ne touche ni /usr/local/bin/ ni /etc/systemd/system/ |
| Survie à un revert de snapshot | Non — à réinstaller si retour à un snapshot antérieur à l’installation |
Cette section couvre le déploiement de la variante cloud décrite dans le chapitre principal, section 10 — le remplacement du moteur Ollama/Qwen par l’API Mistral, en complément (jamais en remplacement) du pipeline local documenté dans le reste de cette annexe.
⚠️ Ne pas rester sur le tier gratuit “Experiment” pour de vraies données. Ce tier utilise les entrées/sorties API pour l’entraînement de leurs modèles par défaut (opt-out manuel requis dans Admin Console → Confidentialité si on l’utilise quand même) et n’est explicitement prévu que pour l’évaluation. Le Scale plan est une facturation à l’usage réel, sans abonnement ni engagement — pour ce pipeline (~0,20 $/mois), le passage est quasi gratuit et débloque la garantie de non-entraînement, plus un DPA self-serve (sans négociation commerciale), à vérifier/accepter dans la console selon la procédure en vigueur au moment du déploiement.
Quatre fichiers, disponibles dans /scripts :
| Fichier | Rôle |
|---|---|
utmstack-resume-quotidien-mistral.json |
Rapport officiel quotidien — tri déterministe, moteur Mistral |
utmstack-resume-a-la-demande-mistral.json |
Rapport officiel à la demande — webhook |
utmstack-resume-quotidien-libre-mistral.json |
Rapport complémentaire quotidien — analyse libre, sans tri |
utmstack-resume-a-la-demande-libre-mistral.json |
Rapport complémentaire à la demande — webhook |
Les deux variantes “officielles” réutilisent la même architecture que les workflows Qwen déjà documentés (section 8) — seul le nœud d’appel LLM change. Les deux variantes “libre” suivent le protocole de test de la section 9 du chapitre principal, mais tournent en continu avec stockage et envoi mail propres, plutôt qu’en test manuel isolé.
Pour ne jamais mélanger les deux narrations (déterministe vs libre), le rapport complémentaire utilise sa propre table :
CREATE TABLE resumes_soc_libre (
id SERIAL PRIMARY KEY,
date_generation TIMESTAMP DEFAULT NOW(),
contenu TEXT
);
Les variantes “officielle” et “libre” de la version cloud, ainsi que le rapport quotidien local (-llama.json / -v12.json, voir section 8), envoient le rapport par e-mail en plus de l’écriture en base. Le nœud d’envoi utilise un credential SMTP dédié, qui doit être créé manuellement dans n8n — pas parce que c’est un oubli du fichier fourni, mais parce que le format d’export des workflows n8n ne contient jamais les données de credential, uniquement une référence à un credential qui doit déjà exister dans l’instance cible. C’est la même contrainte, pour la même raison de sécurité, que celle déjà rencontrée pour les credentials PostgreSQL et OpenSearch en section 9 de ce document — aucun JSON, aussi complet soit-il, ne peut la contourner.
Création du credential (n8n → Credentials → Add credential → SMTP) :
| Champ | Valeur |
|---|---|
| User | <SMTP_USER> — l’adresse d’envoi, ex. une boîte partagée dédiée |
| Password | <SMTP_PASSWORD> — mot de passe d’application si le MFA est actif sur le compte |
| Host | <SMTP_HOST> — smtp.office365.com pour un tenant Microsoft 365 |
| Port | 587 |
| SSL/TLS | Désactivé — le port 587 utilise STARTTLS (négocié automatiquement), pas du SSL direct dès la connexion. Le port 465, lui, demanderait ce toggle activé |
| Client Host Name | Optionnel, laisser vide ou renseigner le nom de la VM |
Nommer ce credential “SMTP UTMStack Notifications” (le nom exact référencé dans tous les workflows qui envoient un email — local et cloud), puis le rattacher au nœud d’envoi mail de chacun après import.
ℹ️ Ce credential est partagé entre le pipeline local et la variante cloud. Une seule création suffit pour les deux : rapport quotidien local (
-llama.json/-v12.json) et variantes Mistral (officielle et libre) référencent tous le même nom de credential — pas besoin de le recréer plusieurs fois si tu utilises plusieurs de ces workflows sur la même instance n8n.
ℹ️ Pourquoi SMTP classique et pas OAuth Microsoft 365. n8n propose un nœud dédié “Microsoft Outlook” en OAuth2 via Microsoft Graph — c’est d’ailleurs la seule méthode que Microsoft continue de garantir dans la durée, l’authentification SMTP basique étant progressivement dépréciée sur Exchange Online. Ce choix SMTP classique a été retenu ici parce qu’UTMStack Community Edition lui-même ne supporte pas encore OAuth pour ses propres notifications — la config a été alignée sur le même mécanisme, avec la même adresse d’envoi, pour rester cohérent. Si l’authentification SMTP basique venait à être coupée côté tenant, la migration vers le nœud Outlook (avec une App Registration Azure en mode “Application”, pour éviter la complexité d’une boîte partagée en délégué) serait la solution durable.
Le format resourceMapper du nœud Postgres “Insert” — ce nœud n’accepte pas un simple mapping {colonne: valeur} : il attend une structure complète avec __rl (resource locator), un tableau schema décrivant chaque colonne (type, nom, métadonnées) et un tableau matchingColumns. Cette structure est normalement remplie automatiquement par l’UI n8n lorsqu’elle interroge la base de données — un JSON construit à la main sans cette étape produit une erreur relation "value.value" does not exist à l’exécution, apparemment sans rapport avec la vraie cause. Si ce nœud doit être reconstruit manuellement, copier la structure columns complète d’un nœud Insert déjà fonctionnel plutôt que la deviner.
Le paramètre responseMode du nœud Webhook Trigger — par défaut (ou si absent d’un JSON construit à la main), ce nœud répond immédiatement à l’appel HTTP entrant avec un message générique, sans attendre la fin du workflow ni utiliser un éventuel nœud “Respond to Webhook” plus loin dans la chaîne — produisant l’erreur Unused Respond to Webhook node found in the workflow. Le paramètre responseMode doit être explicitement réglé sur responseNode pour que le webhook attende la fin du pipeline et utilise la vraie réponse construite (page HTML du rapport, dans ce cas).
| Variable | Où la trouver | Ce qu’il faut y mettre |
|---|---|---|
<MISTRAL_API_KEY> |
Headers du nœud d’appel Mistral | Ta clé API, Scale plan |
<SMTP_USER>, <SMTP_PASSWORD>, <SMTP_HOST> |
Credential SMTP à créer (section 12.4) | Les identifiants de ta boîte d’envoi |
<EMAIL_DESTINATAIRE> |
Champ toEmail du nœud d’envoi mail |
L’adresse qui doit recevoir les rapports |
Les autres variables (IP UTMStack, credentials PostgreSQL/OpenSearch, clés threat intelligence) sont les mêmes que celles du tableau général de la section 9 — cette variante cloud ne change que le moteur de rédaction et l’envoi mail, pas le reste du pipeline.
Contrairement au heartbeat (section 11.6), qui a un protocole de test unique et définitif, la variante cloud mérite une vérification en deux temps : d’abord que le pipeline officiel (déterministe + Mistral) tourne sans erreur, ensuite que le contexte topologique ajouté au prompt (voir encadré ci-dessous) est bien respecté par le modèle.
Test 1 — Le pipeline officiel se termine sans balise résiduelle
Déclenche manuellement le workflow “à la demande” (webhook), puis vérifie le rapport reçu par mail ou via la page web :
[COMMENTAIRE_N] ou [CONCLUSION] visible dans le texte final⚠️ GÉNÉRATION INCOMPLÈTE en haut du rapportTest 2 — Le garde-fou topologique est respecté
Ce pipeline inclut une ligne de contexte précisant que 192.168.1.203 est l’IP WAN d’OPNsense (passerelle NAT), pas un hôte identifiable — un correctif appliqué après qu’un test en analyse libre ait produit un faux scénario de compromission basé sur cette IP (voir chapitre principal, section 9).
Dans un rapport contenant cette IP, vérifier que le texte généré :
192.168.1.203 comme un hôte compromis uniqueRésultat obtenu lors des tests de ce lab : confirmé indépendamment sur deux modèles différents (Claude Sonnet 5 et Mistral Large), chacun ayant correctement noté l’IP comme (NAT) et reformulé sa recommandation en conséquence, sans reconstruire de faux scénario de pivot. Deux runs par modèle, résultat reproductible dans les deux cas — voir le détail complet en section 9 du chapitre principal.
⚠️ Ce test ne couvre pas tout. Contrairement au heartbeat, dont le comportement est binaire (alerte émise ou non), la qualité rédactionnelle d’un LLM reste variable d’un run à l’autre. Un test réussi confirme que le garde-fou fonctionne sur le cas précis testé — il ne garantit pas l’absence d’autres erreurs d’interprétation sur des signatures différentes (voir le cas “Golden Ticket”, section 9 du chapitre principal, qui reste une erreur non corrigible par un simple ajout de contexte).
← Retour à l’index ← Pipeline SOC augmenté par IA locale