Potok wykrywania jako kodu z Git i CI/CD
W 2025 roku tylko 35% zespołów bezpieczeństwa wykorzystuje Systematycznie wykrywanie jako kod, pomimo 63% profesjonalistów deklarujesz, że chcesz go regularnie adoptować. Ta rozbieżność między intencją a praktyką ujawnia prawdziwy problem: większość organizacji nie wie Jak zbuduj solidny rurociąg DaC, który obsłuży cały cykl żywotność reguły wykrywania, od stworzenia do wdrożenia w środowisku produkcyjnym.
Wykrywanie jako kod (DaC) stosuje praktyki inżynierskie oprogramowania do domeny bezpieczeństwa: kontrola wersji, przegląd kodu, testowanie zautomatyzowany potok CI/CD i kontrolowane wdrażanie. Wynik jest jeden drastyczne skrócenie czasu pomiędzy odkryciem nowej techniki ataku oraz wdrożenie skutecznego wykrywania przy zachowaniu zasad jakości mierzalne i możliwe do udoskonalenia w czasie.
Czego dowiesz się w tym artykule
- Struktura repozytorium DaC: układ katalogów, konwencja nazewnictwa, schemat
- Przepływ pracy w Git: strategia rozgałęziania, konwencja zatwierdzania, przegląd żądania ściągnięcia
- Potok akcji GitHub: lint, sprawdzanie poprawności, testowanie, konwertowanie, wdrażanie
- Zautomatyzowane testowanie z logami syntetycznymi: pozytywne i negatywne
- Bezpieczne wdrożenie w Splunk, Elastic i Sentinel poprzez API
- Automatyczne przywracanie zmian i zarządzanie incydentami po wdrożeniu
- Metryki jakości: zasięg, odsetek wyników fałszywie dodatnich, częstotliwość wdrażania
- Integracja z JIRA/GitHub Problemy ze śledzeniem wykrycia
ponieważ wykrywanie jako kod jest podstawą
Przed DaC typowy proces aktualizacji reguły w SIEM wyglądał następująco: uzyskaj dostęp do konsoli internetowej, znajdź regułę, edytuj ją ręcznie, zapisz, mam nadzieję, że niczego to nie zepsuje. Żadnych poprzednich wersji, żadnych testów, nie peer review, brak możliwości śledzenia zmian. Kiedy zaczęła się reguła generowanie tysięcy fałszywych alarmów o 3 w nocy było niemożliwe do zrozumienia kto co i kiedy zrobił.
DaC przekształca ten chaotyczny proces w coś systematycznego:
- Każda reguła jest plikiem tekstowym w wersji Git
- Każda zmiana przechodzi przez żądanie ściągnięcia z obowiązkowym sprawdzeniem
- Przed połączeniem rurociąg przeprowadza automatyczne testy na kłodach syntetycznych
- Wdrożenie następuje tylko wtedy, gdy wszystkie testy zakończą się pomyślnie
- W razie problemów przywrócenie ustawień jest prostą sprawą
git revert
Struktura Repozytorium DaC
Dobrze zorganizowane repozytorium DaC jest podstawą, na której zbudowane jest wszystko inne. Oto zalecany układ dla organizacji korporacyjnej:
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
Konwencja nazewnictwa i schemat reguł
Spójna i istotna konwencja nazewnictwa zapewniająca nawigację po repozytorium z setkami zasad. Zalecana konwencja nazw plików:
# 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
Strategia rozgałęzienia dla 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"
Kompletny potok CI/CD za pomocą akcji 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']
})
Ramy testowania reguł wykrywania
Testowanie jest sercem DaC. Oto kompletny framework testowy Pythona:
#!/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)
)
Urządzenia testowe: przykłady kłód syntetycznych
# 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"
}
Wskaźniki jakości DaC
| Metryczny | Definicja | Cel |
|---|---|---|
| Zasięg wykrywania | % Technik ATT&CK objętych regułą stabilną | > 60% |
| Zasięg testowy | % reguł z testami pozytywnymi i negatywnymi | 100% |
| Fałszywie dodatni współczynnik | Alerty FP / Łączna liczba alertów tygodniowo | < 10% |
| Częstotliwość wdrażania | Połącz z nowymi regułami co tydzień | > 3/tydz |
| Czas do wykrycia (TTD) | Czas od opublikowanej techniki do wdrożonej reguły | < 48h (duża dotkliwość) |
Anty-wzorce, których należy unikać
- Wdróż bez testowania: nawet w sytuacji awaryjnej przynajmniej jeden ręczny test dymu
- Sekrety jasne: zawsze używaj sekretów GitHub, nigdy twardego kodu
- Rurociąg bez powiadomień: ciche i niebezpieczne, nieudane wdrożenie
- Zasady bez właścicieli: użyj CODEOWNERS dla każdego obszaru technicznego
- Tylko pozytywne testy: bez negatywnych urządzeń nie wiesz, czy wygenerujesz FP
Wnioski
Detection-as-Code to nie tylko zmiana narzędzi, to zmiana mentalności: wykrycia są kodem i jako takie muszą zostać przetestowane, wersjonowane i poprawione i wdrażaj systematycznie. Dojrzały rurociąg DaC skraca czas do wykrycia, zwiększa jakość reguł i umożliwia skalowanie programu detekcyjnego bez proporcjonalnego zwiększania personelu.
Następny artykuł z serii
W następnym artykule zobaczymy jak integrować AT&CK UKOŚNIENIA w przepływie pracy DaC, aby automatycznie mapować luki w pokryciu i ustalać priorytety nowe wykrycia w oparciu o rzeczywiste ryzyko.







