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:
- Secret Manager öffnen
- Create Secret klicken
- Name:
job-myJobId-credentials(Pattern:job-{jobId}-credentialsfür System-Jobs) - Secret value: JSON-String mit allen Feldern eingeben
- Replication:
Automatic(multi-region) - 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:
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:
- Repository Settings öffnen
- Settings → Secrets and variables → Actions
- New repository secret klicken
- Name + Value aus den generierten Dateien einfügen
- Add secret klicken
- 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. CLI-Credentials (DevOps-Automation)¶
Unterschied zu Runtime-Secrets: CLI-Credentials werden von DevOps-Tools (easysale-cli, gcloud, gh) während Setup/Deployment verwendet — nicht von der laufenden Application.
4.1 Verwendungszweck¶
CLI-Credentials sind: - Account-übergreifend (nicht Projekt-spezifisch) - Für Setup-Automatisierung (Onboarding-CLI, CI/CD-Setup) - Entwickler-lokal gespeichert (nicht in Secret Manager)
Beispiele: - Resend Full-Access Token (für Domain-Management im Onboarding) - GitHub Personal Access Token (für Repository-Setup) - gcloud Application Default Credentials - npm/yarn Auth-Tokens
4.2 Speicherorte¶
| Credential | Speicherort | Permission |
|---|---|---|
| Resend Full-Access Token | ~/.easysale/resend_tokens/<kunde>.token |
chmod 600 |
| gcloud ADC | ~/.config/gcloud/application_default_credentials.json |
chmod 600 |
| GitHub Token | ~/.config/gh/hosts.yml |
chmod 600 |
| npm Token | ~/.npmrc |
chmod 600 |
Warum lokal und nicht Secret Manager?
- Scope: Account-übergreifend, nicht Projekt-spezifisch
- Zeitpunkt: Benötigt bevor Firebase-Projekt existiert (Phase 0/1 im Onboarding)
- Konvention: Parallele zu anderen CLI-Tools (gcloud, gh, npm)
4.3 Backup & Sicherung¶
Primär-Speicher: Lokal auf Dev-Machine (~/.easysale/)
Backup-Speicher: Dashlane (Team-Vault "easySale DevOps")
Zu sichernde Credentials:
| Credential | Dashlane-Eintrag | Rotation |
|---|---|---|
| Resend Full-Access Token | easySale - Resend CLI Token (<kunde>) |
90 Tage |
| GitHub PAT (easysale-cli) | easySale - GitHub PAT (CLI) |
90 Tage |
| Android Keystores (Passwörter) | easySale - Android Keystore ERP |
nie |
| iOS Distribution Certificate (Passwort) | easySale - iOS Distribution Cert |
1 Jahr |
| App Store Connect API Key | easySale - App Store Connect API |
bei Bedarf |
Workflow:
- CLI-Credential wird lokal in
~/.easysale/gespeichert - Sofort danach: Eintrag in Dashlane erstellen/aktualisieren
- Bei Rotation: Dashlane-Eintrag aktualisieren, alte Version archivieren
Warum Dashlane zusätzlich?
- Disaster Recovery: Dev-Machine verloren → Credentials aus Dashlane wiederherstellen
- Team-Zugriff: Andere Entwickler können Onboarding für denselben Kunden durchführen
- Audit-Trail: Dashlane loggt Zugriffe und Änderungen
- Compliance: DSGVO/ISO-Anforderung für Credential-Management
4.4 Resend Token Management¶
Einmalige Erstellung (pro Kunde):
# Automatisch beim ersten Durchlauf von Phase 2 (Resend)
easysale-cli client phase 2 --slug <kunde>
# CLI fragt nach Token falls nicht vorhanden:
# → Browser öffnet https://resend.com/api-keys
# → Token eingeben → gespeichert in ~/.easysale/resend_tokens/<kunde>.token
Token wiederverwenden:
# Bei erneutem Durchlauf wird Token automatisch geladen
easysale-cli client phase 2 --slug <kunde>
# → "✓ Resend-Token aus ~/.easysale/resend_tokens/<kunde>.token geladen"
Token manuell setzen/aktualisieren:
# Token direkt in Datei schreiben
mkdir -p ~/.easysale/resend_tokens
echo "re_abc123..." > ~/.easysale/resend_tokens/<kunde>.token
chmod 600 ~/.easysale/resend_tokens/<kunde>.token
In Dashlane sichern:
- Dashlane öffnen → "easySale DevOps" Vault
- Neuer Eintrag:
easySale - Resend CLI Token (<kunde>) - Felder:
- Website:
https://resend.com/api-keys - Token:
re_...(aus~/.easysale/resend_tokens/<kunde>.token) - Scope:
Full access(für Domain-Management) - Erstellt:
<Datum> - Rotation:
alle 90 Tage - Notizen:
Rotation (alle 90 Tage):
# 1. Neuen Token in Resend erstellen (alter bleibt aktiv)
# → https://resend.com/api-keys → "Create API Key" → "Full access"
# 2. Lokal aktualisieren
echo "re_new_token..." > ~/.easysale/resend_tokens/<kunde>.token
# 3. Dashlane aktualisieren (alte Version archivieren, nicht löschen)
# 4. Alten Token in Resend revozieren
# → https://resend.com/api-keys → alten Token löschen
4.5 Credentials wiederherstellen (Disaster Recovery)¶
Szenario: Dev-Machine verloren/neu aufgesetzt
# 1. Dashlane installieren + einloggen
# 2. Resend-Tokens wiederherstellen
mkdir -p ~/.easysale/resend_tokens
# Für jeden Kunden aus Dashlane → Token in Datei kopieren
echo "re_..." > ~/.easysale/resend_tokens/<kunde>.token
chmod 600 ~/.easysale/resend_tokens/<kunde>.token
# 3. gcloud ADC wiederherstellen
gcloud auth application-default login
# 4. GitHub CLI authentifizieren
gh auth login
# 5. Keystores wiederherstellen (falls nötig)
# → Android: Keystore-Dateien aus Backup (~/.easysale/keystores/)
# → iOS: Certificates aus Apple Developer Portal re-downloaden
5. 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 | ☐ |