Zum Hauptinhalt springen
Secrets Management: Best Practices für GitHub und CI/CD
#Security #GitHub #CI/CD #DevOps #Best Practices

Secrets Management: Best Practices für GitHub und CI/CD


Wie Sie Zugangsdaten sicher verwalten – und warum "das hat ja bisher auch geklappt" keine Strategie ist

12 Minuten Lesezeit

Irgendwo in Ihrem Code steht ein Passwort. Vielleicht in einer .env-Datei, die “nur lokal” ist. Vielleicht in einem Slack-Chat von 2021. Vielleicht in einem GitHub-Commit, den Sie längst vergessen haben.

Das Problem: Bots haben nicht vergessen. Sie scannen GitHub 24/7 nach geleakten AWS-Keys, Stripe-Secrets und Datenbank-Credentials. Ein Treffer, und innerhalb von Minuten laufen auf Ihrem Account Crypto-Miner. Oder schlimmer.

Die gute Nachricht: Secrets richtig zu verwalten ist kein Hexenwerk. Es braucht nur ein System.

Was sind Secrets?

Secrets sind alle Daten, die Ihre Anwendung braucht, aber niemand sehen sollte:

TypBeispielSchadenspotenzial
Datenbank-Credentialspostgresql://user:pass@host/dbDatenleck, Manipulation, Löschung
API-KeysStripe, OpenAI, TwilioKosten, Missbrauch im Namen Ihres Accounts
JWT-SecretsSigning Key für TokensKomplette Auth-Übernahme
SSH-KeysDeployment-ZugangServer-Zugriff, Malware
SMTP-CredentialsMail-Server-ZugangSpam, Phishing unter Ihrer Domain

Die Faustregel: Wenn es peinlich wäre, wenn es jemand sieht – es ist ein Secret.

Die fünf Todsünden

Bevor wir zu den Lösungen kommen, die Klassiker des Versagens:

1. Hardcodierte Secrets im Code

// Das hier. Nicht machen.
const stripeKey = "sk_live_abc123...";

Einmal committen, für immer in der Git-History. Auch in privaten Repos ein Risiko.

2. .env in Git committen “Ich hab es nur in den main-Branch gepusht, das sieht doch keiner.” Doch.

3. Secrets im Slack/E-Mail teilen “Kannst du mir kurz den Datenbank-Zugang schicken?” – Jetzt liegt er für immer in einem Chat-Log.

4. Gleiche Secrets überall Dev, Staging, Prod – alles das gleiche Passwort. Wenn einer fällt, fallen alle.

5. “Das rotieren wir später” Spoiler: Später ist nie. Das Secret aus 2019 läuft immer noch.

GitHub Secrets einrichten

GitHub hat ein eingebautes Secrets-Management. Es ist nicht perfekt, aber es ist da.

Repository Secrets

Pfad: Repository → Settings → Secrets and variables → Actions → New repository secret

Das sind die Secrets, die nur dieses Repo braucht:

SecretWoherGenerierung
DATABASE_URLIhr Datenbankproviderpostgresql://user:pass@host:5432/db
JWT_SECRETSelbst generierenopenssl rand -base64 32
STRIPE_SECRET_KEYStripe DashboardKopieren
SMTP_PASSMail-ProviderProvider-Interface

Organization Secrets

Pfad: Organization → Settings → Secrets and variables → Actions

Für Secrets, die mehrere Repos brauchen: Docker Registry Credentials, NPM Tokens, Shared API Keys.

Environment Secrets

Pfad: Repository → Settings → Environments → New environment

Der eleganteste Weg: Unterschiedliche Secrets für development, staging, production. Das Production-Stripe-Key sollte niemand sehen, der nur am Staging arbeitet.

Secrets in GitHub Actions

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Wichtig: Environment angeben
    
    steps:
      - uses: actions/checkout@v4

      - name: Build
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          JWT_SECRET: ${{ secrets.JWT_SECRET }}
        run: npm run build

Was GitHub für Sie tut:

  • Secrets werden nie in Logs ausgegeben (maskiert)
  • Secrets werden verschlüsselt gespeichert
  • Nur Workflows können sie lesen, nicht Menschen

Was GitHub nicht für Sie tut:

  • Verhindern, dass Sie echo $SECRET in einen Log schreiben
  • Secrets rotieren
  • Prüfen, ob Ihre Secrets stark genug sind

Projektstruktur

projekt/
├── .env.example      # Committen! Vorlage mit Platzhaltern
├── .env              # NICHT committen! Echte Werte
├── .env.test         # NICHT committen! Test-Umgebung
└── .gitignore        # Muss .env enthalten

.gitignore – nicht verhandelbar

# Environment files - ALLE außer example
.env
.env.*
!.env.example

# Keys und Zertifikate
*.pem
*.key
*.p12
secrets/

# IDE-spezifische Configs (enthalten oft Secrets)
.idea/
.vscode/settings.json

.env.example – die Dokumentation

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb

# Auth
JWT_SECRET=generate-with-openssl-rand-base64-32

# Stripe (use test keys in development!)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Mail (use Mailhog or similar in development)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_USER=
SMTP_PASS=

Tipp: Kommentare, die erklären, wie man an die echten Werte kommt. “Frag Max im Slack” ist besser als nichts. Ein Link zum Password-Manager ist besser als “Frag Max”.

Secrets generieren

JWT Secret (mindestens 32 Zeichen)

openssl rand -base64 32
# Ausgabe: etwas wie K7dF9xMn2pQr5tYw8vBcHj3kLmNq6sPu

# Oder mit Node
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Passwort (lesbar, aber sicher)

openssl rand -base64 20 | tr -d '/+=' | head -c 20

SSH Deployment Key

# Ed25519 – moderner, kürzer, sicherer
ssh-keygen -t ed25519 -C "deploy@projekt" -f deploy_key -N ""

# Public Key auf Server
cat deploy_key.pub >> ~/.ssh/authorized_keys

# Private Key als GitHub Secret
cat deploy_key | base64  # Base64, weil Zeilenumbrüche Probleme machen

Validierung zur Laufzeit

Das Schlimmste: Die App startet, aber ein Secret fehlt. Statt sofort zu crashen, funktioniert sie 95% – bis jemand die eine Funktion aufruft, die das Secret braucht.

Lösung: Secrets beim Start validieren.

// src/config/env.ts
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";

export const env = createEnv({
  server: {
    NODE_ENV: z.enum(["development", "production", "test"]),
    DATABASE_URL: z.string().url(),
    JWT_SECRET: z.string().min(32, "JWT_SECRET muss mindestens 32 Zeichen haben"),
    STRIPE_SECRET_KEY: z.string().startsWith("sk_", "Muss ein Stripe Secret Key sein"),
  },
  runtimeEnv: process.env,
});

Wenn jetzt ein Secret fehlt oder falsch ist, crasht die App sofort beim Start – mit einer klaren Fehlermeldung. Nicht irgendwann um 3 Uhr nachts.

Secrets in Produktion

Vercel / Railway / Fly.io

Die meisten modernen Hosting-Plattformen haben ein eingebautes Secrets-Management:

  • Vercel: Project Settings → Environment Variables
  • Railway: Variables Tab im Service
  • Fly.io: fly secrets set DATABASE_URL=postgres://...

Nutzen Sie es. Keine .env-Dateien auf Servern.

VPS / Eigene Server

# Systemd Unit mit Environment
[Service]
EnvironmentFile=/etc/myapp/secrets

Die /etc/myapp/secrets-Datei gehört root, ist nur für den Service lesbar (chmod 600), und wird nicht ins Backup eingeschlossen.

Docker

# docker-compose.prod.yml
services:
  api:
    secrets:
      - db_password
      - jwt_secret

secrets:
  db_password:
    external: true  # Muss vorher mit 'docker secret create' angelegt werden
  jwt_secret:
    external: true

Enterprise: Secret Manager

Ab einer gewissen Größe oder bei Compliance-Anforderungen:

  • HashiCorp Vault – Self-hosted, mächtig, komplex
  • AWS Secrets Manager – Wenn Sie eh in AWS sind
  • Google Secret Manager – Dito für GCP
  • 1Password / Doppler – Entwicklerfreundliche SaaS-Lösungen

Brauchen Sie das? Wahrscheinlich nicht, wenn Sie diesen Artikel lesen. GitHub Secrets + vernünftige Hygiene reichen für die meisten Projekte.

Wenn es passiert ist

Sie haben ein Secret committet. Oder Sie vermuten es. Was jetzt?

1. Secret sofort invalidieren

Nicht morgen. Nicht nach dem Meeting. Jetzt. Neuen API-Key generieren, altes Secret deaktivieren.

2. Aus Git-History entfernen

# BFG Repo-Cleaner (schneller als git filter-branch)
bfg --delete-files .env
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push origin --force --all

3. GitHub Support kontaktieren

Cached Daten, Pull Requests, Forks – vieles bleibt auch nach Force Push. GitHub kann helfen.

4. Schadensbegrenzung

  • Logs prüfen: Wurde das Secret benutzt?
  • Bei Datenbank-Credentials: Zugriffslogs checken
  • Bei Payment-Keys: Transaktionen prüfen
  • Bei Cloud-Credentials: Nutzung und Kosten prüfen

Die harte Wahrheit: Ein einmal gepushtes Secret ist kompromittiert. Punkt. Bots finden es in Sekunden. Die History-Bereinigung ist Schadensbegrenzung, keine Lösung.

Checkliste

Bevor Sie git push ausführen:

  • git diff --staged zeigt keine Secrets
  • .env ist in .gitignore
  • .env.example ist aktuell
  • Neue Secrets sind in GitHub dokumentiert
  • CI/CD Workflow funktioniert mit den neuen Secrets

Einmal im Quartal:

  • Secrets rotieren (mindestens die kritischen)
  • Alte, nicht mehr genutzte Secrets löschen
  • .env.example noch aktuell?
  • Wer hat Zugriff auf welche Secrets?

Die Kurzfassung

Secrets Management ist kein Hexenwerk. Es ist Hygiene. Wie Zähneputzen – langweilig, aber die Alternative ist schmerzhaft.

Die Kurzfassung:

  1. Secrets nie in Git
  2. .env.example committen, .env nicht
  3. GitHub Secrets für CI/CD
  4. Unterschiedliche Secrets für Dev/Staging/Prod
  5. Validieren beim App-Start
  6. Rotieren, bevor es wehtut

Das ist alles. Keine Enterprise-Software nötig, keine komplexe Infrastruktur. Nur Disziplin.

Und wenn Sie denken “bei mir ist das nicht so kritisch” – fragen Sie sich, wie Sie sich fühlen würden, wenn Ihre Datenbank morgen öffentlich wäre. Wenn die Antwort nicht “entspannt” ist, haben Sie Arbeit vor sich.


Weiterführend