|8 min de lecture|Yvann Lièvre

Detection-as-code Sigma : pipeline CI/CD pour tester et versionner ses règles

Pipeline CI/CD Sigma detection as code : validate, translate, test, deploy, fixtures ATT&CK et gouvernance experimental/stable, sans règle non testée en prod.

SigmaCI/CDDetection EngineeringDevSecOps
Detection-as-code Sigma : pipeline CI/CD pour tester et versionner ses règles

Une règle Sigma est écrite en dix minutes. Un dossier de cent règles Sigma sans workflow de test devient, en quelques mois, un champ de mines : personne ne sait plus lesquelles matchent réellement la télémétrie, lesquelles sont mortes depuis un renommage de champ, lesquelles noient l'équipe sous les faux positifs. La detection-as-code n'est pas un slogan : c'est l'application, à des règles de détection, de la discipline qu'on impose depuis longtemps au code applicatif (revue, tests, versioning, déploiement contrôlé). Ce billet décrit le pipeline concret : validate, translate, test, deploy, avec des gates qui bloquent réellement le merge.

Pourquoi une règle Sigma mérite le même traitement qu'un commit

Une règle Sigma promue en production sans validation a trois modes de défaillance fréquents : elle ne convertit pas vers le backend cible (un champ mal nommé, une syntaxe de détection invalide), elle convertit mais ne matche jamais rien (un champ de log qui ne correspond pas au schéma réel du SIEM), ou elle matche trop largement et génère un flux de faux positifs qui fait couper l'alerte par l'analyste au bout de deux semaines. Aucun de ces trois cas ne se voit à la lecture du YAML. Ils se voient à l'exécution, contre un moteur réel et une télémétrie réelle. D'où la nécessité d'un pipeline qui exécute, pas seulement qui relit.

Le pipeline en quatre étapes

Le schéma général tient en quatre verbes : valider, traduire, tester, déployer. Chaque étape est un gate indépendant, et chacune peut faire échouer le pipeline.

# .gitlab-ci.yml (ou equivalent Forgejo/GitHub Actions)
stages:
  - validate
  - translate
  - test
  - deploy
 
sigma-validate:
  stage: validate
  script:
    - pip install sigma-cli
    - sigma check rules/
 
sigma-translate:
  stage: translate
  script:
    - sigma convert -t splunk -p splunk_windows rules/ -o build/splunk/
    - sigma convert -t elasticsearch-lucene -p ecs_windows rules/ -o build/elastic/
 
sigma-test:
  stage: test
  script:
    - sigma check --validate-correlations rules/
    - python -m pytest tests/ -v
 
sigma-deploy:
  stage: deploy
  only:
    - main
  script:
    - ./scripts/deploy_rules.sh build/

Validate vérifie la syntaxe et le schéma Sigma lui-même (champs obligatoires, structure de detection, références valides) avec sigma check. C'est le niveau le plus faible mais aussi le plus rapide à exécuter : il écarte en quelques secondes les erreurs de frappe avant d'aller plus loin.

Translate convertit chaque règle vers le ou les backends cibles réellement utilisés en production, avec sigma convert -t <backend>. Une règle qui ne convertit pas casse le build ici, avant même d'atteindre l'étape de test. C'est le premier vrai gate : une règle Sigma qui ne se traduit pas vers Splunk ou Elastic est une règle qui ne sert à rien dans un environnement qui tourne sur Splunk ou Elastic.

Test est l'étape qui distingue une règle valide d'une règle qui détecte réellement quelque chose, détaillée ci-dessous.

Deploy ne s'exécute que sur la branche principale, après que les trois étapes précédentes soient vertes, et pousse vers le SIEM ou l'EDR de destination.

Prouver qu'une règle matche : les fixtures positives et négatives

Le point que la plupart des workflows Sigma sautent complètement : une règle qui convertit sans erreur n'a jamais été testée contre de la télémétrie. sigma-cli (ou des tests pySigma directs en Python) permettent d'associer chaque règle à des échantillons de logs et de vérifier le résultat attendu.

# structure de test attendue
tests/
  fixtures/
    positive/
      lsass_dump_procdump.evtx.json     # doit matcher
      lsass_dump_taskmgr.evtx.json      # doit matcher
    negative/
      procdump_normal_dump.evtx.json    # doit rester silencieux
      lsass_read_by_edr_agent.evtx.json # faux positif connu, doit rester silencieux
 
  test_lsass_dump.py
# tests/test_lsass_dump.py
from sigma.collection import SigmaCollection
from sigma.backends.splunk import SplunkBackend
import json
 
def load_rule():
    return SigmaCollection.from_yaml(open("rules/credential_access/lsass_dump.yml"))
 
def test_matches_procdump_lsass_positive():
    events = json.load(open("tests/fixtures/positive/lsass_dump_procdump.evtx.json"))
    result = evaluate_rule(load_rule(), events)
    assert result.matched is True
 
def test_silent_on_normal_procdump():
    events = json.load(open("tests/fixtures/negative/procdump_normal_dump.evtx.json"))
    result = evaluate_rule(load_rule(), events)
    assert result.matched is False

Une fixture positive prouve que la règle détecte réellement le comportement visé. Une fixture négative, construite à partir d'un cas connu pour ressembler à l'attaque sans en être une (un dump mémoire légitime par un outil de diagnostic, un agent EDR qui lit LSASS pour son propre monitoring), prouve que la règle ne va pas noyer l'équipe. Sans fixture négative, une règle "large" passe tous les tests et explose en faux positifs dès le premier jour en production.

Intégrer Atomic Red Team ou un droid d'attaque simulée

Les fixtures statiques valident la logique de matching, mais elles ne prouvent pas que la règle détecte la technique ATT&CK correspondante dans un environnement réel, avec le bruit de fond d'un vrai système. C'est le rôle d'une simulation d'attaque comme Atomic Red Team (ou un droid interne qui rejoue des techniques MITRE ATT&CK contre un lab isolé) : exécuter la technique T1003.001 (OS Credential Dumping : LSASS Memory) sur une machine instrumentée, capturer la télémétrie produite, et vérifier que la règle Sigma se déclenche sur ce flux réel avant même d'envisager la production.

# execution d'un atomic test cible sur la technique visee par la regle
Invoke-AtomicTest T1003.001 -TestNumbers 1
 
# export de la telemetrie generee (Sysmon/EDR) vers le pipeline de test
# puis rejeu contre le backend converti (etape translate) pour confirmation finale
sigma convert -t splunk rules/credential_access/lsass_dump.yml | \
  splunk-test-runner --against captured_telemetry.json

Cette étape est la seule qui ferme réellement la boucle : advisory ou hypothèse de menace, règle écrite, fixtures unitaires vertes, puis preuve sur une simulation d'attaque qui reproduit la technique. Une règle qui n'a pas passé ce dernier palier est une hypothèse, pas une détection validée.

Versioning et gouvernance : experimental, stable, et les faux positifs assumés

Une règle Sigma porte des métadonnées de gouvernance qui doivent être traitées comme des champs obligatoires du pipeline, pas comme de la documentation optionnelle.

title: Suspicious LSASS Memory Dump via ProcDump
status: experimental          # experimental -> test -> stable
id: 7d3a1c9e-...
related:
  - id: 5b8f2e11-...
    type: derived              # cette regle derive d'une regle plus ancienne
falsepositives:
  - "Legitimate memory dump by an authorized incident response tool"
  - "EDR agent process reading LSASS for its own monitoring (see negative fixture)"
level: high

Le champ status suit un cycle de vie explicite : une règle naît en experimental, passe en test une fois ses fixtures et son atomic test verts, puis en stable après une période d'observation en production sans faux positif non documenté. Le champ related trace la généalogie des règles (dérivation, renommage, fusion), essentiel quand une règle stable doit être retirée ou remplacée sans casser l'historique d'alertes. Le champ falsepositives n'est pas une case à cocher : chaque faux positif connu et accepté doit y être déclaré, avec le contexte qui l'explique, pour que l'analyste qui reçoit l'alerte sache immédiatement s'il regarde un cas documenté ou une vraie anomalie.

Le gate qui bloque réellement le merge

Le pipeline décrit plus haut n'a de valeur que si l'échec d'une étape bloque effectivement l'intégration. En pratique cela veut dire configurer la protection de branche pour exiger les trois checks (sigma-validate, sigma-translate, sigma-test) comme statuts obligatoires avant merge, et refuser tout contournement manuel. Une règle qui ne convertit pas vers le backend de production, ou dont une fixture échoue, ne doit jamais pouvoir atteindre main par un merge forcé ou une exception de dernier moment. C'est la différence entre un pipeline décoratif (des checks verts qu'on ignore quand ça presse) et un pipeline de gouvernance (des checks qui sont la seule porte d'entrée).

En résumé

Un pipeline detection-as-code pour Sigma tient en quatre étapes qui se vérifient chacune contre le réel : valider la syntaxe, traduire vers le backend de production, prouver le matching avec des fixtures positives et négatives, confirmer la détection contre une simulation d'attaque type Atomic Red Team. La gouvernance (status, related, falsepositives) transforme un tas de fichiers YAML en un catalogue de règles dont on sait, pour chacune, ce qu'elle détecte, ce qu'elle risque de déclencher à tort, et depuis quand elle est fiable.

C'est exactement ce travail que nous menons en amont pour constituer le pack de règles Sigma : chaque règle y est déjà passée par validate, translate, test et une preuve de détection sur simulation d'attaque, avec son statut de gouvernance et ses faux positifs déclarés, prête à s'insérer directement dans votre propre pipeline.

Articles liés