GitLab CI Deployment mit Vault SSH Secret Engine und Rsync
Diese Anleitung beschreibt, wie man in GitLab CI/CD Pipelines das Hashicorp Vault SSH Secret Engine nutzt, um sichere SSH-Verbindungen für Deployments via Rsync herzustellen.
Voraussetzungen
- Hashicorp Vault mit aktiviertem SSH Secret Engine
- GitLab mit JWT-Authentifizierung für Vault konfiguriert (siehe gitlab-jwt.md)
- Ziel-Server mit SSH-Zugang
- GitLab Runner mit
vaultundrsyncBinary
Vault Konfiguration
SSH Secret Engine aktivieren
bash
vault secrets enable -path=ssh sshSSH CA konfigurieren
Generiere ein CA-Schlüsselpaar für das Signieren von SSH-Zertifikaten:
bash
vault write ssh/config/ca generate_signing_key=trueSSH-Rolle erstellen
Erstelle eine Rolle für GitLab CI Deployments:
bash
vault write ssh/roles/gitlab-deployer \
key_type=ca \
ttl=10m \
max_ttl=30m \
allow_user_certificates=true \
allowed_users="deploy,www-data" \
default_extensions="permit-pty="Parameter:
key_type=ca- Verwendet CA-basierte Signierungttl=10m- Zertifikate sind 10 Minuten gültigallowed_users- Erlaubte SSH-Benutzer auf dem Ziel-Serverdefault_extensions- SSH-Zertifikat-Extensions
Policy für SSH-Zugriff
Erstelle gitlab-ssh-deploy.hcl:
hcl
# SSH-Zertifikat signieren
path "ssh/sign/gitlab-deployer" {
capabilities = ["create", "update"]
}Policy anwenden:
bash
vault policy write gitlab-ssh-deploy gitlab-ssh-deploy.hclJWT-Rolle aktualisieren:
bash
vault write auth/jwt_gitlab/role/gitlab-deployer - <<EOF
{
"role_type": "jwt",
"policies": ["gitlab-ssh-deploy"],
"token_explicit_max_ttl": 600,
"user_claim": "user_email",
"bound_audiences": "https://vault.iteas.cloud",
"bound_claims_type": "glob",
"bound_claims": {
"project_path": "iteas/*",
"ref_protected": "true"
},
"token_no_default_policy": true
}
EOFZiel-Server Konfiguration
CA Public Key auf Server installieren
Hole den öffentlichen CA-Schlüssel von Vault:
bash
vault read -field=public_key ssh/config/ca > /etc/ssh/vault_ca.pubKonfiguriere SSH Server (/etc/ssh/sshd_config):
TrustedUserCAKeys /etc/ssh/vault_ca.pubSSH-Dienst neu laden:
bash
systemctl reload sshdGitLab CI/CD Konfiguration
Beispiel .gitlab-ci.yml
yaml
stages:
- deploy
variables:
VAULT_ADDR: "https://vault.iteas.cloud"
DEPLOY_USER: "deploy"
DEPLOY_HOST: "prod.example.com"
DEPLOY_PATH: "/var/www/app"
deploy-prod:
stage: deploy
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.iteas.cloud
before_script:
# Vault-Authentifizierung via JWT
- export VAULT_TOKEN=$(vault write -field=token auth/jwt_gitlab/login jwt=$VAULT_ID_TOKEN)
# Temporären SSH-Schlüssel generieren
- ssh-keygen -t ed25519 -f /tmp/deploy_key -N ""
# Öffentlichen Schlüssel von Vault signieren lassen
- vault write -field=signed_key ssh/sign/gitlab-deployer public_key=@/tmp/deploy_key.pub > /tmp/deploy_key-cert.pub
# SSH-Konfiguration
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- cp /tmp/deploy_key ~/.ssh/deploy_key
- cp /tmp/deploy_key-cert.pub ~/.ssh/deploy_key-cert.pub
- chmod 600 ~/.ssh/deploy_key
- |
cat > ~/.ssh/config <<EOF
Host ${DEPLOY_HOST}
User ${DEPLOY_USER}
IdentityFile ~/.ssh/deploy_key
CertificateFile ~/.ssh/deploy_key-cert.pub
StrictHostKeyChecking accept-new
EOF
script:
# Deployment via Rsync
- |
rsync -avz --delete \
--exclude='config/secrets.php' \
--exclude='runtime/*' \
--exclude='web/uploads/*' \
--exclude='.git' \
./ ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/
# Post-Deployment-Befehle via SSH
- ssh ${DEPLOY_USER}@${DEPLOY_HOST} "cd ${DEPLOY_PATH} && composer install --no-dev"
- ssh ${DEPLOY_USER}@${DEPLOY_HOST} "cd ${DEPLOY_PATH} && php yii migrate --interactive=0"
- ssh ${DEPLOY_USER}@${DEPLOY_HOST} "cd ${DEPLOY_PATH} && php yii cache/flush-all"
only:
- tags
tags:
- docker-runner
image: alpine:latest
.install_dependencies: &install_dependencies
- apk add --no-cache openssh-client rsync curl
- curl -o /usr/local/bin/vault https://releases.hashicorp.com/vault/1.21.0/vault_1.21.0_linux_amd64.zip
- unzip /usr/local/bin/vault -d /usr/local/bin/
- chmod +x /usr/local/bin/vaultErweiterte Rsync-Optionen
yaml
script:
- |
rsync -avz \
--delete \
--delete-excluded \
--exclude='config/secrets.php' \
--exclude='runtime/*' \
--exclude='web/uploads/*' \
--exclude='.git' \
--exclude='tests/' \
--exclude='codeception.yml' \
--checksum \
--compress-level=9 \
./ ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/Wichtige Rsync-Flags:
-a- Archive-Modus (erhält Rechte, Timestamps, etc.)-v- Verbose-Ausgabe-z- Komprimierung während Übertragung--delete- Löscht Dateien auf dem Ziel, die in der Quelle nicht existieren--checksum- Verwendet Checksummen statt Timestamps--exclude- Schließt Dateien/Verzeichnisse aus
Sicherheitshinweise
- Protected Branches: Deployments sollten nur auf geschützten Branches laufen (
ref_protected: truein der Vault-Rolle) - Kurze TTL: SSH-Zertifikate sollten eine kurze Lebensdauer haben (5-30 Minuten)
- Minimale Berechtigungen: Der Deploy-User auf dem Server sollte nur Schreibrechte auf das Deployment-Verzeichnis haben
- Audit Logging: Aktiviere Vault Audit Logs für SSH-Zertifikat-Requests
- Known Hosts: Verwende
StrictHostKeyChecking accept-newstattnofür bessere Sicherheit
Fehlerbehebung
SSH-Verbindung schlägt fehl
Prüfe auf dem Ziel-Server:
bash
# SSH-Logs prüfen
journalctl -u sshd -f
# CA-Zertifikat testen
ssh-keygen -L -f /etc/ssh/vault_ca.pubVault-Authentifizierung schlägt fehl
bash
# Token-Payload prüfen
echo $VAULT_ID_TOKEN | cut -d. -f2 | base64 -d | jq
# Vault-Login testen
vault write auth/jwt_gitlab/login jwt=$VAULT_ID_TOKENRsync-Probleme
bash
# Trockenlauf
rsync -avzn --dry-run ./ user@host:/path/
# Verbose-Output erhöhen
rsync -avvvz ./ user@host:/path/Vorteile dieses Ansatzes
- Keine statischen SSH-Keys: Zertifikate werden on-demand generiert
- Automatische Rotation: Kurze TTL erzwingt regelmäßige Erneuerung
- Audit Trail: Alle SSH-Zugriffe werden in Vault protokolliert
- Zentrale Verwaltung: SSH-Berechtigungen werden zentral in Vault verwaltet
- Erhöhte Sicherheit: Kompromittierte Zertifikate sind nur kurz gültig
