Salta al contenuto principale

02 - Detection-as-Code Pipeline con Git e CI/CD

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%...

Cosa ne pensi di questo articolo?

02 - Detection-as-Code Pipeline con Git e CI/CD
Detection EngineeringFoto:Ruben BoekelooPexels

In Breve

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%...

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.

Fonti e approfondimenti

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti