CI/CD
.yml
GitLab · Variables · Secrets · Environment Scopes
Protected, Masked und Environment Scopes
GitLab CI/CD Variables wirklich verstehen

Die drei Häkchen beim Anlegen einer GitLab-Variable sehen harmlos aus – aber welche Kombination für welche Variable gilt, bestimmt über die Sicherheit des gesamten Deployment-Prozesses. Protected, Masked und Environment Scope bedeuten drei verschiedene Dinge, die oft verwechselt werden.

10 Min. Lesezeit Protected · Masked · Scope · File-Type · Group-Variables GitLab 16+ · Magento 2.4

1. Protected, Masked und Scope: drei verschiedene Konzepte

Der häufigste Irrtum beim Umgang mit GitLab CI/CD Variables: dass Protected und Masked dasselbe bedeuten. Das tun sie nicht. Protected steuert, auf welchen Branches und Tags eine Variable verfügbar ist. Masked steuert, ob der Wert der Variable im Job-Log angezeigt oder unkenntlich gemacht wird. Environment Scope steuert, in welchen Deployment-Umgebungen eine Variable injiziert wird. Diese drei Dimensionen sind unabhängig voneinander konfigurierbar und ergeben zusammen das Sicherheitsmodell.

Ein konkretes Beispiel: Ein SSH-Private-Key für Production sollte Protected (nur auf main und Tags verfügbar), Masked (kein Klartext im Log) und scoped auf die production-Umgebung sein. Fehlt eine dieser drei Eigenschaften, entsteht eine Lücke: ohne Protected landet der Key in Feature-Branch-Pipelines, ohne Masked ist er im Job-Log lesbar, ohne Scope könnte er auf Staging-Jobs wirken, wo er nicht benötigt wird.

Das Verständnis dieser drei Konzepte ist die Grundlage für einen sicheren GitLab-Deployment-Prozess. Wer sie verwechselt, konfiguriert Variables entweder zu offen (Sicherheitsrisiko) oder zu restriktiv (Pipeline-Jobs schlagen fehl, weil Variables nicht verfügbar sind). Beide Fehler sind in der Praxis häufig und leicht vermeidbar.

2. Protected Variables: nur auf Protected Branches

Eine Protected Variable ist ausschließlich in Jobs verfügbar, die auf Protected Branches oder Protected Tags laufen. In GitLab werden Branches als Protected markiert unter Settings → Repository → Protected Branches. Die typische Konfiguration für Magento: main und release/* als Protected Branches, v* als Protected Tags. Eine Variable mit Protected-Flag ist auf Feature-Branches nicht verfügbar – das verhindert, dass ein Entwickler mit einem Feature-Branch-Pipeline-Lauf auf Production-Credentials zugreift.

Der wichtigste Anwendungsfall: Production-SSH-Keys, Production-Datenbankpasswörter und Production-API-Keys werden als Protected Variables angelegt. Sie sind damit auf Feature-Branches unsichtbar, auch wenn jemand versucht, sie über echo $VARIABLE in einem Job auszulesen. Nur Jobs auf main oder Tags sehen diese Variables. Diese Einschränkung ist keine Bequemlichkeit, sondern ein aktiver Schutz gegen Credential-Leakage durch böswilligen oder versehentlichen Code in Merge Requests.

# Variables configuration — protection levels for Magento deployment
variables:
  # Non-sensitive: available on all branches, no masking needed
  DEPLOY_PATH: /var/www/magento
  RELEASE_RETENTION: "5"
  MAGENTO_LOCALE: de_DE

# Protected + Masked + Production-scoped variables (set in GitLab UI):
# SSH_PRIVATE_KEY       — Protected: yes, Masked: yes, Scope: production
# COMPOSER_AUTH         — Protected: yes, Masked: yes, Scope: *
# DB_PASSWORD_PROD      — Protected: yes, Masked: yes, Scope: production
# DB_PASSWORD_STAGING   — Protected: no,  Masked: yes, Scope: staging

# Usage in deploy job — SSH_PRIVATE_KEY only injected on protected branches
deploy:production:
  stage: deploy
  environment:
    name: production
  rules:
    # Only runs on protected tags — Protected Variable will be injected
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
  before_script:
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | ssh-add -
  script:
    - echo "Deploying to production..."

3. Masked Variables: kein Klartext im Job-Log

Masked Variables werden im Job-Log mit [MASKED] ersetzt, wenn ihr Wert in der Ausgabe erscheinen würde. Das schützt vor versehentlicher Anzeige von Secrets in Pipeline-Logs, die von anderen Teammitgliedern eingesehen werden können. Wichtig zu verstehen: Masking ist kein vollständiger Schutz – wenn der Secret-Wert in einem anderen Format (z.B. Base64-kodiert oder aufgeteilt) im Log erscheint, wird er nicht maskiert. Masking ist eine Sicherheitsebene, keine absolute Garantie.

Es gibt technische Einschränkungen für Masked Variables: der Wert muss mindestens 8 Zeichen lang sein und darf keine Newlines enthalten. SSH-Private-Keys können daher nicht direkt als Masked Variables angelegt werden – sie enthalten Newlines. Der Workaround: den Key Base64-kodieren, als Masked Variable speichern und im Job dekodieren. Das ist in der Praxis das üblichste Muster für SSH-Keys in GitLab-Pipelines.

4. Environment Scopes: Variable pro Umgebung

Environment Scopes sind das mächtigste und am häufigsten falsch verstandene Feature von GitLab CI/CD Variables. Ein Scope bestimmt, in welchen Environments eine Variable injiziert wird. Der Wildcard-Scope * bedeutet: Variable ist in allen Environments verfügbar. Ein spezifischer Scope wie production bedeutet: Variable wird nur injiziert, wenn der Job in einem Environment mit dem Namen production läuft.

Für Magento-Deployments bedeutet das: DEPLOY_HOST kann zweimal angelegt werden – einmal mit Wert staging.example.com und Scope staging, einmal mit Wert production.example.com und Scope production. Der Deploy-Job bekommt automatisch den richtigen Wert, je nachdem in welchem Environment er läuft. Das eliminiert bedingte Logik in der Pipeline-Konfiguration und stellt sicher, dass Staging-Jobs niemals Production-Hosts verwenden können.

# Environment-scoped variables allow the same job definition for both environments
# Variables configured in GitLab UI with scopes:
# DEPLOY_HOST   → staging.example.com  (scope: staging)
# DEPLOY_HOST   → production.example.com (scope: production)
# DEPLOY_USER   → deploy (scope: *)
# REDIS_HOST    → 10.0.1.10 (scope: staging)
# REDIS_HOST    → 10.0.2.10 (scope: production)

# Single job definition — correct DEPLOY_HOST injected automatically per environment
.deploy-template: &deploy-template
  stage: deploy
  before_script:
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | ssh-add -
  script:
    - echo "Deploying to ${DEPLOY_HOST} as ${DEPLOY_USER}"
    - rsync -az ./ "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/releases/${RELEASE_ID}/"

deploy:staging:
  <<: *deploy-template
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "develop"'

deploy:production:
  <<: *deploy-template
  environment:
    name: production
    url: https://shop.example.com
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+/'
      when: manual

5. File-Type Variables: SSH-Keys und .env-Dateien

File-Type Variables in GitLab CI/CD speichern ihren Inhalt nicht als Umgebungsvariable, sondern als temporäre Datei, deren Pfad als Variable injiziert wird. Das ist die korrekte Methode für SSH-Private-Keys, die Newlines enthalten und daher nicht als normale Variables verwendet werden können. Ein SSH-Key als File-Type Variable angelegt: GitLab schreibt den Key in eine temporäre Datei und setzt die Variable auf den Dateipfad, z.B. /tmp/gitlab-ci-key-XXXX.

Der Vorteil gegenüber dem Base64-Workaround: kein Encoding und Decoding im Job-Script, kein Risiko, dass der dekodierte Inhalt im Log erscheint. Die Verwendung im Job ist direkter: ssh-add "$SSH_PRIVATE_KEY" – wobei $SSH_PRIVATE_KEY den Pfad zur temporären Datei enthält. Diese temporäre Datei wird nach dem Job automatisch gelöscht. File-Type Variables sind auch für composer auth.json und andere Konfigurationsdateien mit strukturierten Inhalten geeignet.

6. Group- vs. Project-Variables: Sichtbarkeit steuern

GitLab ermöglicht die Verwaltung von Variables auf drei Ebenen: Instance, Group und Project. Group-Variables werden an alle Projekte innerhalb der Gruppe vererbt, Project-Variables überschreiben Group-Variables mit demselben Namen. Für Magento-Agenturen mit mehreren Projekten ist das Group-Level der richtige Ort für gemeinsame Secrets wie Composer-Auth für private Pakete oder allgemeine Build-Tools-Credentials.

Project-spezifische Secrets wie Production-SSH-Keys, Datenbankpasswörter und API-Keys gehören ausschließlich auf Project-Level. Das verhindert, dass ein Projekt versehentlich auf Credentials eines anderen Projekts zugreift – auch wenn beide in derselben Gruppe sind. Die Hierarchie: Project-Variable überschreibt Group-Variable, Group-Variable überschreibt Instance-Variable. Bei Namenskonflikten gewinnt immer der spezifischere Level.

7. Magento-Deployment-Variables in der Praxis

Für ein typisches Magento-Deployment mit Staging und Production ergibt sich folgendes Variables-Schema: Nicht-sensible Werte wie Pfade und Locale-Settings werden direkt in der .gitlab-ci.yml als variables: definiert – sie sind transparent und versioniert. Semi-sensible Werte wie Hostnamen und Deploy-User werden in GitLab als nicht-Protected, nicht-Masked, mit Environment-Scope angelegt. Hochsensible Werte wie SSH-Keys, Datenbankpasswörter und API-Keys werden als Protected, Masked und environment-scoped angelegt.

Diese Dreiteilung hat einen praktischen Vorteil: die Pipeline-Konfiguration in der .gitlab-ci.yml bleibt lesbar und nachvollziehbar, weil alle nicht-sensiblen Werte dort stehen. Die GitLab-UI enthält nur die Secrets. Neue Entwickler können die Pipeline-Logik verstehen, ohne Zugriff auf GitLab-Settings zu benötigen. Und Änderungen an nicht-sensiblen Werten können über Merge Requests nachverfolgt werden.

8. Typische Konfigurationsfehler

Der häufigste Fehler: Production-Credentials werden ohne Protected-Flag angelegt. Sie sind damit in Feature-Branch-Pipelines verfügbar und könnten theoretisch über einen Merge Request mit einer echo $VARIABLE-Zeile im Script ausgelesen werden. Der zweithäufigste Fehler: Environment Scopes werden nicht gesetzt, weil es auf den ersten Blick einfacher erscheint, eine Variable global anzulegen. Das führt dazu, dass Staging-Jobs mit Production-Hosts oder -Credentials arbeiten, wenn die Variable denselben Namen hat und nur der Wert unterschiedlich sein sollte.

Ein dritter, subtilerer Fehler: SSH-Keys werden als normale (nicht File-Type) Variable angelegt und dann in Deployment-Scripts mit echo "$SSH_KEY" | ssh-add - verwendet. Das funktioniert in vielen Fällen, hat aber zwei Probleme: Der Key-Inhalt erscheint möglicherweise im Job-Log (wenn Masking fehlschlägt, weil der Wert zu lang oder aufgeteilt ist), und der Key wird direkt in die Prozessumgebung expandiert, was bei bestimmten Shell-Konfigurationen zu Problemen mit Whitespace-Handling führen kann. File-Type Variables vermeiden beide Probleme.

9. Variable-Typen im Vergleich

Die Auswahl der richtigen Variable-Konfiguration hängt von der Sensitivität und dem Verwendungskontext ab. Die folgende Tabelle zeigt die empfohlene Konfiguration für typische Magento-Deployment-Variables.

Variable Protected Masked Scope Typ
SSH_PRIVATE_KEY (prod) Ja n/a production File
COMPOSER_AUTH Ja Ja * Variable
DEPLOY_HOST Nein Nein staging / production Variable
DB_PASSWORD Ja Ja staging / production Variable
DEPLOY_PATH Nein Nein * In .gitlab-ci.yml

Die Tabelle zeigt das Prinzip: Je sensibler eine Variable, desto restriktiver die Konfiguration. Nicht-sensible Werte gehören direkt in die .gitlab-ci.yml – sie sind transparent, versioniert und müssen nicht über die GitLab-UI verwaltet werden. Hochsensible Secrets werden Protected, Masked und environment-scoped – sie sind in keinem Log sichtbar und auf Feature-Branches nicht zugänglich.

10. Zusammenfassung

Protected, Masked und Environment Scopes sind drei unabhängige Sicherheitsdimensionen, die zusammen das Variablen-Sicherheitsmodell einer GitLab-Pipeline bestimmen. Protected schränkt auf Protected Branches ein, Masked verhindert Log-Ausgabe und Scope trennt Umgebungen. Wer alle drei korrekt konfiguriert, stellt sicher, dass Staging-Credentials nicht in Production-Jobs landen und Production-Secrets nicht in Feature-Branch-Logs erscheinen.

Die praktische Empfehlung: Nicht-sensible Werte direkt in der .gitlab-ci.yml als variables: definieren. Umgebungsspezifische Hostnamen und User ohne Protected/Masked, aber mit Scope. Alle Secrets als Protected + Masked + environment-scoped. SSH-Keys als File-Type Variables. Diese Systematik macht die Pipeline-Konfiguration nachvollziehbar, die Secrets sicher und die Umgebungstrennung verlässlich.

GitLab Variables richtig konfigurieren — Das Wichtigste auf einen Blick

Protected

Variable nur auf Protected Branches und Tags verfügbar. Verhindert Zugriff auf Production-Credentials in Feature-Branch-Pipelines.

Masked

Wert wird im Job-Log mit [MASKED] ersetzt. Kein absoluter Schutz, aber verhindert versehentliche Klartext-Anzeige in Pipeline-Logs.

Environment Scope

Variable wird nur in Jobs injiziert, die im definierten Environment laufen. Ermöglicht gleiche Variablennamen mit verschiedenen Werten pro Umgebung.

File-Type

Inhalt wird als temporäre Datei gespeichert. Pflicht für SSH-Keys mit Newlines. Verhindert Whitespace-Probleme und unbeabsichtigte Log-Ausgabe.

11. FAQ: Protected Variables, Masked Variables und Environment Scopes

1Was ist der Unterschied zwischen Protected und Masked?
Protected steuert Verfügbarkeit (nur Protected Branches/Tags). Masked steuert Log-Anzeige (Wert wird durch [MASKED] ersetzt). Beide sind unabhängig voneinander konfigurierbar.
2Kann ich einen SSH-Key als Masked Variable anlegen?
Nein, SSH-Keys enthalten Newlines und Masked Variables erlauben keine Newlines. Lösung: SSH-Key als File-Type Variable anlegen – GitLab speichert ihn als temporäre Datei.
3Was passiert, wenn ich Environment Scope auf * setze?
Die Variable wird in allen Jobs injiziert. Für nicht-sensible, umgebungsunabhängige Werte geeignet. Für sensible Credentials immer spezifischen Scope setzen.
4Wie verhindere ich, dass Staging-Credentials in Production-Jobs wirken?
Durch Environment Scopes: Staging-Variable mit Scope "staging", Production-Variable mit Scope "production". GitLab injiziert automatisch die richtige Version.
5Können Group-Variables Project-Variables überschreiben?
Nein. Project-Variables haben höhere Priorität als Group-Variables. Bei gleichem Namen gewinnt immer die spezifischere Ebene (Project > Group > Instance).
6Was sind die Mindestanforderungen für Masked Variables?
Mindestens 8 Zeichen, keine Newlines, nur Base64-kompatible Zeichen. JSON-Strings können oft nicht direkt als Masked Variable angelegt werden.
7Wie lege ich COMPOSER_AUTH sicher in GitLab an?
Als Protected + Masked Variable mit Scope *. JSON-Wert: {"http-basic": {"repo.example.com": {"username": "user", "password": "token"}}}.
8Kann ich Variables in .gitlab-ci.yml statt in der GitLab-UI definieren?
Ja, für nicht-sensible Werte ist das empfohlen – transparent, versioniert, nachvollziehbar. Sensible Secrets gehören immer in die GitLab-UI, nie in die Versionskontrolle.
9Was bedeutet "Expand variable reference" in GitLab?
Wenn aktiviert, werden andere Variable-Referenzen im Wert expandiert. Für Secrets immer deaktiviert lassen, um unerwartetes Verhalten zu vermeiden.
10Wie überprüfe ich, welche Variables in einem Job verfügbar sind?
Mit export oder env im Job-Script. Maskierte Werte erscheinen als [MASKED], Protected Variables auf nicht-Protected Branches erscheinen gar nicht.