Veröffentlicht am: 14. August 2026
12 Minuten Lesezeit
Wie das Signals-Engineering-Team von GitLab das WATCH-Framework gebaut hat, um die eigene Security-Monitoring-Pipeline fortlaufend zu validieren.

Wer das Alerting eines Security Operations Center (SOC) gesund halten will, hat mit dem Tuning von False Positives nur die Hälfte der Arbeit erledigt. Oft übersehen: Kritische Detections, die selten auslösen, können längst kaputt sein, ohne dass es jemandem auffällt.
Bei GitLab prüft das Signals-Engineering-Team seine Detections, indem es echtes Angreiferverhalten auf eigener Infrastruktur simuliert. So lässt sich validieren, dass die Detections über die gesamte Kette auslösen: von der Log-Quelle über die Ingestion in das SIEM bis hin zum Alert-Routing durch unser System für Security Orchestration, Automation and Response (SOAR). Kommerzielle Werkzeuge für Breach and Attack Simulation (BAS) gehen denselben Weg, sind aber teuer, generisch und nicht auf unseren konkreten Detection-Stack zugeschnitten. Also haben wir ein eigenes, vollständig automatisiertes Framework gebaut: Weekly Attack Testing for Continuous Health, kurz WATCH.
Dieser Artikel zeigt, warum wir das Framework entwickelt haben, wie es funktioniert und wie es sich in der eigenen Umgebung einsetzen lässt.
Zwischen Änderungen am Log-Schema, SIEM-Updates und Fehlkonfigurationen in der Pipeline gibt es unzählige Wege, auf denen Detections stillschweigend versagen, und genau einen, auf dem sie wie vorgesehen auslösen. Bei diesem Verhältnis liegt ein Schluss nahe: Man muss alte Detections gezielt auslösen. Damit stellt sich die nächste Frage: Wie löst man Detections überhaupt aus, und wie oft?
Ein Weg ist der synthetische Ansatz: Logs, die schädliches Verhalten simulieren, wieder in das SIEM einspielen. Anschließend wartet man ab, ob die Detection-Regel den vorgetäuschten Vorfall erkennt und einen Alert auslöst. Dieser Ansatz belegt jedoch nicht, dass die Detection unter realen Bedingungen funktioniert, und er validiert eine der fehleranfälligsten Phasen im Alert-Lebenszyklus nicht: die Log-Ingestion, also den Weg von der Log-Quelle ins SIEM.
Über unser System GitLab Universal Automated Response and Detection (GUARD) haben wir bereits geschrieben: Es automatisiert das Erstellen und Ausrollen von Detections über eine Detections-as-Code-Pipeline (DaC), und Alerts werden über unser SOAR geroutet und triagiert. Unsere DaC-Pipelines lösen das Problem, zu validieren, dass eine Detection fehlerfrei ausgerollt werden kann. Sie beantworten aber nicht die Frage, ob diese Detection auch tatsächlich auslöst, wenn das Verhalten eintritt, auf das sie zielt.
WATCH schließt diese Lücke. Es ist die fortlaufende Validierung, die uns die Sicherheit gibt, dass unsere Detections funktionieren.
Im Grundsatz führt WATCH skriptgesteuerte Angriffssimulationen in unserer Staging-Umgebung aus. Anschließend prüft es, ob die erwarteten Alerts durch den gesamten Security-Monitoring-Stack laufen: durch das SIEM für die Detection-Regeln, durch das SOAR für das Alert-Routing und schließlich in die Dashboards, über die unser Team den Zustand der Detections beobachtet.
Der Lebenszyklus eines WATCH-Tests sieht so aus:
WATCH nutzt GitLab CI/CD als Rückgrat der Orchestrierung, über drei Pipeline-Stages hinweg.
Die Stage schedule_pipelines läuft wöchentlich und übernimmt die Verteilung der Tests. Sie ermittelt alle aktiven Tests, fasst sie in Gruppen zusammen und legt geplante Pipelines an, die zu zufälligen Zeitpunkten über die Woche laufen. Jede geplante Pipeline erhält eine Variable TESTS_TO_RUN, die festlegt, welche Tests sie ausführen soll.
In der Stage run_tests findet die eigentliche Angriffssimulation statt. Sie führt die dieser Pipeline zugewiesenen Tests aus, schreibt Ausführungsstatistiken in detection_status.json und hält die SOAR-Record-IDs fest, damit die Alert-Korrelation nachgelagert erfolgen kann.
Die Stage pages übernimmt Verifizierung und Reporting. Sie fragt unser SOAR ab, um zu bestätigen, dass Alerts erzeugt und korrekt geroutet wurden, aktualisiert die Detection-Metadaten mit den Prüfergebnissen und veröffentlicht das GitLab-Pages-Dashboard mit den aktuellen Testergebnissen.
Unten steht eine Vorlage für die GitLab-CI/CD-Konfigurationsdatei .gitlab-ci.yml der WATCH-Pipeline:
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
Beim Entwurf von WATCH kam es unter anderem darauf an, dass im Signals-Engineering- und im SIRT-Team alle mühelos neue Tests ergänzen können. Das Framework stellt dafür eine abstrakte Klasse BaseSecurityTest bereit, die den gesamten Boilerplate-Code übernimmt: das Erzeugen der Test-ID, die Verwaltung der Akteursidentität und die Abstimmung mit dem SOAR. Wer Tests schreibt, muss sich damit nur um drei Dinge kümmern: die Testumgebung aufsetzen, das simulierte Angreiferverhalten ausführen und anschließend aufräumen.
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"""
Zentral für die Konfiguration ist das Dictionary expected_detections. Es ordnet die SIEM-Regelnamen der erwarteten Detections der Akteursidentität und dem erwarteten Eintreffzeitpunkt des Alerts zu. Ein neuer Test ist einfach eine Python-Datei im Verzeichnis tests/, die von BaseSecurityTest erbt, ihr simuliertes Verhalten definiert und angibt, welche Detections sie auslösen soll. Der Test-Runner findet sie beim nächsten geplanten Lauf automatisch.
Diese reibungsarme Schnittstelle ist wichtig, denn Detection-Tests funktionieren als Praxis nur, wenn das Team tatsächlich Tests schreibt. Wenn das Ergänzen eines Tests verlangt, die Interna der gesamten Pipeline zu durchdringen, wird es niemand tun. Der einfache Vertrag, setup, execute und cleanup zu implementieren und die erwarteten Detections zu deklarieren, macht WATCH-Tests außerdem zu einem guten Kandidaten für GitLab Duo, GitLabs KI-Assistenz. Duo bekommt die Basisklasse und einen Prompt wie "Erstelle mir einen Test, der viele Projekte aus einer Zielgruppe klont" oder "Erstelle mir einen Test, der über GraphQL auf alle CI-Variablen dieses Projekts zugreift" oder auch "Benenne all diese Projekte nach demselben Schema um". Duo kann daraus einen funktionierenden WATCH-Test bauen, der sich direkt in das Framework einfügt. Das senkt die Hürde weiter: Vom Wunsch, eine bestimmte Detection zu testen, bis zum laufenden Test übernimmt Duo den Großteil der Umsetzung.
Tipp: Um GitLab Duo noch wirksamer einzusetzen, habe ich Duo Agent Skills genutzt. Sie eignen sich gut dafür, Standards und Abläufe für wiederkehrende Arbeit wie das Schreiben von Tests festzulegen. In unserem Projektverzeichnis liegt ein Ordner skills/WATCH-test-creator mit einer SKILL.md, die beschreibt, was einen guten Test auszeichnet, welche Hilfsfunktionen ein Test nutzen kann und wozu das Projekt dient. Diese Datei wird unmittelbar nach einem Prompt wie den oben genannten gelesen. Damit entfällt es, Duo ständig daran zu erinnern, worum es gerade geht und wie vorzugehen ist. Vor allem werden die Ergebnisse dadurch konsistenter und besser. Hier ein Auszug aus dieser Datei:
---
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 veröffentlicht außerdem zwei interaktive Dashboards über GitLab Pages, die dem Team in Echtzeit Einblick in den Zustand der Detections geben.
Diese Dashboards ersetzen einen vormals manuellen Vorgang, bei dem sich das Team durch Pipeline-Logs und SIEM-Abfragen graben musste, um den Zustand der Detections zu beurteilen.
Wie der Rest von GUARD stützt sich WATCH stark auf GitLab als Plattform:
Mit WATCH hat sich das Verhältnis unseres Teams zur Detection-Qualität verschoben: von reaktiv zu vorausschauend. Vor WATCH zeigte sich eine defekte Detection erst dann, wenn ein Vorfall eintrat und der erwartete Alert ausblieb, also zum schlechtesten denkbaren Zeitpunkt. Inzwischen erhalten wir regelmäßig Rückmeldung zum Zustand unserer Detections und wissen von einem Defekt, bevor tatsächlich etwas passiert. Das gibt uns die Gewissheit, dass neue Detections nicht unbemerkt kaputtgehen und in Vergessenheit geraten.
WATCH hält außerdem die Taktiken, Techniken und Prozeduren (TTPs) fest, die unser Red Team bei Flash-Operationen eingesetzt hat. Sind Detections implementiert und ist die rückblickende Analyse einer Pentest-Operation abgeschlossen, lassen sich die verwendeten TTPs mit WATCH erneut abspielen, um diese Detections zu validieren. Im Kern macht WATCH aus atomaren Detection-Tests wiederholbare TTPs.
Wer ein SOC betreibt und sich beim Erkennen von Bedrohungen auf SIEM-Detections verlässt, steht nicht vor der Frage, ob Detections irgendwann versagen, sondern davor, ob der Ausfall auffällt. Für den Anfang braucht es dafür keine kommerzielle BAS-Plattform. Eine Sandbox-Umgebung, eine CI/CD-Pipeline und ein Framework für skriptgesteuerte Angriffssimulationen bringen einen weit.
Ein eigenes Framework für Detection-Tests lässt sich mit einer kostenlosen Testversion von GitLab Ultimate aufbauen.
Kostenlose 30-tägige
GitLab-Testversion starten
Keine Kreditkarte erforderlich.
Hat dir dieser Blogbeitrag gefallen? Hast du Fragen oder Feedback? Erstelle ein neues Diskussionsthema im GitLab-Community-Forum und lass andere an deinen Eindrücken teilhaben.
Feedback teilenBeginne noch heute, schneller zu entwickeln
Entdecke, was dein Team mit der intelligenten Orchestrierungsplattform für DevSecOps erreichen kann.