Importer le corpus de l'ancien projet
Un script peuple la base depuis _source-oldproject, l'application Python que SekoiaCraft
remplace. Il importe les technologies, leurs messages types, leurs templates et leurs données.
pnpm db:seed [chemin-du-corpus]
Le chemin est optionnel : à défaut, _source-oldproject à la racine du dépôt.
C'est l'étape « données de référence » du démarrage. La commande construit au
passage les packages dont l'import dépend — il les charge par leur nom, donc depuis leur dist/.
Sans ce build, l'équivalent bas niveau pnpm --filter @sekoiacraft/database seed:source échoue sur
une résolution de module tant que le dépôt n'a pas été construit.
_source-oldproject/ est dans le .gitignore — il contient les clés d'intake de production des
communities SEKOIA. Le sondage du corpus est donc un geste local, jamais une dépendance de test
ni de CI. Si le dossier manque, la commande ne s'arrête pas pour autant : elle charge
l'instantané SQL versionné dans le dépôt.
Ce qu'il importe
| Source | Destination | Volume |
|---|---|---|
generators/__init__.py | Technologies (nom, format, alertes seules) | 28 |
<technologie>.py | Messages types, avec leurs poids | 470 |
event/<source>/templates/*.jinja | Templates Nunjucks | 470 |
event/<source>/data/*.json | Pools d'échantillons | 383 |
conf/{users,servers,web}/*.json | Populations | 6 |
Le script exige python3 : c'est sa seule dépendance. Il importe réellement les classes du
registre plutôt que d'en lire la source, parce que les listes de messages types sont parfois
héritées et que les poids sont calculés, pas déclarés.
Sans le corpus, l'instantané
Ces deux exigences — le dossier et Python — n'ont leur place ni dans une image Docker ni dans un
runner de CI. pnpm db:seed choisit donc seul : le corpus quand il est présent, et sinon
l'instantané SQL de packages/database/prisma/seed/corpus/, un fichier
par technologie, versionné en clair. C'est par là que les environnements déployés reçoivent leurs
technologies, leurs messages types et leurs gabarits.
L'instantané n'est pas une seconde source de vérité : il est photographié depuis une base que
cet import vient d'écrire. Le corollaire est à retenir — un rafraîchissement du corpus, ou une
correction des règles de réécriture des gabarits, ne prend effet en CI et en déploiement qu'après
un pnpm db:snapshot.
Ce qu'il déduit
Rien de tout cela n'est écrit dans la source : le script appelle chaque générateur et note ce qu'il demande à rendre. Le rapport final compte chaque déduction, pour qu'aucune approximation ne passe pour un fait.
Le template — la convention « un message type rend par le fichier .jinja qui porte son nom »
ne vaut plus. windows/process-created rend par security-4688.jinja, les 24 messages types de
Microsoft IIS passent tous par iis-access.jinja, et Sekoia Agent choisit son corps selon l'OS de
l'entité. Le script observe le gabarit réellement atteint ; quand un message type en atteint
plusieurs, ils sont réunis derrière un {% if %}.
Les constantes — le générateur source calculait hors du gabarit ce qui distingue deux messages
types partageant un même fichier : remote-auth-success et local-auth-success rendent tous deux
par security-logon.jinja et ne diffèrent que par leur enveloppe. Ces valeurs sont posées en
préambule {% set %}, une fois par message type — près de 5 000 lignes sur l'ensemble du corpus.
La catégorie — l'ancien projet n'en a aucune notion. Elle est déduite du nom du message type
(auth-failure → AUTH, dns-query-result → DNS, process-created → PROCESS…). Sur les 470,
297 sont déduites et 173 reçoivent la valeur par défaut NETWORK, à corriger depuis
l'interface.
Le format de sortie — il est lu dans les templates, sur le corps une fois les balises de tête
retirées. La majorité fait loi au niveau de la technologie ; celui qui déroge reçoit une valeur
propre — aujourd'hui pan_ngfw/dns, seul objet JSON d'un générateur qui émet du CSV positionnel.
Le rattachement des données — un message type reçoit le pool que le générateur lui donne
réellement, identifié par le contenu du fichier et non par son nom : zscaler-event est
alimenté par zscaler.json, et sept messages types d'Office 365 se partagent message-trace.json.
420 des 470 sont rattachés ainsi ; les autres passent par le nom, ou n'ont pas de fichier de
données dans la source — ce qui est normal.
Ce qu'il écarte
Le corpus source n'est pas le catalogue : certains messages types n'ont pas leur place dans
SekoiaCraft, quoi qu'en dise l'ancien projet. Ils sont listés dans
packages/database/prisma/seed/excluded-event-types.ts, et le rapport final les nomme — un retrait
volontaire doit se lire, pas se deviner d'un compte qui baisse.
| Technologie | Message type | Motif |
|---|---|---|
| Akamai WAF | waf-allow | Trafic autorisé, retiré du catalogue — 800 des 850 points de poids |
L'exclusion coupe aux deux bouts : ni message type, ni pool de données. C'est ici qu'elle est
portée, et non par une suppression depuis l'interface, parce que l'import est rejoué à chaque
seed:source — ce qui n'est retiré qu'en base reviendrait au passage suivant. Le script purge
d'ailleurs ce qu'un import antérieur avait créé, pour qu'une base déjà peuplée converge.
Il est rejouable
Tout passe par des upsert : un second passage ne duplique rien. Il ne réécrit pas un template
que vous auriez édité à la main — sauf demande explicite :
pnpm db:seed --overwrite-templates
Si une technologie existe déjà sous un nom dérivé de son dossier, elle est renommée en place plutôt que dupliquée : ses datasets restent les siens.
Repartir d'une base vide
L'import complète une base ; il ne la nettoie pas. Pour effacer et recharger d'un coup :
pnpm db:reset [chemin-du-corpus] [--overwrite-templates]
Quatre étapes enchaînées : build des packages dont le seed dépend, prisma db push --force-reset
(drop de la base puis schéma), purge des files BullMQ, puis cet import — auquel les arguments sont
transmis tels quels.
La purge Redis efface les clés du seul préfixe courant (REDIS_QUEUE_PREFIX, sekoiacraft-dev
en local), jamais tout Redis : dev et preprod partagent la même instance. Elle est nécessaire parce
que les jobs log-dispatch en attente référencent des lots d'envoi qui viennent de disparaître.
Le drop est irréversible et sans confirmation. Le script refuse de démarrer si DATABASE_URL
ou REDIS_URL ne pointent pas sur localhost — un .env recopié de .env.preprod ne peut donc
pas emporter la base de preprod. Passer outre demande un ALLOW_REMOTE_RESET=1 explicite.
Ce qui ne rend pas encore
L'import remplit la base ; il ne rend pas tout fonctionnel pour autant. Sur les 470 messages types :
| rendent et valident | 457 |
| rendent une sortie invalide | 13 |
| ne rendent pas | 0 |
| sans template | 0 |
Les treize restants butent tous sur la même chose : des idiomes propres à Jinja2 que Nunjucks
ne connaît pas — filename.endswith(…), _map.get(…), rsplit, le littéral dictionnaire {{{.
Ils se corrigent un par un, dans le gabarit, depuis l'éditeur.
Le contexte de rendu, lui, ne manque plus : les 470 messages types ne référencent plus une seule racine que le moteur n'expose pas. Ce que le générateur source calculait au-dehors — constantes d'enveloppe, identifiants, valeurs tirées de l'entité ou du pool — est posé en préambule à l'import.
Plus aucun gabarit ne refuse de compiler aujourd'hui, mais le garde-fou reste : quand un template
ne compile pas, il est stocké tel quel et signalé au rapport (Templates cassés). Le perdre
priverait de ce qu'il faut corriger, et l'éditeur situe l'erreur à la ligne.
Les trois qui figuraient ici — sekoia_agent/registry-value-set, vmware_vcenter/lro-operation et
vmware_vcenter/local-auth-failure — butaient sur la tranche Python (op_id[:8]), que Nunjucks
ne sait pas analyser : son [ ouvre un accès indexé, et le : y est une erreur de syntaxe. Elle
est désormais traduite en truncate à l'import. La même passe a réglé le formatage % de
Python, plus sournois puisqu'il compilait : en JavaScript le % est un modulo, si bien que
'%03d' % 45 rendait NaN dans l'horodatage de 32 gabarits vCenter et Salesforce, sans qu'aucune
validation ne s'en aperçoive — la sortie restait un JSON valide.