Detection-as-Code Pipeline con Git e CI/CD
Nel 2025, solo il 35% dei team di sicurezza utilizza Detection-as-Code in modo sistematico, nonostante il 63% dei professionisti dichiari di volerlo adottare regolarmente. Questo gap tra intenzione e pratica rivela un problema reale: la maggior parte delle organizzazioni non sa come costruire una pipeline DaC robusta che gestisca l'intero ciclo di vita di una regola di detection, dalla creazione al deployment in produzione.
Detection-as-Code (DaC) applica le pratiche ingegneristiche del software al dominio della sicurezza: version control, code review, testing automatizzato, CI/CD pipeline, e deployment controllato. Il risultato e una riduzione drastica del tempo tra la scoperta di una nuova tecnica di attacco e il deployment di una detection efficace, con una qualità delle regole misurabile e migliorabile nel tempo.
Cosa Imparerai in Questo Articolo
- Struttura di un repository DaC: directory layout, naming convention, schema
- Workflow Git: branching strategy, commit convention, pull request review
- GitHub Actions pipeline: lint, validate, test, convert, deploy
- Testing automatizzato con log sintetici: positivi e negativi
- Deployment sicuro a Splunk, Elastic e Sentinel via API
- Rollback automatico e gestione degli incident post-deploy
- Metriche di qualità: coverage, false positive rate, deployment frequency
- Integrazione con JIRA/GitHub Issues per tracking delle detection
perchè Detection-as-Code e Fondamentale
Prima del DaC, il processo tipico per aggiornare una regola in un SIEM era: accedere alla console web, trovare la regola, modificarla manualmente, salvarla, sperare che non rompa nulla. Nessuna versione precedente, nessun test, nessun peer review, nessuna tracciabilita delle modifiche. Quando una regola iniziava a generare migliaia di falsi positivi alle 3 di notte, era impossibile capire chi aveva fatto cosa e quando.
DaC trasforma questo processo caotico in qualcosa di sistematico:
- Ogni regola e un file di testo versionato in Git
- Ogni modifica passa attraverso una pull request con review obbligatorio
- Prima del merge, la pipeline esegue test automatici su log sintetici
- Il deployment avviene solo se tutti i test passano
- In caso di problemi, il rollback e un semplice
git revert
Struttura del Repository DaC
Un repository DaC ben strutturato e la fondazione su cui costruire tutto il resto. Ecco il layout raccomandato per un'organizzazione enterprise:
detection-as-code/
├── .github/
│ ├── workflows/
│ │ ├── pr-validation.yml # Validazione su ogni PR
│ │ ├── main-deploy.yml # Deploy su merge in main
│ │ └── nightly-test.yml # Test notturno su staging
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── CODEOWNERS
├── rules/
│ ├── windows/
│ │ ├── credential_access/
│ │ ├── lateral_movement/
│ │ └── persistence/
│ ├── linux/
│ ├── cloud/
│ │ ├── aws/
│ │ ├── azure/
│ │ └── gcp/
│ └── network/
├── tests/
│ ├── fixtures/
│ │ └── windows/
│ │ └── credential_access/
│ │ └── cred_mimikatz_lsass/
│ │ ├── positive/
│ │ └── negative/
│ └── unit/
│ └── test_sigma_rules.py
├── pipelines/
│ ├── splunk_enterprise.yml
│ └── elastic_ecs.yml
├── scripts/
│ ├── validate.py
│ ├── test_runner.py
│ ├── convert.py
│ └── deploy_splunk.py
└── Makefile
Naming Convention e Schema delle Regole
Una naming convention coerente e essenziale per la navigabilita del repository con centinaia di regole. La convenzione raccomandata per i filename:
# Pattern: <tactic_prefix>_<technique_name>_<context>.yml
cred_mimikatz_lsass.yml # credential_access
lat_psexec_remote_execution.yml # lateral_movement
per_scheduled_task_creation.yml # persistence
def_timestomp_modification.yml # defense_evasion
exe_powershell_encoded.yml # execution
exf_dns_tunneling.yml # exfiltration
c2_http_beaconing.yml # command_and_control
Branching Strategy per DaC
# Flusso standard per nuove detection
git checkout -b detection/T1003-lsass-dump-via-procdump
# ... crea/modifica la regola e i test ...
git add rules/windows/credential_access/cred_lsass_procdump.yml
git add tests/fixtures/windows/process_creation/procdump_positive.json
git commit -m "feat(detection): T1003.001 LSASS dump via ProcDump
Adds detection for LSASS memory dump via legitimate ProcDump utility.
ATT&CK: T1003.001 - OS Credential Dumping: LSASS Memory
Severity: high
Test coverage: 3 positive, 2 negative fixtures"
git push origin detection/T1003-lsass-dump-via-procdump
# Flusso hotfix per detection urgenti (attacco in corso)
git checkout -b hotfix/active-incident-lateral-movement-mar2026
git commit -m "hotfix(detection): emergency rule for active APT lateral movement
Active incident response. Incident: INC-2026-0312"
Pipeline CI/CD Completa con GitHub Actions
# .github/workflows/pr-validation.yml
name: Detection Rule Validation
on:
pull_request:
paths:
- 'rules/**/*.yml'
- 'tests/**'
jobs:
lint-yaml:
name: YAML Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run yamllint
uses: ibiqlik/action-yamllint@v3
with:
file_or_dir: rules/
config_data: |
extends: default
rules:
line-length:
max: 120
validate-schema:
name: Schema Validation
runs-on: ubuntu-latest
needs: lint-yaml
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install pyyaml jsonschema sigma-cli
- name: Validate Sigma schemas
run: |
python scripts/validate.py ./rules \
--schema schemas/sigma_rule_schema.json
- name: Check UUID uniqueness
run: python scripts/check_uuid_uniqueness.py ./rules
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
needs: validate-schema
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install pytest pyyaml sigma-cli pySigma-backend-splunk
- name: Run detection tests
run: |
pytest tests/unit/ \
-v \
--tb=short \
--junit-xml=test-results.xml
convert-validate:
name: Conversion Test
runs-on: ubuntu-latest
needs: unit-tests
strategy:
matrix:
target: [splunk, elasticsearch, microsoft365defender]
steps:
- uses: actions/checkout@v4
- run: |
pip install sigma-cli \
pySigma-backend-splunk \
pySigma-backend-elasticsearch \
pySigma-backend-microsoft365defender
- name: Test conversion to ${{ matrix.target }}
run: |
sigma convert -t ${{ matrix.target }} rules/ \
2>&1 | tee conversion-${{ matrix.target }}.log
# .github/workflows/main-deploy.yml
name: Deploy Detection Rules to Production
on:
push:
branches: [main]
paths: ['rules/**/*.yml']
jobs:
deploy-splunk:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Identify changed rules
id: changed-rules
run: |
CHANGED=$(git diff --name-only HEAD~1 HEAD -- 'rules/**/*.yml')
echo "files=$CHANGED" >> $GITHUB_OUTPUT
- name: Convert changed rules
run: |
pip install sigma-cli pySigma-backend-splunk
echo "${{ steps.changed-rules.outputs.files }}" | while read rule; do
[ -f "$rule" ] && sigma convert -t splunk -p splunk_windows "$rule" \
>> converted.spl
done
- name: Deploy to Splunk via REST API
env:
SPLUNK_HOST: ${{ secrets.SPLUNK_HOST }}
SPLUNK_TOKEN: ${{ secrets.SPLUNK_TOKEN }}
run: |
python scripts/deploy_splunk.py \
--rules-file converted.spl \
--host "$SPLUNK_HOST" \
--token "$SPLUNK_TOKEN" \
--dry-run false
- name: Notify on failure
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'DEPLOY FAILURE: Detection rules deployment failed',
body: 'Run: ' + context.serverUrl + '/' +
context.repo.owner + '/' + context.repo.repo +
'/actions/runs/' + context.runId,
labels: ['incident', 'deployment-failure']
})
Testing Framework per Detection Rules
Il testing e il cuore del DaC. Ecco il framework di test Python completo:
#!/usr/bin/env python3
"""Detection Rule Test Framework."""
import json
import yaml
import pytest
import subprocess
from pathlib import Path
from typing import NamedTuple
class TestCase(NamedTuple):
rule_path: Path
fixture_path: Path
should_match: bool
def load_test_cases() -> list[TestCase]:
"""Carica tutti i test case dalla struttura di directory."""
test_cases = []
rules_dir = Path("rules")
fixtures_dir = Path("tests/fixtures")
for rule_file in rules_dir.rglob("*.yml"):
rel_path = rule_file.relative_to(rules_dir)
fixture_dir = fixtures_dir / rel_path.parent / rule_file.stem
if fixture_dir.exists():
for pos_file in (fixture_dir / "positive").glob("*.json"):
test_cases.append(TestCase(rule_file, pos_file, True))
for neg_file in (fixture_dir / "negative").glob("*.json"):
test_cases.append(TestCase(rule_file, neg_file, False))
return test_cases
class TestDetectionRules:
"""Test suite per le detection rules."""
@pytest.mark.parametrize("test_case", load_test_cases())
def test_rule_against_fixture(self, test_case: TestCase):
rule_path, fixture_path, should_match = test_case
with open(fixture_path) as f:
log_event = json.load(f)
# Valuta la regola contro l'evento (implementazione omessa per brevita)
matched = evaluate_sigma_rule(rule_path, log_event)
if should_match:
assert matched, (
f"Rule {rule_path.name} SHOULD match {fixture_path.name} "
f"but did NOT match.\nEvent: {json.dumps(log_event, indent=2)}"
)
else:
assert not matched, (
f"Rule {rule_path.name} should NOT match {fixture_path.name} "
f"but DID match (false positive).\n"
f"Event: {json.dumps(log_event, indent=2)}"
)
def test_no_duplicate_ids(self):
"""Verifica che non ci siano UUID duplicati nel repository."""
seen_ids = {}
duplicates = []
for rule_file in Path("rules").rglob("*.yml"):
with open(rule_file) as f:
rule = yaml.safe_load(f)
rule_id = rule.get('id', '')
if rule_id in seen_ids:
duplicates.append(
f"{rule_file} duplicates ID of {seen_ids[rule_id]}"
)
else:
seen_ids[rule_id] = rule_file
assert not duplicates, "Duplicate IDs:\n" + "\n".join(duplicates)
def test_all_rules_have_tests(self):
"""Verifica che ogni regola abbia almeno un test positivo."""
rules_dir = Path("rules")
fixtures_dir = Path("tests/fixtures")
missing = []
for rule_file in rules_dir.rglob("*.yml"):
rel_path = rule_file.relative_to(rules_dir)
pos_dir = fixtures_dir / rel_path.parent / rule_file.stem / "positive"
if not pos_dir.exists() or not list(pos_dir.glob("*.json")):
missing.append(str(rule_file))
if missing:
pytest.fail(
"Rules missing positive test fixtures:\n" +
"\n".join(f" - {r}" for r in missing)
)
Fixture di Test: Esempi di Log Sintetici
# tests/fixtures/windows/credential_access/
# cred_mimikatz_lsass/positive/mimikatz_direct.json
{
"EventID": 10,
"Channel": "Microsoft-Windows-Sysmon/Operational",
"SourceImage": "C:\\Users\\attacker\\Downloads\\mimikatz.exe",
"TargetImage": "C:\\Windows\\System32\\lsass.exe",
"GrantedAccess": "0x1010",
"UtcTime": "2026-03-09 14:30:00.123"
}
# cred_mimikatz_lsass/negative/windows_defender_scan.json
{
"EventID": 10,
"Channel": "Microsoft-Windows-Sysmon/Operational",
"SourceImage": "C:\\Program Files\\Windows Defender\\MsMpEng.exe",
"TargetImage": "C:\\Windows\\System32\\lsass.exe",
"GrantedAccess": "0x1000",
"UtcTime": "2026-03-09 14:31:00.456"
}
Metriche di qualità DaC
| Metrica | Definizione | Target |
|---|---|---|
| Detection Coverage | % tecniche ATT&CK coperte da regola stable | > 60% |
| Test Coverage | % regole con test positivo e negativo | 100% |
| False Positive Rate | Alert FP / Alert totali per settimana | < 10% |
| Deployment Frequency | Merge con nuove regole per settimana | > 3/settimana |
| Time to Detection (TTD) | Tempo da tecnica pubblicata a regola deployata | < 48h (alta severita) |
Anti-Pattern da Evitare
- Deploy senza test: anche in emergenza, almeno uno smoke test manuale
- Secrets in chiaro: usa sempre GitHub Secrets, mai hardcoded
- Pipeline senza notifiche: un deploy fallito silenzioso e pericoloso
- Regole senza owner: usa CODEOWNERS per ogni area tecnica
- Solo test positivi: senza fixture negative non sai se generi FP
Conclusioni
Detection-as-Code non e solo un cambio di strumenti, e un cambio di mentalita: le detection sono codice, e come tale devono essere testate, versionate, riviste e deployate in modo sistematico. Una pipeline DaC matura riduce il Time-to-Detection, aumenta la qualità delle regole, e permette di scalare il programma di detection senza aumentare proporzionalmente il personale.
Prossimo Articolo della Serie
Nel prossimo articolo vedremo come integrare MITRE ATT&CK nel workflow DaC per mappare automaticamente i gap di copertura e prioritizzare le nuove detection in base al rischio reale.
Domande frequenti
Qual è il problema principale che affrontano le organizzazioni che desiderano adottare Detection-as-Code?
Il problema principale è la mancanza di conoscenze per costruire una pipeline robusta e gestire l'intero ciclo di vita delle regole di detection.
Quali sono i benefici principali dell'utilizzo di Detection-as-Code?
I benefici principali includono una riduzione del tempo tra la scoperta di nuove tecniche di attacco e il deployment di una detection efficace, nonché una qualità delle regole misurabile e migliorabile nel tempo.
Come si differenzia il processo di aggiornamento delle regole in un SIEM prima dell'utilizzo di Detection-as-Code?
Il processo tipico prevedeva l'accesso alla console web, la modifica manuale della regola, la salva e la speranza che non rompesse nulla, senza versioni precedenti, test o peer review.
Qual è il ruolo del repository DaC nella struttura di un sistema Detection-as-Code?
Il repository DaC ben strutturato è la fondazione su cui costruire tutto il resto e rappresenta la base per l'organizzazione dell'intero processo.

Commenti
Caricamento commenti...
Accedi per commentare