Zum Inhalt

Secrets Management

Ziel: Sichere Verwaltung von API-Keys, Credentials und Tokens für Cloud Functions, Jobs und CI/CD.

1. Google Secret Manager

Google Secret Manager ist der zentrale Speicher für alle sensiblen Credentials in Production.

1.1 Verwendungszweck

Secrets werden verwendet für: - Job Credentials (z.B. ERP-API-Keys für Connectors) - SMTP-Credentials (Strato App-Passwort für E-Mail-Versand) - Externe API-Keys (Google Maps, Payment-Provider, etc.) - OAuth Client Secrets - Verschlüsselungs-Keys

NICHT in Secret Manager: - Firebase Service Account (wird via ADC bereitgestellt) - Öffentliche API-Keys (z.B. Firebase Web API Key) - Non-sensitive Konfiguration (gehört nach Firestore system_settings)

1.2 Secret anlegen

Via gcloud CLI:

# Neues Secret anlegen
echo '{"apiKey":"abc123","secret":"xyz789"}' | \
  gcloud secrets create job-myJobId-credentials \
    --data-file=- \
    --replication-policy=automatic \
    --project=${PROJECT_ID}

# Version aktualisieren (Secret bleibt bestehen, neue Version wird hinzugefügt)
echo '{"apiKey":"new123","secret":"new789"}' | \
  gcloud secrets versions add job-myJobId-credentials \
    --data-file=- \
    --project=${PROJECT_ID}

# Secret-Wert abrufen (für Debugging)
gcloud secrets versions access latest \
  --secret=job-myJobId-credentials \
  --project=${PROJECT_ID}

Via Google Cloud Console:

  1. Secret Manager öffnen
  2. Create Secret klicken
  3. Name: job-myJobId-credentials (Pattern: job-{jobId}-credentials für System-Jobs)
  4. Secret value: JSON-String mit allen Feldern eingeben
  5. Replication: Automatic (multi-region)
  6. Create klicken

1.3 IAM-Berechtigung

Cloud Functions benötigen die Rolle roles/secretmanager.secretAccessor:

# Service Account ermitteln
PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format='value(projectNumber)')
SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"

# Zugriff auf spezifisches Secret gewähren
gcloud secrets add-iam-policy-binding job-myJobId-credentials \
  --member="serviceAccount:${SA}" \
  --role="roles/secretmanager.secretAccessor" \
  --project=${PROJECT_ID}

# ODER: Zugriff auf alle Secrets gewähren (weniger granular, einfacher)
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
  --member="serviceAccount:${SA}" \
  --role="roles/secretmanager.secretAccessor"

1.4 Secret in Job verwenden

Firestore-Konfiguration (system_settings/jobs/entries/{jobId}):

{
  id: 'myJobId',
  // ... andere Felder ...
  secretName: 'projects/${PROJECT_ID}/secrets/job-myJobId-credentials',
  // NIEMALS credentials direkt in parameters speichern!
  parameters: {
    timeout: 300,  // OK: non-sensitive config
    // apiKey: "..."  ❌ VERBOTEN
  }
}

Job-Handler (core/functions/src/jobs/instances/job_myJobId.js):

exports.execute = async (job, credentials, logger) => {
  // credentials ist bereits geparste JSON aus Secret Manager
  const apiKey = credentials?.apiKey;
  const secret = credentials?.secret;

  if (!apiKey) {
    throw new Error('API-Key fehlt in Secret Manager');
  }

  // ... Job-Logik mit apiKey ...
};

Credential-Workflow: 1. Secret wird beim Job-Start vom Executor aus Secret Manager geladen 2. Automatisches Parsing als JSON 3. Falls Secret nicht existiert oder IAM fehlt → credentials = null 4. Handler muss Null-Check durchführen

1.5 Secret-Rotation

# Neue Version hinzufügen (alte bleibt verfügbar)
echo '{"apiKey":"rotated456"}' | \
  gcloud secrets versions add job-myJobId-credentials --data-file=-

# Alte Version deaktivieren (nicht löschen — für Audit-Trail)
gcloud secrets versions disable <version-number> \
  --secret=job-myJobId-credentials

# Secret komplett löschen (nur wenn Job gelöscht wird)
gcloud secrets delete job-myJobId-credentials

Best Practice: Rotation alle 90 Tage für kritische Credentials (ERP-API-Keys, SMTP-Passwörter).

1.6 Lokale Entwicklung (Emulator)

Secrets werden im Emulator NICHT aus Secret Manager geladen (um keine Prod-Credentials zu verwenden).

Lokale Test-Credentials (nur für Entwicklung):

# Umgebungsvariable setzen
export EASYSALE_TEST_CREDENTIALS='{"apiKey":"test123"}'

# Im Code prüfen
if (process.env.FUNCTIONS_EMULATOR === 'true') {
  credentials = JSON.parse(process.env.EASYSALE_TEST_CREDENTIALS || '{}');
}

2. GitHub Secrets (CI/CD)

GitHub Secrets werden für automatische Deployments in GitHub Actions verwendet.

2.1 Verwendungszweck

Secrets werden verwendet für: - Android Keystores (Base64-kodiert) - iOS Distribution Certificates (Base64-kodiert) - iOS Provisioning Profiles (Base64-kodiert) - App Store Connect API Key - Firebase Service Account Key (für firebase deploy) - Google Cloud Service Account (für GCP-Operationen)

2.2 Secrets generieren

Android Keystores:

cd /Users/michaelmodlmair/Development/easySale
chmod +x scripts/generate_android_keystores.sh
./scripts/generate_android_keystores.sh

Ergebnis: ~/.easysale/keystores/github-secrets.txt mit: - ANDROID_KEYSTORE_ERP_BASE64 - ANDROID_KEYSTORE_ERP_PASSWORD - ANDROID_KEY_ALIAS_ERP - ANDROID_KEY_PASSWORD_ERP - ANDROID_KEYSTORE_SHOP_BASE64 - ANDROID_KEYSTORE_SHOP_PASSWORD - ANDROID_KEY_ALIAS_SHOP - ANDROID_KEY_PASSWORD_SHOP

iOS Zertifikate & Profile:

chmod +x scripts/generate_ios_secrets.sh
./scripts/generate_ios_secrets.sh

Ergebnis: ~/.easysale/ios-secrets/github-secrets.txt mit: - IOS_CERTIFICATE_BASE64 - IOS_CERTIFICATE_PASSWORD - IOS_PROVISIONING_PROFILE_ERP_BASE64 - IOS_PROVISIONING_PROFILE_SHOP_BASE64 - APP_STORE_CONNECT_API_KEY_ID - APP_STORE_CONNECT_ISSUER_ID - APP_STORE_CONNECT_API_KEY_BASE64

Vollständige Anleitung: siehe github-actions-setup.md

2.3 Secrets in GitHub Repository hinterlegen

Via GitHub UI:

  1. Repository Settings öffnen
  2. Settings → Secrets and variables → Actions
  3. New repository secret klicken
  4. Name + Value aus den generierten Dateien einfügen
  5. Add secret klicken
  6. Für jedes Secret wiederholen

Via GitHub CLI:

# Alle Android-Secrets auf einmal setzen
cd ~/.easysale/keystores
while IFS='=' read -r key value; do
  gh secret set "$key" --body "$value" --repo Tech-Schuppen/easySale
done < github-secrets.txt

# Alle iOS-Secrets auf einmal setzen
cd ~/.easysale/ios-secrets
while IFS='=' read -r key value; do
  gh secret set "$key" --body "$value" --repo Tech-Schuppen/easySale
done < github-secrets.txt

2.4 Secrets in GitHub Actions verwenden

Workflow-Datei (.github/workflows/client-release.yml):

jobs:
  build-android:
    runs-on: ubuntu-latest
    steps:
      - name: Decode Keystore
        run: |
          echo "${{ secrets.ANDROID_KEYSTORE_ERP_BASE64 }}" | base64 -d > keystore.jks

      - name: Build APK
        env:
          KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_ERP_PASSWORD }}
          KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS_ERP }}
          KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD_ERP }}
        run: |
          flutter build apk --release \
            --dart-define=KEYSTORE_PATH=../keystore.jks

2.5 Secret-Rotation

Android Keystores: - Keystores sollten nie rotiert werden (Apps können nicht mehr aktualisiert werden) - Nur bei Kompromittierung: neuen Keystore generieren + neue App-Version mit neuem Package-Name

iOS Certificates: - Distribution Certificates sind 1 Jahr gültig - Vor Ablauf: neues Zertifikat in Apple Developer Portal erstellen - Provisioning Profiles: ebenfalls 1 Jahr gültig, können aber jederzeit neu erstellt werden - Skript erneut ausführen: ./scripts/generate_ios_secrets.sh - GitHub Secrets aktualisieren

App Store Connect API Key: - Kann jederzeit revoziert und neu erstellt werden - Bei Verdacht auf Kompromittierung: Key in App Store Connect widerrufen - Neuen Key generieren und GitHub Secret aktualisieren

2.6 Secrets lokal testen (NICHT empfohlen)

# GitHub Secrets lokal exportieren (nur für Debugging)
export ANDROID_KEYSTORE_ERP_BASE64="..."
export ANDROID_KEYSTORE_ERP_PASSWORD="..."

# Workflow lokal simulieren (mit act CLI)
act -s ANDROID_KEYSTORE_ERP_BASE64="..." -s ANDROID_KEYSTORE_ERP_PASSWORD="..."

⚠️ Warnung: Niemals echte Secrets in Shell History oder lokalen Files speichern. Nutze HISTIGNORE oder temporäre Dateien mit chmod 600.


3. Credential-Hygiene-Regeln

3.1 Harte Regeln (IMMER einhalten)

VERBOTEN: - Credentials als Parameter im Firestore-Dokument system_settings/jobs/entries/{jobId}.parameters - Credentials in Cloud Function Environment Variables (über Firebase Console sichtbar) - Credentials in Git (auch nicht in .env-Dateien oder Kommentaren) - Credentials in Cloud Logging (auch nicht verschleiert oder base64-kodiert) - Credentials über Cloud Functions Callables setzen/lesen/aktualisieren

ERLAUBT: - Secret Manager für Production (Google Cloud Secret Manager) - GitHub Secrets für CI/CD (GitHub Actions) - Lokale Umgebungsvariablen für Entwicklung (nur auf Dev-Machine, nie committen) - ADC (Application Default Credentials) für Service-Account-Auth

3.2 Credential-Arten

Art Speicher Production Speicher CI/CD Rotation TTL
ERP-API-Keys (Connectors) Secret Manager 90 Tage unbegrenzt
SMTP App-Passwort Secret Manager 90 Tage unbegrenzt
Firebase Service Account ADC (implizit) GitHub Secret (JSON) unbegrenzt
Android Keystores GitHub Secret (Base64) nie unbegrenzt
iOS Certificates GitHub Secret (Base64) 1 Jahr 1 Jahr
App Store Connect API GitHub Secret (Base64) bei Bedarf unbegrenzt
OAuth Client Secrets Secret Manager bei Bedarf unbegrenzt

3.3 Audit & Detection

ESLint-Regel: no-pii-logging (Custom Rule) - Verhindert console.log(password), logger.info({ apiKey }) - Enforcement via Pre-Commit-Hook

Secret Scanning: - GitHub Advanced Security: automatische Detection von leaked Credentials - GitGuardian: scannt alle Commits + Pull Requests

Manueller Check:

# Lokaler Scan nach potenziellen Secrets in Git-History
git log -p | grep -iE "(password|secret|apikey|token|credential)" | head -50

4. Pre-Go-Live-Check

Check Verantwortlich Status
Secret Manager in Projekt aktiviert DevOps
Job-Credentials als Secrets angelegt (nicht in Firestore) DevOps
IAM roles/secretmanager.secretAccessor für Compute SA DevOps
GitHub Secrets für Android + iOS hinterlegt DevOps
Keine Credentials in Environment Variables (Firebase Console) DevOps
ESLint no-pii-logging aktiv DevOps
Secret Scanning aktiviert (GitHub Advanced Security) DevOps
Lokale .env-Dateien in .gitignore DevOps