Git と CI/CD を使用したコードとしての検出パイプライン
2025年には、 セキュリティ チームの 35% 用途 63% の専門家が組織的にコードとしての検出を実施 あなたはそれを定期的に採用したいと宣言します。意図と実践の間のこのギャップ 本当の問題が明らかになります: ほとんどの組織は気づいていません として サイクル全体を処理する堅牢な DaC パイプラインを構築する 検出ルールの存続期間 (作成から運用環境への展開まで)。
コードとしての検出 (DaC) エンジニアリングの実践を適用する ソフトウェアのセキュリティ領域への移行: バージョン管理、コードレビュー、テスト 自動化、CI/CD パイプライン、および制御されたデプロイメント。結果は1つです 新しい攻撃手法が発見されるまでの時間を大幅に短縮 品質ルールを備えた効果的な検出の展開 時間の経過とともに測定可能であり、改善可能です。
この記事で学べること
- DaC リポジトリの構造: ディレクトリ レイアウト、命名規則、スキーマ
- Git ワークフロー: 分岐戦略、コミット規約、プル リクエストのレビュー
- GitHub Actions パイプライン: lint、検証、テスト、変換、デプロイ
- 合成ログによる自動テスト: 陽性と陰性
- API を介した Splunk、Elastic、Sentinel への安全な導入
- 自動ロールバックと展開後のインシデント管理
- 品質指標: カバレッジ、誤検知率、導入頻度
- 検出追跡のための JIRA/GitHub の問題との統合
コードとしての検出が基本であるため
DaC が登場する前は、SIEM でルールを更新する一般的なプロセスは次のとおりでした。 Web コンソールにアクセスし、ルールを見つけて手動で編集し、保存します。 何も壊れないことを祈ります。以前のバージョンもテストもありません ピアレビュー、変更の追跡不可能。ルールが始まったとき 午前3時に何千もの誤検知を生成することを理解することは不可能でした 誰がいつ何をしたのか。
DaC は、この混沌としたプロセスを体系的なものに変換します。
- 各ルールは Git でバージョン管理されたテキスト ファイルです
- 各変更はプルリクエストを経て、必須のレビューが行われます
- マージ前に、パイプラインは合成ログに対して自動テストを実行します。
- すべてのテストに合格した場合にのみ展開が行われます
- 問題が発生した場合、ロールバックは簡単です
git revert
DaC リポジトリの構造
適切に構造化された DaC リポジトリは、他のすべてが構築される基盤です。 企業組織に推奨されるレイアウトは次のとおりです。
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
命名規則とルールスキーム
リポジトリのナビゲーションを容易にするための一貫した不可欠な命名規則 何百ものルールがあります。推奨されるファイル名規則は次のとおりです。
# 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
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"
GitHub アクションを使用した完全な CI/CD パイプライン
# .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']
})
検出ルールのテスト フレームワーク
テストは DaC の中心です。完全な 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)
)
テスト フィクスチャ: 合成ログの例
# 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"
}
DaC 品質指標
| メトリック | 意味 | ターゲット |
|---|---|---|
| 検出範囲 | % 安定ルールの対象となる ATT&CK テクニック | > 60% |
| テスト範囲 | 陽性および陰性のテストを含む % ルール | 100% |
| 誤検知率 | FP アラート / 週あたりの合計アラート数 | < 10% |
| 導入頻度 | 週ごとに新しいルールとマージします | > 週3回 |
| 検出までの時間 (TTD) | 公開されたテクニックからルールがデプロイされるまでの時間 | 48 時間未満 (重大度が高い) |
避けるべきアンチパターン
- テストせずにデプロイ: 緊急時であっても、少なくとも 1 回の手動煙テスト
- 秘密は明らかです: 常に GitHub Secret を使用し、決してハードコーディングしないでください
- 通知のないパイプライン: 静かで危険な展開の失敗
- 所有者のいないルール: 各技術分野に CODEOWNERS を使用する
- 陽性反応のみ: ネガティブなフィクスチャがなければ、FP を生成するかどうかはわかりません
結論
コードとしての検出は、単なるツールの変更ではなく、考え方の変更です。 検出はコードであるため、テスト、バージョン管理、改訂が必要です そして体系的に展開します。成熟した DaC パイプラインにより検出までの時間が短縮され、 ルールの品質が向上し、検出プログラムを拡張できるようになります。 比例して人員を増やさずに。
シリーズの次の記事
次の記事では、統合する方法について説明します。 マイターアタック&CK DaC ワークフローでカバレッジ ギャップを自動的にマッピングし、優先順位を付ける 実際のリスクに基づいた新しい検出。







