Veröffentlicht am: 14. August 2026

12 Minuten Lesezeit

Detection-Tests mit GitLab CI/CD und Duo automatisieren

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.

Eine Lücke in der Detection-Validierung

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.

So funktioniert WATCH

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:

  1. Planung: Wöchentlich ermittelt eine geplante GitLab-CI/CD-Pipeline alle aktiven Tests und verteilt sie auf zufällige Zeitfenster über die Woche. Diese Randomisierung ist wichtig. Tests sollen nicht zu vorhersehbaren Zeiten auslösen, weil sich Testaktivität dann zu leicht von echten Bedrohungen unterscheiden ließe und zeitkritische Probleme in unseren Detections verdeckt bleiben könnten.
  2. Vorabmeldung: Bevor ein Test läuft, meldet WATCH ihn über eine eigene Story "WATCH Heads Up" an unser SOAR und registriert dort die Detections, die er auslösen soll. So entstehen nachvollziehbare Datensätze, und das SOAR weiß, was kommt.
  3. Ausführung: Der Test führt das simulierte Angreiferverhalten aus. Er setzt zum Beispiel das Passwort eines Admin-Kontos zurück oder stellt verdächtige API-Aufrufe gegen die Staging-Umgebung.
  4. Detection: Das SIEM verarbeitet die Aktivitätslogs aus Staging und löst (hoffentlich) die entsprechenden Detection-Regeln aus.
  5. Korrelation: Sobald Alerts in unserem SOAR eintreffen, entscheidet eine Prüfung mit der Frage "Ist das ein WATCH-Test?", ob ein Alert zu einem registrierten Test gehört. Sie stützt sich auf drei Faktoren: das Zeitfenster zwischen Testlauf und Alert, die Identität des Akteurs (IP oder Benutzername) und die Regel-ID der ausgelösten Detection. Genau das verhindert, dass von WATCH erzeugte Alerts als echte Vorfälle an das SIRT eskaliert werden, während die vollständige Pipeline dennoch validiert wird.
  6. Verifizierung: Eine nachgelagerte Pipeline-Stage prüft, ob alle erwarteten Detections ausgelöst haben, aktualisiert die Statusmetadaten der Detections und veröffentlicht die neuen Ergebnisse auf unserem GitLab-Pages-Dashboard. Löst eine Detection nicht aus, geht eine Benachrichtigung in den Slack-Kanal unseres Teams.

WATCH mit GitLab CI/CD nutzen

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

    

Tests mit GitLab Duo schreiben

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
```


    

Mehr Sichtbarkeit durch Test-Dashboards

Test-Dashboards

WATCH veröffentlicht außerdem zwei interaktive Dashboards über GitLab Pages, die dem Team in Echtzeit Einblick in den Zustand der Detections geben.

  • Das Detection-Status-Dashboard gibt einen Überblick über alle Detection-Regeln und ihren aktuellen Teststatus, samt Kennzahlen wie der Zahl der bisherigen Auslösungen, dem aktuellen Pass-/Fail-Zustand und der Dauer, die eine Detection schon aktiv ist. Die Tabelle lässt sich filtern und sortieren, sodass sich schnell erkennen lässt, welche Detections Aufmerksamkeit brauchen.
  • Das Test-Runs-Dashboard bietet eine detaillierte Sicht auf einzelne Testläufe, gruppiert nach Test-ID und mit einer Aufschlüsselung der Detection-Abdeckung. Es enthält eine Zeitleiste der Alert-Propagation, die zeigt, wie lange es von der Testausführung bis zum Eintreffen des Alerts dauerte, sowie direkte Verweise auf die zugehörigen Alerts in unserem SIEM.

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:

  • GitLab-CI/CD-Pipelines und geplante Pipelines orchestrieren den gesamten Test-Lebenszyklus, von der wöchentlichen Planung über die Ausführung bis zur Veröffentlichung der Dashboards.
  • Pipeline-Inputs erlauben es, Stages unabhängig anzustoßen. So lässt sich nur der Verifizierungsschritt oder nur die Dashboard-Aktualisierung erneut ausführen, ohne alle Tests neu laufen zu lassen.
  • CI/CD-Variablen halten die API-Schlüssel für Tines und den Zugang zur GitLab-Staging-Umgebung sicher vor.
  • GitLab Pages hostet die WATCH-Dashboards ohne zusätzliche Infrastruktur. Es gibt also kein separates Hosting zu verwalten und keine zusätzlichen Deployment-Werkzeuge.
  • Da Tests einfach Python-Dateien in einem GitLab-Projekt sind, profitieren sie von Versionskontrolle, Merge-Request-Reviews und Code-Ownership, genauso wie unsere Detection-Regeln über DaC.

Mit WATCH vorausschauend bleiben

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.

WATCH ausprobieren

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.

Feedback erwünscht

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 teilen

Beginne noch heute, schneller zu entwickeln

Entdecke, was dein Team mit der intelligenten Orchestrierungsplattform für DevSecOps erreichen kann.