CI/CD
.yml
GitLab · SSH · Deploy Keys · Magento
SSH-Keys, Deploy Keys und Known Hosts
für sichere Deployments einrichten

Deployment-Keys, die zu breit berechtigt sind oder ohne Rotation jahrelang im Einsatz bleiben, sind ein unterschätztes Risiko. Ed25519-Schlüsselpaare, GitLab Deploy Keys, korrekte Known-Hosts-Verwaltung und sichere Rotation sind die Bausteine für auditierbare, widerrufbare SSH-Verbindungen in automatisierten Magento-Deployments.

11 Min. Lesezeit Ed25519 · Deploy Keys · Known Hosts · Key-Rotation GitLab CI/CD · Magento 2 · SSH-Sicherheit

1. SSH-Schlüsseltypen: RSA, ECDSA und Ed25519 im Vergleich

Für neue Deployment-Keys ist Ed25519 die richtige Wahl. Der Algorithmus basiert auf Elliptic-Curve-Kryptographie und erzeugt kurze, schnell zu verarbeitende Schlüssel, die bei gleichem Sicherheitsniveau erheblich kompakter sind als RSA-4096. Ed25519 ist seit OpenSSH 6.5 verfügbar und auf allen aktuellen Linux-Systemen unterstützt – inklusive der GitLab-Runner-Umgebungen. Im Gegensatz zu ECDSA, bei dem fehlerhafte RNG-Implementierungen historisch zu Key-Kompromittierungen geführt haben, ist Ed25519 konstruktionsbedingt robust gegen solche Angriffsvektoren.

RSA-Keys mit 2048 Bit sollten für neue Deployments nicht mehr verwendet werden. RSA-4096 ist zwar noch sicher, aber langsamer und produziert deutlich längere Schlüsseldateien, was in automatisierten Systemen keine Vorteile bringt. Für bestehende RSA-Keys gilt: Sie müssen nicht sofort rotiert werden, aber bei der nächsten planmäßigen Rotation auf Ed25519 wechseln. Der Unterschied in der Sicherheitsbewertung liegt nicht primär im Algorithmus, sondern in der Schlüssellänge und der korrekten Verwaltung.

Ein Aspekt, der oft übersehen wird: Der Schlüsseltyp beeinflusst auch die Zeilenlänge in der authorized_keys. Ed25519-Keys passen in eine kompakte Zeile und lassen sich einfacher überprüfen und in GitLab-Variablen speichern. RSA-4096-Keys sind mehrzeilig, was in einigen CI-Variablen-Konfigurationen zu Problemen führen kann, wenn Zeilenumbrüche unbeabsichtigt escaped werden.

2. GitLab Deploy Keys vs. Personal Access Tokens vs. CI-Variablen

GitLab bietet drei Wege, SSH-Zugriff für automatisierte Prozesse zu konfigurieren. Deploy Keys sind SSH-Public-Keys, die direkt an ein Repository gebunden sind und nur Lesezugriff (oder optional Schreibzugriff) auf dieses eine Repository gewähren. Sie sind ideal für Fälle, in denen der Runner Code aus einem privaten Repository klonen muss. Ein Deploy Key ist kein Personenkonto – er gehört dem Repository, nicht einem User.

CI/CD-Variablen vom Typ File sind der richtige Weg, den Private Key für Server-Deployments im Runner verfügbar zu machen. Der Unterschied zu Deploy Keys: Der Private Key in einer CI-Variable authentifiziert den Runner gegenüber einem Server, nicht gegenüber GitLab selbst. Diese Trennung ist wichtig: Für Repository-Zugriff Deploy Keys nutzen, für Server-Deployments CI-Variablen mit dem Deployment-Key-Paar. Beide kombinieren manche Teams – ein Deploy Key für Composer-Pakete aus privaten GitLab-Repositories, ein separates Schlüsselpaar für den Zielsystem-SSH-Zugriff.

stages:
  - build
  - deploy
  - verify

variables:
  GIT_STRATEGY: fetch
  COMPOSER_CACHE_DIR: .cache/composer

# Reusable SSH setup anchor — loaded before each deploy/verify job
.ssh_init: &ssh_init
  before_script:
    # Start SSH agent and load deployment private key (File variable)
    - eval $(ssh-agent -s)
    - chmod 600 "$SSH_PRIVATE_KEY"
    - ssh-add "$SSH_PRIVATE_KEY"
    # Write known_hosts from CI variable — prevents MitM attacks
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts

deploy:production:
  stage: deploy
  <<: *ssh_init
  script:
    - ssh -o StrictHostKeyChecking=yes "$DEPLOY_USER@$DEPLOY_HOST" \
        "bash -s -- $CI_COMMIT_TAG" < scripts/deploy.sh
  environment:
    name: production
    url: https://mironsoft.de
  only:
    - tags
  when: manual

3. Ed25519-Schlüsselpaar für Deployments generieren

Ein Deployment-Schlüsselpaar wird einmalig auf einer sicheren lokalen Maschine generiert und nie auf dem Produktionsserver erzeugt. Der Befehl lautet: ssh-keygen -t ed25519 -C "gitlab-deploy@mironsoft.de" -f ~/.ssh/magento_deploy_ed25519 -N "". Das -N "" setzt keine Passphrase – das ist bei automatisierten Deployments notwendig, weil kein interaktiver Passphrase-Input möglich ist. Der Private Key wird in GitLab als CI-Variable hinterlegt, der Public Key auf dem Zielserver in die authorized_keys des Deployment-Users eingetragen.

Der Kommentar (-C) sollte den Verwendungszweck klar beschreiben: Wer diesen Key wofür verwendet. Das erleichtert die Verwaltung, wenn auf einem Server mehrere Keys in der authorized_keys stehen. Ein typisches Namensschema für Deployment-Keys: gitlab-deploy-[projekt]-[umgebung]@[organisation]. Mit diesem Schema sieht man sofort, ob ein Key für Production oder Staging gedacht ist und zu welchem Projekt er gehört.

4. Private Key als GitLab File-Variable hinterlegen

In den Projekteinstellungen unter Settings > CI/CD > Variables legt man eine neue Variable an: Name SSH_PRIVATE_KEY, Typ File, Wert: den vollständigen Inhalt der privaten Schlüsseldatei inklusive der Headerzeilen -----BEGIN OPENSSH PRIVATE KEY----- und -----END OPENSSH PRIVATE KEY-----. Die Variable muss als Protected markiert sein, wenn sie nur für Protected Branches und Tags verfügbar sein soll. Masked für File-Variablen ist in GitLab nicht möglich, weil der Inhalt mehrere Zeilen hat.

Ein häufiger Fehler: Die Variable als normalen String statt als File anlegen. Als String erhält der Runner den Key-Inhalt als Umgebungsvariable, nicht als Dateipfad. Der ssh-add-Befehl erwartet aber einen Pfad. Die Fehlermeldung ist dann No such file or directory, obwohl die Variable gesetzt ist. Als File-Variable schreibt GitLab den Inhalt in eine temporäre Datei und übergibt deren Pfad als Umgebungsvariable $SSH_PRIVATE_KEY – genau das, was chmod 600 "$SSH_PRIVATE_KEY" && ssh-add "$SSH_PRIVATE_KEY" erwartet.

5. authorized_keys auf dem Zielserver korrekt konfigurieren

Der Public Key des Deployment-Schlüsselpaares wird in die Datei ~/.ssh/authorized_keys des Deployment-Users auf dem Zielserver eingetragen. Die Datei und das .ssh-Verzeichnis müssen die korrekten Berechtigungen haben: chmod 700 ~/.ssh und chmod 600 ~/.ssh/authorized_keys. Falsche Berechtigungen führen dazu, dass sshd den Key ignoriert – ohne Fehlermeldung, die den wahren Grund nennt.

Für zusätzliche Sicherheit kann jeder authorized_keys-Eintrag mit Optionen eingeschränkt werden. Die Option no-pty,no-agent-forwarding,no-X11-forwarding verhindert, dass der Deployment-Key für interaktive Sessions oder Agent-Forwarding missbraucht wird. Mit from="[IP-des-Runners]" kann der Key auf Verbindungen von bestimmten IP-Adressen beschränkt werden – praktisch, wenn der GitLab-Runner eine feste IP hat. Mit command="/opt/deploy/entrypoint.sh" wird der Key auf ein einziges Skript beschränkt, was aber die Deployment-Flexibilität stark einschränkt.

6. Known Hosts: Fingerprints sammeln und verwenden

Die known_hosts-Datei ist der Mechanismus, mit dem SSH sicherstellt, dass der Zielserver derselbe ist wie beim letzten Verbindungsaufbau. In automatisierten Umgebungen muss diese Datei vorab befüllt werden, weil kein interaktiver Fingerprint-Bestätigungs-Dialog möglich ist. Der Befehl ssh-keyscan -H $DEPLOY_HOST liefert die Hostschlüssel des Servers im known_hosts-Format. Das -H-Flag hasht den Hostnamen, was empfohlen wird, damit ein kompromittierter known_hosts-Eintrag keine Hostnamen preisgibt.

Den Output von ssh-keyscan kopiert man als GitLab CI/CD-Variable SSH_KNOWN_HOSTS. Im before_script schreibt man ihn mit echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts in die known_hosts-Datei des Runners. Die Kombination aus gespeichertem Fingerprint und StrictHostKeyChecking=yes stellt sicher, dass die Pipeline sofort fehlschlägt, wenn der Server-Fingerprint nicht übereinstimmt – ein wichtiger Sicherheitsindikator für kompromittierte oder gewechselte Server.

# Collecting SSH fingerprints for known_hosts (run locally, not in pipeline):
#   ssh-keyscan -H production.mironsoft.de > known_hosts_production
#   ssh-keyscan -H staging.mironsoft.de > known_hosts_staging
# Then paste content into GitLab CI variables SSH_KNOWN_HOSTS_PROD / SSH_KNOWN_HOSTS_STAG

verify:connection:
  stage: verify
  before_script:
    - eval $(ssh-agent -s)
    - chmod 600 "$SSH_PRIVATE_KEY"
    - ssh-add "$SSH_PRIVATE_KEY"
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    # Use environment-specific known_hosts variable
    - echo "$SSH_KNOWN_HOSTS_PROD" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    # Verify connection and basic Magento availability
    - ssh -o StrictHostKeyChecking=yes "$DEPLOY_USER@$DEPLOY_HOST" \
        "cd $DEPLOY_PATH/current && bin/magento --version"
    # Verify HTTP health endpoint responds with 200
    - curl --fail --silent --output /dev/null \
        --write-out "HTTP %{http_code}" \
        "https://mironsoft.de/health"
  environment:
    name: production
  only:
    - tags

7. Sichere vs. unsichere Key-Verwaltung im Vergleich

Die Unterschiede zwischen sicherer und unsicherer SSH-Key-Verwaltung in GitLab-Deployments lassen sich in wenigen Punkten zusammenfassen, die jeweils konkrete Konsequenzen haben.

Aspekt Unsichere Praxis Sichere Praxis Risiko bei unsicher
Schlüsseltyp RSA-2048 oder älter Ed25519 Schwächere Sicherheitsmargin
Variable-Typ String-Variable File-Variable ssh-add schlägt fehl
Host-Prüfung StrictHostKeyChecking=no StrictHostKeyChecking=yes MitM-Angriff möglich
Key-Berechtigung Root-User oder breite sudo-Rechte Dedizierter Deploy-User ohne sudo Kompromittierung = Root-Zugriff
Key-Rotation Kein Rotationsplan Jährlich oder bei Personalwechsel Veraltete Keys im Einsatz

Besonders der Punkt StrictHostKeyChecking=no ist in vielen Projekten als Quick-Fix eingebaut worden, um Verbindungsprobleme zu umgehen. Diese Einstellung sollte in keiner Produktionspipeline existieren. Der korrekte Fix ist immer, die known_hosts-Variable zu aktualisieren – nicht, die Sicherheitsprüfung zu deaktivieren.

8. Key-Rotation ohne Deployment-Downtime

Die Rotation eines Deployment-Keys ohne Unterbrechung laufender Deployments ist mit einem einfachen zweistufigen Verfahren möglich. Zuerst generiert man ein neues Ed25519-Schlüsselpaar. Den neuen Public Key fügt man als zweiten Eintrag in die authorized_keys des Deployment-Users ein – der alte Key bleibt dabei erhalten. Dann aktualisiert man die GitLab CI/CD-Variable SSH_PRIVATE_KEY auf den neuen Private Key und löst einen Test-Pipeline-Lauf aus.

Sobald der Test erfolgreich ist und bestätigt wird, dass die Pipeline den neuen Key korrekt nutzt, entfernt man den alten Public Key aus der authorized_keys. Laufende Jobs, die noch mit dem alten Key verbunden sind, werden durch diesen zweiten Schritt nicht unterbrochen, weil die SSH-Verbindung bereits aufgebaut ist. Zukünftige Jobs verwenden ausschließlich den neuen Key. Die Rotation sollte im Team kommuniziert werden und in einem Wartungsfenster stattfinden, in dem keine kritischen Deployments geplant sind.

9. SSH-Key-Setup in der GitLab-Pipeline vollständig integrieren

Ein vollständig integriertes SSH-Key-Setup in einer GitLab-Pipeline nutzt YAML-Anchors (&anchor und *anchor), um den SSH-Initialisierungs-Block nicht in jedem Job zu wiederholen. Der Anchor enthält den kompletten before_script-Block mit SSH-Agent-Start, Key-Load und known_hosts-Schreibung. Jobs, die SSH benötigen, mergen diesen Anchor mit <<: *ssh_init. Das reduziert Wiederholung, stellt aber sicher, dass der SSH-Setup in jedem Job frisch initialisiert wird – weil Runner-Umgebungen nach jedem Job zurückgesetzt werden.

Für Multi-Environment-Setups mit Staging und Production empfiehlt sich eine Variable SSH_KNOWN_HOSTS pro Environment-Scope. GitLab erlaubt es, unterschiedliche Variablenwerte für unterschiedliche Environments (staging, production) zu definieren. So holt sich der Deploy-Job automatisch die richtigen Known-Hosts für seine Umgebung, ohne dass die Pipeline-Konfiguration für jede Umgebung manuell angepasst werden muss. Dasselbe gilt für DEPLOY_HOST und DEPLOY_USER.

stages:
  - build
  - package
  - deploy
  - verify

# SSH initialization anchor — merged into any job needing SSH access
.ssh_init: &ssh_init
  before_script:
    - eval $(ssh-agent -s)
    - chmod 600 "$SSH_PRIVATE_KEY"
    - ssh-add "$SSH_PRIVATE_KEY"
    - mkdir -p ~/.ssh && chmod 700 ~/.ssh
    # SSH_KNOWN_HOSTS is environment-scoped in GitLab variable settings
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
    # Verify key is loaded before proceeding
    - ssh-add -l

deploy:production:
  stage: deploy
  <<: *ssh_init
  script:
    - |
      ssh -o StrictHostKeyChecking=yes \
          -o ConnectTimeout=10 \
          "$DEPLOY_USER@$DEPLOY_HOST" bash -s <<'REMOTE'
      set -euo pipefail
      readonly RELEASE_ID="$(date +%Y%m%d-%H%M%S)"
      readonly RELEASE_PATH="$DEPLOY_PATH/releases/$RELEASE_ID"
      mkdir -p "$RELEASE_PATH"
      echo "[INFO] Prepared release directory: $RELEASE_PATH"
      REMOTE
  environment:
    name: production
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
      when: manual

verify:production:
  stage: verify
  <<: *ssh_init
  script:
    - curl --fail --max-time 10 "https://mironsoft.de/health"
    - ssh -o StrictHostKeyChecking=yes "$DEPLOY_USER@$DEPLOY_HOST" \
        "cd $DEPLOY_PATH/current && bin/magento cache:status"
  environment:
    name: production
  rules:
    - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'

10. Zusammenfassung

Sichere SSH-Key-Verwaltung in GitLab-Deployments beginnt mit der richtigen Wahl des Algorithmus – Ed25519 statt RSA-2048 – und endet bei einem definierten Rotationsplan. Der Private Key gehört als File-Variable in GitLab CI/CD, der Public Key in die authorized_keys eines dedizierten Deployment-Users ohne sudo-Rechte. Known Hosts werden vorab mit ssh-keyscan gesammelt und als Variable hinterlegt. StrictHostKeyChecking=yes ist keine optionale Sicherheitsmaßnahme, sondern Pflicht in jeder produktionsnahen Pipeline.

Die Rotation von Deployment-Keys ist mit dem beschriebenen zweistufigen Verfahren ohne Downtime möglich und sollte mindestens einmal jährlich oder bei Personalwechseln durchgeführt werden. Teams, die diese Grundlagen sauber umgesetzt haben, können auf jedem weiteren Sicherheitsaudit mit klaren Antworten punkten: welcher Key wird für welchen Zweck verwendet, wann wurde er zuletzt rotiert und wie kann er im Notfall sofort widerrufen werden.

SSH-Keys und Deploy Keys in GitLab — Das Wichtigste auf einen Blick

Schlüsseltyp

Ed25519 für alle neuen Deployment-Keys. Kürzer, schneller und konstruktionsbedingt robuster als RSA-2048.

GitLab-Variable

SSH_PRIVATE_KEY als File-Variable anlegen, nicht als String. ssh-add erwartet einen Dateipfad, keine Stringvariable.

Known Hosts

ssh-keyscan -H $DEPLOY_HOST ausführen, Output als CI-Variable speichern, vor jedem Job in ~/.ssh/known_hosts schreiben.

Key-Rotation

Neuen Key hinzufügen, Variable aktualisieren, testen, alten Key entfernen. Beide Keys kurzzeitig aktiv – kein Deployment-Ausfall.

11. FAQ: SSH-Keys und Deploy Keys in GitLab

1Warum Ed25519 statt RSA für Deployment-Keys?
Ed25519 erzeugt kürzere Schlüssel bei gleichwertigem Sicherheitsniveau, ist schneller und robust gegen RNG-Angriffe. Für neue Keys immer Ed25519 verwenden.
2Deploy Key vs. CI-Variable mit SSH-Key?
Deploy Key authentifiziert gegenüber GitLab-Repository (Clone). CI-Variable mit Private Key authentifiziert gegenüber externem Server (SSH-Deployment). Beide können kombiniert werden.
3ssh-add schlägt fehl, obwohl Variable gesetzt ist?
Variable muss als File-Typ angelegt sein, nicht als String. Als String enthält die Variable den Key-Inhalt, nicht den Dateipfad, den ssh-add erwartet.
4Richtigen Key im SSH-Agent prüfen?
ssh-add -l zeigt geladene Keys mit Fingerprint. ssh-keygen -lf ~/.ssh/deploy.pub zeigt den Fingerprint des Public Keys. Ausgaben müssen übereinstimmen.
5Wie oft Deployment-Keys rotieren?
Mindestens jährlich und bei jedem Personalwechsel. Bei sicherheitskritischen Projekten alle sechs Monate.
6Denselben Deploy Key für Staging und Production?
Technisch möglich, aber nicht empfohlen. Getrennte Keys erlauben unabhängige Rotation und begrenzen den Schaden einer Kompromittierung auf eine Umgebung.
7Was ist ein SSH-Server-Fingerprint?
Der Hash des öffentlichen Host-Keys des Servers. Prüfen mit: ssh-keyscan -H hostname | ssh-keygen -lf -. Muss mit dem Wert auf dem Server übereinstimmen.
8StrictHostKeyChecking=no in Produktion – warum gefährlich?
Mit dieser Option verbindet sich die Pipeline auch mit einem kompromittierten Server ohne Warnung. known_hosts pflegen und StrictHostKeyChecking=yes setzen ist die einzig sichere Alternative.
9Known Hosts für mehrere Server in einer Variable?
ssh-keyscan für jeden Server ausführen und alle Ausgaben in einer Variable zusammenfassen. Beim Schreiben in ~/.ssh/known_hosts werden alle Zeilen berücksichtigt.
10Was passiert mit laufenden Jobs bei Key-Rotation?
Bereits aufgebaute Verbindungen werden nicht getrennt. Neue Jobs verwenden den neuen Key. Solange der alte Key noch in authorized_keys steht, laufen beide parallel – das ermöglicht downtime-freie Rotation.