Pipeline Detection-as-Code cu Git și CI/CD
În 2025, doar 35% din echipele de securitate utilizări Detection-as-Code în mod sistematic, în ciuda a 63% dintre profesioniști declarați că doriți să o adoptați în mod regulat. Acest decalaj între intenție și practică dezvăluie o problemă reală: majoritatea organizațiilor nu știu ca construiți o conductă DaC robustă care se ocupă de întregul ciclu durata de viață a unei reguli de detectare, de la creare până la implementare în producție.
Detectare ca cod (DaC) aplică practici de inginerie de software către domeniul de securitate: controlul versiunilor, revizuirea codului, testarea automată, conductă CI/CD și implementare controlată. Rezultatul este unul reducerea drastică a timpului dintre descoperirea unei noi tehnici de atac și implementarea unei detectări eficiente, cu reguli de calitate măsurabile și îmbunătățibile în timp.
Ce veți învăța în acest articol
- Structura unui depozit DaC: aspectul directorului, convenția de denumire, schema
- Flux de lucru Git: strategie de ramificare, convenție de comitere, revizuire a cererii de extragere
- Conducta GitHub Actions: scame, validați, testați, convertiți, implementați
- Testare automată cu loguri sintetice: pozitiv și negativ
- Implementare sigură în Splunk, Elastic și Sentinel prin API
- Rollback automat și gestionarea incidentelor după implementare
- Valori de calitate: acoperire, rata fals pozitive, frecvența de implementare
- Integrare cu JIRA/GitHub Issues pentru urmărirea detectării
deoarece Detectarea-ca-Cod este fundamentală
Înainte de DaC, procesul tipic pentru actualizarea unei reguli într-un SIEM era: accesați consola web, găsiți regula, editați-o manual, salvați-o, sper sa nu rupa nimic. Fără versiuni anterioare, fără teste, nu evaluare inter pares, nicio trasabilitate a modificărilor. Când a început o regulă pentru a genera mii de fals pozitive la 3 dimineața, era imposibil de înțeles cine făcuse ce şi când.
DaC transformă acest proces haotic în ceva sistematic:
- Fiecare regulă este un fișier text versionat în Git
- Fiecare modificare trece printr-o cerere de extragere cu revizuire obligatorie
- Înainte de fuzionare, conducta rulează teste automate pe bușteni sintetici
- Implementarea are loc numai dacă toate testele trec
- În caz de probleme, rollback-ul este o chestiune simplă
git revert
Structura depozitului DaC
Un depozit DaC bine structurat este fundația pe care se construiește totul. Iată aspectul recomandat pentru o organizație de întreprindere:
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
Convenția de denumire și schema de reguli
O convenție de denumire coerentă și esențială pentru navigabilitatea depozitului cu sute de reguli. Convenția de nume de fișier recomandată:
# 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
Strategie de ramificare pentru 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"
Completați pipeline CI/CD cu acțiuni GitHub
# .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']
})
Cadrul de testare pentru regulile de detectare
Testarea este inima DaC. Iată cadrul complet de testare Python:
#!/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)
)
Dispozitive de testare: Exemple de bușteni 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"
}
Măsuri de calitate DaC
| Metric | Definiţie | Ţintă |
|---|---|---|
| Acoperire de detectare | % tehnici ATT&CK acoperite de regula stabilă | > 60% |
| Acoperire de testare | % reguli cu teste pozitive și negative | 100% |
| Rata fals pozitivă | Alerte FP / Total alerte pe săptămână | < 10% |
| Frecvența de implementare | Fuzionați cu reguli noi pe săptămână | > 3/saptamana |
| Timp până la detectare (TTD) | Timp de la tehnica publicată la regula implementată | < 48h (severitate mare) |
Anti-modele de evitat
- Implementați fără testare: chiar și în caz de urgență, cel puțin un test manual de fum
- Secrete în clar: folosiți întotdeauna secretele GitHub, niciodată hardcode
- Conductă fără notificări: o desfășurare eșuată tăcută și periculoasă
- Reguli fără proprietari: utilizați CODEOWNERS pentru fiecare domeniu tehnic
- Doar teste pozitive: fara fixtures negative nu stii daca generezi FP
Concluzii
Detection-as-Code nu este doar o schimbare de instrumente, este o schimbare de mentalitate: detecțiile sunt cod și, ca atare, trebuie testate, versionate, revizuite și implementați sistematic. O conductă DaC matură reduce timpul de detectare, crește calitatea regulilor și vă permite să scalați programul de detectare fără a crește proporțional personalul.
Următorul articol din serie
În articolul următor vom vedea cum să integrăm MITRE ATT&CK în fluxul de lucru DaC pentru a mapa automat lacunele de acoperire și pentru a stabili priorități noile depistari bazate pe riscul real.







