CI/CD
.yml
GitLab · Runner Security · CI/CD · Magento
Runner-Security:
Shared, Private oder Self-Hosted?

Die Wahl des Runner-Typs ist keine Komfortfrage — sie ist eine Sicherheitsentscheidung. Shared Runner sind praktisch, aber ihr Sicherheitsmodell ist für Magento-Produktions-Deployments mit SSH-Keys und Datenbank-Credentials oft nicht ausreichend. Dieser Artikel zeigt, wo die Grenzen liegen und was Self-Hosted Runner leisten können.

18 Min. Lesezeit Isolation · Secrets · SSH-Keys · Privileged Mode · Network GitLab SaaS · Self-Managed · Magento 2.4

1. Die drei Runner-Typen und ihr Sicherheitsmodell

GitLab unterscheidet bei Runnern nach Sichtbarkeit und Registrierungsebene: Shared Runner sind für alle Projekte einer GitLab-Instanz verfügbar, werden von GitLab Inc. betrieben (bei SaaS) oder vom Admin einer Self-Managed-Instanz bereitgestellt. Group Runner (oft als Private Runner bezeichnet) sind auf eine GitLab-Gruppe und ihre Unterprojekte beschränkt. Project Runner sind exklusiv einem einzelnen Projekt zugeordnet. Self-Hosted Runner beschreibt in der Praxis alle Runner, bei denen das eigene Team die Hardware, das Betriebssystem und die gitlab-runner-Software selbst betreibt — unabhängig davon, ob sie auf Shared-, Group- oder Project-Ebene registriert sind.

Das Sicherheitsmodell unterscheidet sich fundamental: Bei Shared Runnern auf GitLab.com führt derselbe Runner-Prozess Jobs von tausenden verschiedener Projekte und Organisationen aus. Beim Self-Hosted Runner führt der Runner ausschließlich Jobs der eigenen Organisation aus. Das bedeutet nicht, dass Shared Runner unsicher sind — GitLab.com setzt Docker-Isolation mit kurzlebigen Containern ein — aber es bedeutet, dass der eigene Einfluss auf Isolation, Netzwerkzugriff und Secrets-Schutz auf einem fremden Runner minimal ist.

Für Magento-Deployments, bei denen SSH-Private-Keys, Datenbank-Passwörter, Composer-Auth-Tokens und Production-Server-Adressen in GitLab-CI-Variablen gespeichert sind, ist die Frage der Runner-Sicherheit keine akademische Diskussion. Ein kompromittierter Runner mit Zugriff auf diese Variablen kann SSH-Verbindungen zu Produktionsservern aufbauen. Deshalb lohnt es sich, die Sicherheitsgarantien der verschiedenen Runner-Typen konkret zu verstehen.

2. Shared Runner: Risiken und Einsatzgrenzen

Shared Runner auf GitLab.com verwenden den Docker-Executor mit kurzlebigen Containern. Jeder Job startet einen frischen Container und löscht ihn nach Abschluss. Das verhindert, dass ein Job Daten aus einem vorherigen Job desselben Containers liest. Die Isolation zwischen Jobs verschiedener Projekte erfolgt auf Container-Ebene — vorausgesetzt, der Container ist nicht im Privileged Mode gestartet, was auf GitLab.com standardmäßig nicht der Fall ist.

Das verbleibende Risiko bei Shared Runnern liegt weniger in der technischen Isolation als im Vertrauen in den Betreiber und in der Sichtbarkeit von Variablen. Auf GitLab.com sind Protected Variablen nur in Jobs sichtbar, die auf Protected Branches oder Protected Tags laufen. Ungeschützte Variablen sind in jedem Job sichtbar, auch in Merge-Request-Pipelines von Fork-Repositories. Wenn ein Repository öffentlich ist und Pipelines aus Forks erlaubt, können externe Nutzer Jobs auslösen, die Zugriff auf ungeschützte CI/CD-Variablen erhalten. Für private Repositories ohne Forks ist dieses Risiko geringer, aber es bleibt die Grundfrage: Warum sollte ein Magento-Team, das Production-SSH-Keys in GitLab speichert, diese Keys auf einer fremden Infrastruktur exponieren?

3. Private Runner: Isolation auf Projekt- und Gruppenebene

Ein Group Runner, der für eine Gruppe mit mehreren Magento-Projekten registriert ist, bringt eine wesentliche Verbesserung gegenüber Shared Runnern: Er führt nur Jobs dieser Gruppe aus. Andere GitLab-Gruppen oder öffentliche Projekte haben keinen Zugriff auf diesen Runner. Das reduziert die Angriffsfläche erheblich: Nur Mitglieder der eigenen Gruppe können Pipelines auslösen, die auf diesem Runner laufen.

Ein Project Runner — exklusiv einem einzigen Projekt zugeordnet — bietet die stärkste Isolation auf GitLab-Ebene. Jobs anderer Projekte können diesen Runner nicht nutzen, selbst wenn sie zur gleichen Gruppe gehören. Dieses Modell ist sinnvoll für Production-Deploy-Runner, auf denen SSH-Keys für Produktionsserver gespeichert sind: Nur Pipelines des einen Projekts können diesen Runner und damit die gespeicherten Secrets nutzen.

4. Self-Hosted Runner: vollständige Kontrolle und ihre Kosten

Ein Self-Hosted Runner — eine eigene Maschine, auf der das eigene Team gitlab-runner betreibt — gibt vollständige Kontrolle über Betriebssystem, Netzwerkzugriff, installierte Tools, gespeicherte Credentials und Ausführungsumgebung. Kein Shared-Runner-Betreiber, kein Cloud-Provider und keine andere Organisation hat Zugriff auf diese Maschine. Das ist das stärkste Isolation-Modell — aber es bringt entsprechenden Betriebsaufwand: Patch-Management, Monitoring, Backups, Incident Response bei Ausfällen und Zugang für das Team.

Für Magento-Deployments mit sensiblen Production-SSH-Keys und Datenbank-Credentials ist der Self-Hosted Runner in den meisten Fällen die richtige Wahl. Der Deploy-Runner, der SSH-Verbindungen zu Produktionsservern aufbaut, sollte immer selbst betrieben werden. Der Build-Runner für composer install und setup:di:compile ohne Production-Secrets kann dagegen auf GitLab.com Shared Runnern laufen — hier reichen die Sicherheitsgarantien von GitLab.com in der Regel aus, solange keine Production-Credentials im Build-Job exponiert werden.

5. Secrets-Schutz: wie Variablen in jedem Runner-Typ behandelt werden

GitLab CI/CD-Variablen werden vom GitLab-Server verschlüsselt gespeichert und zur Laufzeit des Jobs als Umgebungsvariablen in die Runner-Umgebung injiziert. Der Unterschied zwischen Runner-Typen liegt darin, wer die Umgebung kontrolliert, in der diese Variablen existieren. Auf einem Self-Hosted Runner kontrolliert das eigene Team, welche Prozesse auf der Maschine laufen, welche Ports offen sind und wer SSH-Zugang hat. Auf einem Shared Runner des Cloud-Providers existiert diese Kontrolle nicht.

Masked Variablen in GitLab verhindern, dass der Wert in Job-Logs angezeigt wird. Protected Variablen sind nur in Jobs auf Protected Branches und Tags sichtbar. Diese Schutzmechanismen gelten unabhängig vom Runner-Typ — aber sie schützen nicht vor einem kompromittierten Runner-Host selbst. Ein Angreifer mit Zugriff auf den Runner-Prozess kann Umgebungsvariablen aus dem Prozess-Kontext auslesen, bevor sie maskiert werden. Deshalb gilt: Je sensibler die Variable (Production-SSH-Key, Datenbank-Root-Passwort), desto stärker muss die Isolation des Runners sein.

6. Sichere Self-Hosted Runner-Konfiguration für Magento

Eine sichere Grundkonfiguration für einen Self-Hosted Runner umfasst mehrere Ebenen: den Runner-Prozess selbst mit minimalen Rechten, den Docker-Executor mit Ressourcenlimits und ohne Privileged Mode, und die Netzwerkkonfiguration, die ausgehende Verbindungen auf bekannte Ziele beschränkt. Die folgende Konfiguration zeigt einen produktionsnahen Setup für einen Magento-Build-Runner ohne Production-Secrets.

# /etc/gitlab-runner/config.toml — Secure self-hosted build runner
concurrent = 4
check_interval = 5
shutdown_timeout = 60
log_level = "warning"
log_format = "json"

[[runners]]
  name = "magento-build-secure"
  url = "https://gitlab.example.com"
  token = "${GITLAB_RUNNER_TOKEN}"
  executor = "docker"
  limit = 2
  maximum_timeout = 1500
  # Only accept tagged jobs — no untagged job pickup
  run_untagged = false
  tag_list = ["build", "php84", "docker"]

  [runners.docker]
    tls_verify = false
    image = "php:8.4-cli"
    # Never run in privileged mode for build jobs
    privileged = false
    # Disable Docker socket access — no container escape via /var/run/docker.sock
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    # Allowed volumes: only cache, no host system paths
    volumes = ["/runner-cache:/cache:rw"]
    # No extra capabilities
    cap_add = []
    cap_drop = ["ALL"]
    security_opt = ["no-new-privileges:true"]
    memory = "3g"
    memory_swap = "3g"
    cpus = "2.0"
    # Isolated network — no host network access
    network_mode = "runner-net"
    # Pull always to prevent stale image attacks
    pull_policy = ["always"]

  [runners.cache]
    Type = "local"
    Shared = false

Drei Sicherheitsaspekte sind in dieser Konfiguration besonders wichtig: privileged = false verhindert, dass Jobs Container-Escape-Techniken nutzen können. cap_drop = ["ALL"] mit leerem cap_add entfernt alle Linux-Capabilities vom Container. network_mode = "runner-net" platziert den Container in ein isoliertes Docker-Netzwerk ohne Zugang zum Host-Netzwerk oder zu internen Diensten. Für den Deploy-Runner, der SSH-Keys benötigt, gelten andere Regeln — aber dieser Runner sollte auf einem eigenen Host mit deutlich schärferen Zugriffskontrollen laufen.

7. Netzwerkisolation und Privileged Mode

Der Privileged Mode ist der gefährlichste Schalter in der Runner-Konfiguration. Ein Container im Privileged Mode hat nahezu vollständigen Zugriff auf den Host-Kernel und kann Linux-Namespaces verlassen. GitLab.com schaltet Privileged Mode standardmäßig ab. Auf Self-Hosted Runnern aktivieren manche Teams ihn für Docker-in-Docker-Builds (dind), also für Jobs, die selbst Docker-Images bauen. Für Magento-Build-Jobs ist Docker-in-Docker nicht notwendig und sollte deaktiviert bleiben.

Netzwerkisolation schützt davor, dass ein kompromittierter Job auf interne Dienste im Netzwerk des Runner-Hosts zugreift. Ohne Isolation könnte ein Job, der auf dem Build-Runner läuft, Verbindungen zu internen Diensten wie Redis, RabbitMQ oder Datenbank-Servern aufbauen, die im selben Netzwerksegment liegen. Mit einem isolierten Docker-Netzwerk (network_mode = "runner-net") hat der Container nur Zugang zu explizit erlaubten Zielen — zum Beispiel GitLab selbst für Artifact-Uploads und Packagist für Composer.

8. Direkter Sicherheitsvergleich

Der folgende Vergleich fasst die Sicherheitseigenschaften der drei Runner-Typen für den typischen Magento-Deployment-Kontext zusammen. Die Bewertungen beziehen sich auf den Einsatz mit Production-SSH-Keys und Datenbank-Credentials.

Eigenschaft Shared Runner Group/Project Runner Self-Hosted Runner
Job-Isolation Container pro Job Container pro Job Vollständig kontrolliert
Wer hat Zugriff? Alle Projekte der Instanz Nur eigene Gruppe / Projekt Nur eigene Pipelines
Netzwerkkontrolle Keine eigene Kontrolle Keine eigene Kontrolle Vollständig konfigurierbar
Production-SSH-Keys Nicht empfehlenswert Bedingt geeignet Empfohlen
Betriebsaufwand Kein eigener Aufwand Eigene Runner-Instanz Host, OS, Monitoring, Updates

Die Empfehlung für die meisten Magento-Teams lautet: Build-Jobs (ohne Production-Secrets) können auf GitLab.com Shared Runnern oder auf einem Group Runner laufen. Deploy-Jobs (mit Production-SSH-Key) müssen auf einem Self-Hosted Runner laufen, der auf einem eigenen Host mit strikten Zugriffskontrollen betrieben wird. Diese Aufteilung kombiniert die Bequemlichkeit von Shared Runnern für unkritische Builds mit der Sicherheit von Self-Hosted Runnern für kritische Deployments.

9. Typische Sicherheitsfehler bei der Runner-Konfiguration

Der häufigste Fehler ist das Speichern von Production-SSH-Keys als ungeschützte Variablen (ohne Protected-Flag). Ungeschützte Variablen sind in jedem Job sichtbar, auch in Jobs auf Feature-Branches und in Merge-Request-Pipelines. Auf einem Shared Runner bedeutet das, dass der Key — wenn auch nur vorübergehend — in einer Container-Umgebung existiert, die nicht vollständig unter eigener Kontrolle ist. Lösung: SSH-Keys immer als Protected und Masked Variablen anlegen, und nur auf Protected Branches deployen.

Ein zweiter schwerwiegender Fehler ist das Aktivieren von privileged = true auf Build-Runnern, die keine Docker-in-Docker-Builds benötigen. Teams aktivieren Privileged Mode oft, weil ein Job es verlangt, ohne die Konsequenzen zu verstehen. Im Privileged Mode kann ein Job auf den Docker-Socket des Hosts zugreifen (/var/run/docker.sock), alle Container des Hosts inspecten und beliebige Container mit Host-Netzwerkzugang starten — das ist ein vollständiger Container-Escape. Ein dritter Fehler: Runner ohne Netzwerklimit in einem Netzwerksegment mit Datenbank-Servern registrieren. Selbst ohne Privileged Mode kann ein Job über das Netzwerk interne Dienste erreichen, wenn kein Netzwerk-Isolation-Konzept umgesetzt wurde.

10. Zusammenfassung

Die Wahl des richtigen Runner-Typs ist für Magento-Teams eine sicherheitskritische Entscheidung, nicht nur eine infrastrukturelle. Shared Runner bieten null Aufwand, aber minimale Kontrolle über Isolation und Netzwerkzugang. Self-Hosted Runner bieten maximale Kontrolle, aber erfordern eigenen Betriebsaufwand. Die pragmatische Lösung ist eine Aufteilung: unkritische Build-Jobs auf Shared oder Group Runnern, Production-Deployments ausschließlich auf Self-Hosted Runnern mit strenger Netzwerk- und Privileged-Konfiguration.

Drei Regeln, die keine Ausnahmen dulden: Production-SSH-Keys niemals auf Shared Runnern exponieren. Privileged Mode nur aktivieren, wenn Docker-in-Docker wirklich benötigt wird. Alle Variablen mit Production-Zugang als Protected und Masked anlegen, damit sie nur auf Protected Branches sichtbar sind. Mit diesen Grundregeln lässt sich ein GitLab-CI/CD-Setup aufbauen, das auch einer Sicherheitsüberprüfung standhält.

Runner-Security — Das Wichtigste auf einen Blick

Shared Runner

Kein eigener Aufwand, aber keine Kontrolle über Isolation und Netzwerk. Nicht geeignet für Production-SSH-Keys und Datenbank-Credentials.

Self-Hosted Deploy-Runner

Eigene Hardware, eigene Netzwerkkontrolle, privileged = false, cap_drop ALL. Pflicht für Jobs mit Production-SSH-Zugang.

Protected Variablen

Production-Secrets immer Protected und Masked. Sichtbarkeit auf Protected Branches beschränken — unabhängig vom Runner-Typ.

Netzwerk-Isolation

Docker-Netzwerk für Runner-Container isolieren. Kein Zugang zu internen Diensten ohne explizite Konfiguration.

11. FAQ: Runner-Security in GitLab

1Sind Shared Runner auf GitLab.com sicher?
Für unkritische Build-Jobs ohne Production-Credentials: ja. Für Jobs mit Production-SSH-Keys oder Datenbank-Passwörtern: nein. Self-Hosted Runner sind hier die richtige Wahl.
2Protected vs. Masked Variablen?
Protected: nur auf Protected Branches sichtbar. Masked: Wert in Logs versteckt. Für Production-Secrets immer beide Optionen kombinieren.
3Wann ist privileged = true gerechtfertigt?
Nur für Docker-in-Docker. Für Magento-Build-Jobs (Composer, npm) ist privileged = false die richtige und sicherere Einstellung.
4Group Runner sicher für Production-Deploys?
Bedingt — nur wenn er auf einer selbst betriebenen Maschine läuft. Ein Group Runner auf GitLab.com-Infrastruktur ist für Production-SSH-Keys nicht geeignet.
5run_untagged = false — Zweck?
Runner übernimmt nur Jobs mit seinen Tags. Verhindert, dass Deploy-Runner allgemeine Build-Jobs übernimmt und Kapazität und Secrets unnötig exponiert.
6Docker-Socket auf Runner-Hosts schützen?
/var/run/docker.sock nicht als Volume mounten. privileged = false. gitlab-runner-User nicht in docker-Gruppe, wenn kein dind benötigt wird.
7Warum ist Netzwerkisolation wichtig?
Ohne Isolation können Jobs interne Dienste (DB, Redis) im Netzwerk des Runner-Hosts erreichen. Mit isoliertem Docker-Netzwerk nur explizit erlaubte Ziele.
8Forks und Secrets?
Protected Variablen sind in MR-Pipelines aus Forks nicht sichtbar. Bei öffentlichen Projekten alle sensitiven Variablen als Protected anlegen.
9Linux-Capabilities im Container begrenzen?
cap_drop = ['ALL'] entfernt alle Capabilities. Dann nur explizit benötigte per cap_add hinzufügen. Für Magento-Builds werden keine zusätzlichen Capabilities benötigt.
10SSH-Keys in GitLab CI/CD?
Protected und Masked speichern. Deploy-Jobs nur auf Protected Branches. Runner selbst betreiben und Netzwerkzugang auf bekannte Zielserver beschränken.

Entscheidungshilfe: Welcher Runner-Typ für welchen Job?

Für Production-Deployments: eigener, gesperrter Runner mit privileged = false. Für Code-Qualitätschecks (PHPStan, PHPCS): Shared Runner ausreichend. Für Artifacts mit Vendor-Cache: Group Runner mit persistentem Cache-Volume. Für Integrationstests mit echter DB: Self-Hosted Runner mit Docker-Isolation.

Runner-Sicherheit ist kein Einmal-Setup — sie muss mit der Infrastruktur mitwachsen. Neue Projekte, neue Teammitglieder und neue Deployment-Umgebungen erfordern regelmäßige Überprüfung der Runner-Konfiguration und Zugriffsrechte.