Detection-as-Code Pipeline s Git a CI/CD
V roce 2025 pouze 35 % bezpečnostních týmů používá Detekce jako kód systematicky, navzdory 63 % profesionálů prohlašujete, že jej chcete pravidelně přijímat. Tato propast mezi záměrem a praxí odhaluje skutečný problém: většina organizací neví jako vybudovat robustní DaC potrubí, které zvládne celý cyklus životnost detekčního pravidla, od vytvoření až po nasazení v produkci.
Detekce jako kód (DaC) aplikuje inženýrské postupy softwaru do bezpečnostní domény: kontrola verzí, kontrola kódu, testování automatizované, CI/CD potrubí a řízené nasazení. Výsledek je jeden drastické zkrácení doby mezi objevením nové techniky útoku a zavedení účinné detekce s pravidly kvality měřitelné a zlepšovatelné v čase.
Co se dozvíte v tomto článku
- Struktura úložiště DaC: rozložení adresářů, konvence pojmenování, schéma
- Pracovní postup Git: strategie větvení, konvence odevzdání, kontrola požadavku na stažení
- Kanál akcí GitHub: lint, ověřovat, testovat, převádět, nasazovat
- Automatizované testování se syntetickými protokoly: pozitivní a negativní
- Bezpečné nasazení do Splunk, Elastic a Sentinel přes API
- Automatické vrácení zpět a správa incidentů po nasazení
- Metriky kvality: pokrytí, míra falešných poplachů, frekvence nasazení
- Integrace s JIRA/GitHub Problémy pro sledování detekce
protože Detection-as-Code je zásadní
Před DaC byl typický proces aktualizace pravidla v SIEM: vstoupit do webové konzole, najít pravidlo, upravit jej ručně, uložit, doufám, že to nic nezlomí. Žádné předchozí verze, žádné testy, ne peer review, žádná sledovatelnost změn. Když začalo pravidlo generovat tisíce falešně pozitivních výsledků ve 3 hodiny ráno, to nebylo možné pochopit kdo co a kdy udělal.
DaC transformuje tento chaotický proces na něco systematického:
- Každé pravidlo je textový soubor s verzí v Gitu
- Každá změna prochází žádostí o stažení s povinnou kontrolou
- Před sloučením potrubí spustí automatické testy na syntetických kmenech
- K nasazení dojde pouze v případě, že projdou všechny testy
- V případě problémů je rollback jednoduchou záležitostí
git revert
Struktura DaC úložiště
Dobře strukturované úložiště DaC je základem, na kterém je postaveno vše ostatní. Zde je doporučené rozložení pro podnikovou organizaci:
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
Schéma pojmenování a pravidel
Souvislá a zásadní konvence pojmenování pro splavnost úložiště se stovkami pravidel. Doporučená konvence pro názvy souborů:
# 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 větvení pro 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"
Dokončete kanál CI/CD pomocí akcí 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']
})
Testovací rámec pro pravidla detekce
Testování je srdcem DaC. Zde je kompletní testovací rámec Pythonu:
#!/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)
)
Testovací přípravky: Příklady syntetických protokolů
# 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"
}
Metriky kvality DaC
| Metrický | Definice | Cíl |
|---|---|---|
| Pokrytí detekcí | % ATT&CK technik pokrytých stabilním pravidlem | > 60 % |
| Test pokrytí | % pravidel s pozitivními a negativními testy | 100 % |
| Falešně pozitivní sazba | FP Alerts / Total Alerts za týden | < 10 % |
| Frekvence nasazení | Sloučení s novými pravidly týdně | > 3/týden |
| Čas do detekce (TTD) | Čas od zveřejněné techniky k nasazenému pravidlu | < 48 h (vysoká závažnost) |
Anti-vzory, kterým je třeba se vyhnout
- Nasazení bez testování: i v případě nouze alespoň jeden ruční kouřový test
- Tajemství jasně: vždy používejte GitHub Secrets, nikdy pevný kód
- Potrubí bez upozornění: tiché a nebezpečné neúspěšné nasazení
- Pravidla bez vlastníků: použijte CODEOWNERS pro každou technickou oblast
- Pouze pozitivní testy: bez negativních svítidel nevíte, jestli generujete FP
Závěry
Detection-as-Code není jen změna nástrojů, je to změna mentality: detekce jsou kód, a jako takový musí být testován, verzován, revidován a nasazovat systematicky. Vyspělé potrubí DaC snižuje čas do detekce, zvyšuje kvalitu pravidel a umožňuje škálovat detekční program bez úměrného navýšení počtu zaměstnanců.
Další článek v seriálu
V dalším článku se podíváme, jak integrovat MITRE ATT&CK v pracovním postupu DaC k automatickému mapování mezer v pokrytí a stanovení priorit nové detekce založené na skutečném riziku.







