セキュリティ運用センター(SOC)において健全なアラートシステムを維持する上で、誤検出のチューニングは課題の半分にすぎません。見落とされがちなもう一方の課題は、めったに発火しない重要な検知ルールが、誰にも気づかれないまま完全に機能しなくなっていないかを確認することです。
GitLabでは、Signals Engineeringチームが自社インフラ上で実際の悪意ある行動をシミュレートすることで検知テストを実施しています。ログソースからインジェスション、SIEMへの取り込み、そしてSOARによるアラートルーティングまで、検知がエンドツーエンドで機能することを検証するためです。これは商用のBreach and Attack Simulation(BAS)ツールと同じアプローチですが、それらのツールは高価で汎用的であり、私たちの検知スタックに特化したものではありません。そこで私たちは、Weekly Attack Testing for Continuous Health、略してWATCHと名付けた完全自動化フレームワークを自社で構築しました。
本記事では、このフレームワークを開発した背景、その仕組み、そして自社環境での活用方法についてご説明します。
検知の検証におけるギャップ
ログスキーマの変更、SIEMのアップデート、パイプラインの設定ミスなど、検知がサイレントに失敗する原因は無数に存在します。一方、期待通りに発火するパターンはただひとつです。こうした現実を踏まえると、結論は明らかです。「既存の検知を意図的にトリガーしてみよう!」ただし、すぐに次の疑問が浮かびます。「どうやって検知をトリガーするのか?」「どのくらいの頻度で行うべきか?」
検知をトリガーする方法のひとつは、悪意ある行動をシミュレートするログをSIEMに再投入するという「合成アプローチ」です。そして、検知ルールが偽の問題を検知してアラートを発するかどうかを確認します。ただしこのアプローチには、「実世界」のシナリオで検知が機能することを証明できないという問題に加え、アラートライフサイクルの中でも特にエラーが発生しやすいログインジェスション(ログソースからSIEMまでの経路)の検証ができないという欠点があります。
以前、私たちはGitLab Universal Automated Response and Detection(GUARD)システムがDetections as Code(DaC)パイプラインを通じて検知の作成とデプロイを自動化する方法、そしてSOARを通じてアラートのルーティングとトリアージを行う方法についてご紹介しました。DaCパイプラインはエラーなしに検知をデプロイできることの検証問題を解決しましたが、対象とする行動が実際に発生した際にその検知が発火するかという問いには答えられていません。
WATCHはそのギャップを埋めます。検知が機能しているという確信を与えてくれる継続的な検証レイヤーです。
WATCHの仕組み
大まかに言えば、WATCHはステージング環境でスクリプト化された攻撃シミュレーションを実行し、期待されるアラートがセキュリティ監視スタック全体に伝播することを検証します。検知ルールを管理するSIEM、アラートルーティングを担うSOAR、そしてチームが検知の健全性を監視するダッシュボードに至るまでを確認します。
WATCHテストのライフサイクルは次のとおりです。
- スケジューリング: 毎週、スケジュールされたGitLab CI/CDパイプラインがすべてのアクティブなテストを検出し、週全体にわたってランダムな時間スロットに振り分けます。ランダム化は重要です。テストが予測可能なタイミングで発火すると、テスト活動と実際の脅威を区別しやすくなり、タイミングに敏感な検知の問題をマスクしてしまう可能性があるためです。
- 事前通知: テスト実行前に、WATCHは専用の「WATCH Heads Up」ストーリーを通じてSOARに通知し、トリガーが期待される検知を登録します。これにより、SOARが次に何が来るかを把握できるよう、追跡可能なレコードが作成されます。
- 実行: テストがシミュレートされた悪意ある行動を実行します。たとえば、管理者アカウントのパスワードをリセットしたり、ステージング環境に対して不審なAPI呼び出しを行ったりします。
- 検知: SIEMがステージング環境からのアクティビティログを処理し、対応する検知ルールを(うまくいけば)発火させます。
- 相関分析: SOARにアラートが届くと、「これはWATCHテストか?」のチェックが行われ、各アラートが登録済みのテストに対応するかどうかを3つの要素で照合します。テスト実行からアラート到着までの時間ウィンドウ、アクターのID(IPアドレスまたはユーザー名)、そして発火した検知のルールIDです。これにより、WATCHが生成したアラートがSIRTへの実際のインシデントとしてエスカレーションされることなく、パイプライン全体の検証が可能になります。
- 検証: フォローアップのパイプラインステージが、期待されるすべての検知が発火したかどうかを確認し、検知のステータスメタデータを更新し、更新された結果をGitLab Pagesダッシュボードにデプロイします。いずれかの検知が発火しなかった場合、チームのSlackチャンネルに通知が送られます。
GitLab CI/CDでWATCHを使う
WATCHは3つのパイプラインステージにわたるオーケストレーションのバックボーンとしてGitLab CI/CDを活用しています。
schedule_pipelinesステージは毎週実行され、テストの配布を担います。すべてのアクティブなテストを検出し、グループに振り分け、週全体にわたってランダムな時間帯に実行するようスケジュールされたパイプラインを作成します。スケジュールされた各パイプラインには、実行すべきテストを指定するTESTS_TO_RUN変数が付与されます。
run_testsステージでは実際の攻撃シミュレーションが行われます。そのパイプライン実行に割り当てられたテストを実行し、実行統計をdetection_status.jsonに保存し、アラートの相関分析がダウンストリームで行えるようSOARのレコードIDを記録します。
pagesステージは検証とレポートを担います。SOARに問い合わせてアラートが生成され適切にルーティングされたことを確認し、検証結果で検知メタデータを更新し、最新のテスト結果をGitLab Pagesダッシュボードにデプロイします。
以下は、WATCHパイプライン用のGitLab CI/CD gitlab-ci.yml設定ファイルのテンプレートです。
spec:
inputs:
weekly_scheduling:
type: boolean
default: false
description: "Enable weekly scheduling of detection tests."
update_pages:
type: boolean
default: false
description: "For triggering the update of GitLab Pages dashboard."
---
# Specify the Docker image to use for the job
image: python:3.12
stages:
- schedule_pipelines
- run_tests
- pages
# Job to manage scheduled pipelines (runs when weekly_scheduling input is true)
manage_scheduled_pipelines:
stage: schedule_pipelines
script:
- pip install -r requirements.txt
- python scripts/manage_scheduled_pipelines.py
rules:
- if: $TESTS_TO_RUN == null && $CI_PIPELINE_SOURCE == "schedule" && [[ inputs.weekly_scheduling ]] == true
when: on_success
- when: never
# Job to run detection tests, save tines_record_id to detection_status.json, and commit
run_detection_tests:
stage: run_tests
script:
- pip install -r requirements.txt
- python main.py --prod --save-stats --scheduled-tests
rules:
- if: $TESTS_TO_RUN
when: on_success
- when: never
# Job to verify alerts, update detection_status.json, commit, and deploy pages
pages:
stage: pages
script:
- pip install -r requirements.txt
- python scripts/verify_and_update_detections.py --tines-api-key ${TINES_API_KEY}
- mkdir -p public/data
- cp detection_status.json public/data/
- cp -r static/* public/
pages: true # Required for GitLab 17.9+ to trigger Pages deployment
artifacts:
paths:
- public
rules:
- if: $TESTS_TO_RUN == null && [[ inputs.update_pages ]] == true
when: on_success
- when: never
GitLab Duoを使ったテストの作成
WATCHの設計における優先事項のひとつは、Signals EngineeringまたはSIRTチームの誰でも新しいテストを追加できるようにすることでした。このフレームワークはBaseSecurityTest抽象クラスを提供しており、テストIDの生成、アクターIDの管理、SOARとの連携といった定型作業をすべて処理します。これにより、テスト作成者はテスト環境のセットアップ、シミュレートされた悪意ある行動の実行、後処理の3点にのみ集中できます。
class BaseSecurityTest(ABC):
def __init__(self, config = {}, test_id: Optional[str] = None):
self.test_id = test_id or str(uuid.uuid4())
self.test_name = self.__class__.__name__
self.expected_detections = {}
self.actor_id = config.get('gitlab', {}).get(
'default_actor_id',
"sirt_detection_test_user_" + self.test_id[:8]
)
self.isActive = True
self.test_run_time = 300
self.config = config
@abstractmethod
def setup(self) -> bool:
"""Prepare test environment and resources"""
@abstractmethod
def execute(self) -> Dict[str, Any]:
"""Execute the malicious behavior simulation"""
@abstractmethod
def cleanup(self) -> bool:
"""Clean up test environment and resources"""
重要な設定はexpected_detectionsディクショナリです。これは、トリガーが期待されるSIEMのルール名を、アクターIDと期待されるアラート到着時刻にマッピングします。新しいテストは、tests/ディレクトリ内にBaseSecurityTestをサブクラス化したPythonファイルを作成し、シミュレートする行動を定義して、トリガーが期待される検知を宣言するだけです。テストランナーは次回のスケジュール実行時に自動的にそれを検出します。
この低摩擦なインターフェースが重要なのは、チームが実際にテストを書いてくれなければ、検知テストという取り組み自体が成立しないからです。テストの追加にパイプライン内部の全体像の理解が必要になれば、誰もやらなくなります。setup、execute、cleanupを実装し、期待される検知を宣言するというシンプルな仕様は、WATCHテストをGitLab Duo(GitLabのAIアシスタント)との相性を高めます。Duoにベースクラスと「特定のグループから大量のプロジェクトをクローンするテストを作って」「GraphQLを使ってこのプロジェクトのCI変数をすべて取得するテストを作って」「これらのプロジェクトを同じ命名規則にリネームして」といったプロンプトを与えると、Duoはフレームワークに直接プラグインできる動作するWATCHテストをスキャフォールドします。これにより、障壁がさらに下がります。エンジニアは「この検知をテストしたい」という考えから、Duoが実装作業の大半を担った状態で動作するテストまで、一気に進むことができます。
Pro Tip: GitLab Duoをさらに効果的に活用するために、私はDuo Agent Skillsを活用しました。これは、テストの作成のような定型作業の標準と手順を定義するのに最適です。プロジェクトディレクトリにはskills/WATCH-test-creatorというフォルダがあり、SKILL.mdに優れたテストの条件、使用できるヘルパー関数、プロジェクトの目的が記述されています。上記のようなプロンプトを入力した直後にこのファイルが読み込まれるため、Duoに対して「あなたが何をしているのか、どうやればいいのか」を毎回説明し直す必要がなくなります。何より重要なのは、結果が一貫して高品質になることです。以下はそのファイルの抜粋です。
---
name: WATCH-test-creator
description: Create WATCH (Orchestrated Offensive Penetration Simulator) security detection tests that simulate malicious behavior on GitLab infrastructure to validate SIEM detection rules and alerting pipelines.
---
## WATCH Test Creator
You are an expert at writing security detection tests for the WATCH framework. WATCH tests simulate malicious activities on GitLab-owned infrastructure to verify that the SecOps security monitoring stack (Elastic SIEM, Tines SOAR, alerting rules) properly detects and responds to threats.
### Architecture Overview
```
Project Root
├── core/
│ ├── base_test.py # Abstract base class all tests inherit from
│ ├── test_runner.py # Auto-discovers and executes tests
│ └── webhook_manager.py # Tines/SOAR notification integration
├── tests/
│ ├── gitlab/ # GitLab-specific detection tests
│ └── gcp/ # GCP-specific detection tests
├── utils/
│ ├── gitlab_helper.py # GitLab API wrapper (users, projects, tokens, webhooks, OAuth)
│ └── crypto_utils.py # Password generation utility
├── config/
│ ├── settings.py # Config loader (reads YAML + GITLAB_ADMIN_PAT env var)
│ └── environments/
│ ├── dev.yaml # Local GDK config
│ └── prod.yaml # Production staging.gitlab.com config
├── main.py # Entry point with CLI args
└── detection_status.json # Test results and detection metadata
```
テストダッシュボードによる可視性の向上

WATCHはまた、GitLab Pagesを通じて2つのインタラクティブなダッシュボードをデプロイし、チームが検知の健全性をリアルタイムで把握できるようにしています。
- 検知ステータスダッシュボードは、すべての検知ルールと現在のテスト状況の概要を提供します。各検知の発火回数、現在の合否状態、検知のアクティブ期間といったメトリクスが含まれます。テーブルはフィルタリングとソートが可能で、エンジニアはどの検知に注意が必要かをすぐに特定できます。
- テスト実行ダッシュボードは、テストIDごとにグループ化され、検知カバレッジの内訳を含む個々のテスト実行の詳細なビューを提供します。アラート伝播時間を示すタイムライン可視化も含まれており、テスト実行からアラート到着までの所要時間や、SIEMの対応するアラートへの直接リンクを確認できます。
これらのダッシュボードは、以前は手動でパイプラインログやSIEMクエリを掘り起こして検知の健全性を確認していた作業を置き換えました。
GUARDの他の部分と同様に、WATCHはGitLabをそのプラットフォームとして全面的に活用しています。
- GitLab CI/CDパイプラインとスケジュールパイプラインが、週次スケジューリングから実行、ダッシュボードのデプロイまで、テストライフサイクル全体をオーケストレーションします。
- パイプラインインプットにより、ステージを個別にトリガーできるため、すべてのテストを再実行することなく、検証ステップだけ、あるいはダッシュボード更新だけを実行することができます。
- CI/CD変数が、TinesおよびGitLabステージング環境へのアクセスに必要なAPIキーを安全に保管します。
- GitLab Pagesが、追加のインフラ不要でWATCHダッシュボードをホストします。別途ホスティングの管理も、追加のデプロイツールも必要ありません。
- テストはGitLabプロジェクト内のPythonファイルに過ぎないため、DaCを通じた検知ルールと同様に、バージョン管理、マージリクエストレビュー、コードオーナーシップの恩恵を受けます。
WATCHでプロアクティブな姿勢を維持する
WATCHを構築したことで、チームの検知品質に対する姿勢が事後対応型からプロアクティブ型へと変わりました。WATCH導入以前は、検知の不具合はインシデントが発生して期待されるアラートが届かないときに初めて露見していました。これは、ギャップを発見するには最悪のタイミングです。今では検知の健全性について定期的な更新を受け取ることができ、実際に何かが起きる前に不具合を把握できます。新たな検知を開発しても、それが壊れたまま放置されることはないという安心感が生まれました。
WATCHのもうひとつのメリットは、レッドチームがフラッシュオペレーションを実施した際に使用した戦術・技術・手順(TTP)を記録できることです。検知を実装してペンテスト作業の事後分析を行った後、WATCHを使ってそれらの検知を検証するためにTTPを再実行することができます。本質的に、WATCHは検知のアトミックテストを再実行可能なTTPにします。
WATCHを試す
SOCを運営しSIEMの検知を頼りに脅威を捕捉しているなら、問うべきは「検知が壊れるかどうか」ではなく、「壊れたときに気づけるかどうか」です。この問いに答えるために商用BASプラットフォームは必要ありません。サンドボックス環境、CI/CDパイプライン、そして攻撃シミュレーションをスクリプト化するためのフレームワークがあれば、大きく前進できます。
GitLab Ultimateの無料トライアルに登録して、独自の検知テストフレームワークを構築してみてください。



