BYOK-Unterstützung für Cloud-Gateways - Support BYOK (Bring Your Own Key) for Cloud Gateways
Implementierungsanleitung zur Aktivierung der benutzerdefinierten KI-Anbieter-API-Schlüsselverwaltung in Cloud-bereitgestellten OpenClaw-Gateways mit sicherer Speicherung und transparenter Abrechnung.
🔍 Übersicht
Dieses Handbuch behandelt die Implementierungsanforderungen für die Hinzufügung von Bring Your Own Key (BYOK)-Unterstützung für Cloud-bereitgestellte OpenClaw-Gateways. Benutzer müssen ihre eigenen AI-Provider-API-Schlüssel bereitstellen können (OpenAI, Anthropic, Google AI usw.), anstatt sich auf die gebündelten Anmeldedaten der Anwendung zu verlassen.
Funktionsumfang
Das BYOK-System für Cloud-Gateways erfordert eine Implementierung über drei Schichten:
- Client-seitige UI: Einstellungs-Oberfläche zum Eingeben, Anzeigen und Verwalten von API-Schlüsseln pro Anbieter
- Sichere Speicherung: Keychain-Integration auf Desktop-Clients, verschlüsselter Tresor auf Mobilgeräten
- Gateway-Konfiguration: Mechanismus zur Umgebungsvariablen-Injektion für Cloud-bereitgestellte Gateways
Referenz zur bestehenden Implementierung
Lokale Gateways implementieren BYOK bereits über den Einrichtungsassistenten (siehe PR #221). Die Cloud-Gateway-Implementierung sollte sich an den etablierten Mustern orientieren und gleichzeitig cloud-spezifische Aspekte berücksichtigen:
Lokale Gateway-BYOK-Architektur:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Setup Wizard│────▶│ Secure Storage│────▶│ .env.local │
│ (UI) │ │ (Keychain) │ │ (File) │
└─────────────┘ └──────────────┘ └─────────────┘
Cloud-Gateway-BYOK-Architektur:
┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Settings UI │────▶│ Secure Vault │────▶│ Config API Patch │────▶│ Env Var │
│ (Cloud) │ │ (Encrypted) │ │ (Gateway) │ │ Injection │
└─────────────┘ └──────────────┘ └──────────────────┘ └─────────────┘
🧠 Technische Anforderungen
Architekturkomponenten
1. API-Schlüssel-Anbieter-Registrierung
Das System muss eine Registrierung unterstützter AI-Anbieter mit ihren Konfigurationsanforderungen pflegen:
// src/providers/registry.ts
export interface ProviderConfig {
id: string;
name: string;
apiKeyEnvVar: string;
endpoint?: string;
requiresOrgId?: boolean;
documentationUrl: string;
}
export const AI_PROVIDERS: Record<string, ProviderConfig> = {
openai: {
id: 'openai',
name: 'OpenAI',
apiKeyEnvVar: 'OPENAI_API_KEY',
endpoint: 'https://api.openai.com/v1',
documentationUrl: 'https://platform.openai.com/docs/api-keys'
},
anthropic: {
id: 'anthropic',
name: 'Anthropic',
apiKeyEnvVar: 'ANTHROPIC_API_KEY',
documentationUrl: 'https://docs.anthropic.com/en/api/getting-started'
},
google: {
id: 'google',
name: 'Google AI',
apiKeyEnvVar: 'GOOGLE_API_KEY',
requiresOrgId: true,
documentationUrl: 'https://ai.google.dev/tutorials/setup'
}
};
2. Spezifikation für sichere Speicherung
Client-seitige Speicherung (Keychain/Secure Enclave)
// src/storage/secure-keychain.ts
interface SecureKeyStorage {
// API-Schlüssel mit Anbieterkennung speichern
setApiKey(provider: string, key: string): Promise<boolean>;
// API-Schlüssel abrufen (gibt null zurück, wenn nicht gefunden)
getApiKey(provider: string): Promise<string | null>;
// Konfigurierte Anbieter auflisten (ohne Schlüssel offenzulegen)
listProviders(): Promise<string[]>;
// API-Schlüssel entfernen
deleteApiKey(provider: string): Promise<boolean>;
// Schlüsselformat vor Speicherung validieren
validateKeyFormat(provider: string, key: string): ValidationResult;
}
interface ValidationResult {
valid: boolean;
error?: string;
maskedKey?: string; // z.B. "sk-...xyz"
}
Validierungsmuster für Schlüsselformate
// Validierungsmuster nach Anbieter
const KEY_PATTERNS = {
openai: /^sk-[A-Za-z0-9_-]{20,}$/,
anthropic: /^sk-ant-[A-Za-z0-9_-]{20,}$/,
google: /^[A-Za-z0-9_-]{39}$/,
azure: /^[A-Za-z0-9]{32}$/
};
3. Gateway-Konfigurations-API
Das Cloud-Gateway benötigt einen sicheren Konfigurations-Patch-Mechanismus:
// Gateway Config API Endpoint
// POST /api/v1/gateway/config/patch
interface ConfigPatchRequest {
operation: 'set' | 'remove';
target: 'env' | 'secret';
key: string;
value?: string; // Erforderlich für 'set'-Operation
metadata?: {
provider?: string;
createdAt: string;
expiresAt?: string;
};
}
interface ConfigPatchResponse {
success: boolean;
appliedAt: string;
restartRequired: boolean;
error?: string;
}
🛠️ Implementierungsschritte
Phase 1: UI-Komponenten für Einstellungen
Schritt 1.1: API-Schlüssel-Verwaltungspanel erstellen
// src/components/Settings/ApiKeyManager.tsx
import { useState } from 'react';
import { KeychainStorage } from '@/storage/secure-keychain';
import { AI_PROVIDERS } from '@/providers/registry';
import { BillingDisclaimer } from './BillingDisclaimer';
export function ApiKeyManager() {
const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
const [apiKey, setApiKey] = useState('');
const [isValidating, setIsValidating] = useState(false);
const [configuredProviders, setConfiguredProviders] = useState<string[]>([]);
// Konfigurierte Anbieter beim Mount laden
useEffect(() => {
KeychainStorage.listProviders().then(setConfiguredProviders);
}, []);
const handleSaveKey = async () => {
if (!selectedProvider || !apiKey) return;
setIsValidating(true);
const validation = KeychainStorage.validateKeyFormat(selectedProvider, apiKey);
if (!validation.valid) {
showError(validation.error);
setIsValidating(false);
return;
}
// Sicher speichern
await KeychainStorage.setApiKey(selectedProvider, apiKey);
// Mit Cloud-Gateway synchronisieren
await syncKeyToGateway(selectedProvider, apiKey);
// Eingabe löschen und Liste aktualisieren
setApiKey('');
setConfiguredProviders(await KeychainStorage.listProviders());
setIsValidating(false);
};
return (
<div className="api-key-manager">
<BillingDisclaimer />
<div className="provider-grid">
{Object.values(AI_PROVIDERS).map(provider => (
<ProviderCard
key={provider.id}
provider={provider}
isConfigured={configuredProviders.includes(provider.id)}
onSelect={() => setSelectedProvider(provider.id)}
/>
))}
</div>
{selectedProvider && (
<ApiKeyInputForm
provider={AI_PROVIDERS[selectedProvider]}
value={apiKey}
onChange={setApiKey}
onSubmit={handleSaveKey}
isLoading={isValidating}
/>
)}
</div>
);
}
Schritt 1.2: Abrechnungshinweis-Komponente erstellen
// src/components/Settings/BillingDisclaimer.tsx
export function BillingDisclaimer() {
return (
<div className="billing-disclaimer">
<div className="disclaimer-icon">💳</div>
<div className="disclaimer-content">
<h4>Abrechnungsverantwortung</h4>
<p>
Wenn Sie Ihren eigenen API-Schlüssel bereitstellen, werden alle Nutzungskosten direkt
über Ihr Konto beim AI-Anbieter abgerechnet. OpenClaw verarbeitet keine Aufschläge oder
hat Einblick in Ihre API-Nutzung oder Abrechnung.
</p>
<ul>
<li>Sie sind verantwortlich für Nutzungslimits und Kontingente Ihres Anbieters</li>
<li>API-Schlüssel werden sicher übertragen und niemals im Klartext auf Servern gespeichert</li>
<li>Sie können den Zugriff jederzeit über das Dashboard Ihres Anbieters widerrufen</li>
</ul>
<a href="../../../docs/byok/billing-faq" target="_blank">
Mehr über BYOK-Abrechnung erfahren →
</a>
</div>
</div>
);
}
Phase 2: Implementierung der sicheren Speicherung
Schritt 2.1: Keychain-Speicherdienst
// src/storage/secure-keychain.ts
export class KeychainStorage implements SecureKeyStorage {
private static readonly SERVICE_PREFIX = 'openclaw.byok';
static async setApiKey(provider: string, key: string): Promise<boolean> {
const service = `${this.SERVICE_PREFIX}.${provider}`;
// Plattformspezifische Implementierung
if (Platform.OS === 'ios' || Platform.OS === 'android') {
return this.setSecureItem(service, key);
}
// Desktop: Electron-store mit Verschlüsselung oder native Keychain verwenden
if (process.env.ELECTRON === 'true') {
return this.setElectronKeychain(service, key);
}
throw new Error(`Plattform ${Platform.OS} unterstützt keine sichere Speicherung`);
}
static async getApiKey(provider: string): Promise<string | null> {
const service = `${this.SERVICE_PREFIX}.${provider}`;
if (Platform.OS === 'ios') {
return this.getIOSKeychain(service);
}
if (Platform.OS === 'android') {
return this.getAndroidKeystore(service);
}
if (process.env.ELECTRON === 'true') {
return this.getElectronKeychain(service);
}
return null;
}
static async listProviders(): Promise<string[]> {
// Liste der Anbieter mit gespeicherten Schlüsseln zurückgeben (ohne die Schlüssel offenzulegen)
const prefix = `${this.SERVICE_PREFIX}.`;
const services = await this.listSecureServices(prefix);
return services.map(s => s.replace(prefix, ''));
}
static validateKeyFormat(provider: string, key: string): ValidationResult {
const pattern = KEY_PATTERNS[provider];
if (!pattern) {
return { valid: false, error: `Unbekannter Anbieter: ${provider}` };
}
const trimmedKey = key.trim();
if (!pattern.test(trimmedKey)) {
return {
valid: false,
error: `Ungültiges Schlüsselformat für ${provider}. Erwartetes Format: ${pattern.description}`
};
}
return {
valid: true,
maskedKey: this.maskKey(trimmedKey)
};
}
private static maskKey(key: string): string {
// Erste 3 und letzte 4 Zeichen anzeigen
if (key.length <= 10) return '***';
return `${key.slice(0, 3)}...${key.slice(-4)}`;
}
}
Phase 3: Gateway-Konfigurationssynchronisation
Schritt 3.1: Cloud-Gateway-API-Client
// src/services/gateway-config-sync.ts
export class GatewayConfigSync {
private readonly gatewayApiBase: string;
constructor(gatewayId: string) {
this.gatewayApiBase = `https://${gatewayId}.gateways.openclaw.io`;
}
async syncApiKey(provider: string, apiKey: string): Promise<ConfigPatchResponse> {
const providerConfig = AI_PROVIDERS[provider];
if (!providerConfig) {
throw new Error(`Unbekannter Anbieter: ${provider}`);
}
const response = await fetch(`${this.gatewayApiBase}/api/v1/config/patch`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await this.getGatewayToken()}`
},
body: JSON.stringify({
operation: 'set',
target: 'secret', // Secret verwenden, nicht env, für API-Schlüssel
key: providerConfig.apiKeyEnvVar,
value: apiKey,
metadata: {
provider,
createdAt: new Date().toISOString()
}
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API-Schlüssel-Synchronisation fehlgeschlagen: ${error.message}`);
}
return response.json();
}
async removeApiKey(provider: string): Promise<ConfigPatchResponse> {
const providerConfig = AI_PROVIDERS[provider];
const response = await fetch(`${this.gatewayApiBase}/api/v1/config/patch`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await this.getGatewayToken()}`
},
body: JSON.stringify({
operation: 'remove',
target: 'secret',
key: providerConfig.apiKeyEnvVar
})
});
return response.json();
}
}
Schritt 3.2: Secret-Verwaltung auf Gateway-Seite
Auf der Cloud-Gateway-Seite implementieren Sie Secret-Rotation und sichere Injektion:
# gateway/src/middleware/secrets-injector.ts
import { SecretManager } from '@gateway/secrets';
export class SecretsInjector {
private secrets: Map<string, string> = new Map();
private secretManager: SecretManager;
constructor() {
this.secretManager = new SecretManager();
}
async loadSecrets(): Promise<void> {
// Alle BYOK-Secrets aus sicherer Speicherung laden
const userSecrets = await this.secretManager.listUserSecrets();
for (const secret of userSecrets) {
this.secrets.set(secret.key, secret.value);
}
}
getSecret(key: string): string | undefined {
return this.secrets.get(key);
}
// Wird aufgerufen, wenn AI-Anbieter verwendet wird
injectProviderCredentials(provider: string): Record<string, string> {
const envVars: Record<string, string> = {};
switch (provider) {
case 'openai':
envVars.OPENAI_API_KEY = this.getSecret('OPENAI_API_KEY') ?? '';
break;
case 'anthropic':
envVars.ANTHROPIC_API_KEY = this.getSecret('ANTHROPIC_API_KEY') ?? '';
break;
case 'google':
envVars.GOOGLE_API_KEY = this.getSecret('GOOGLE_API_KEY') ?? '';
break;
}
return envVars;
}
}
Phase 4: Konfigurationsmigration
Vorher (Verwendung gebündelter Anmeldedaten)
# gateway/.env (verwaltet von OpenClaw)
AI_PROVIDER=openai
OPENAI_API_KEY=sk-org-managed-key-12345
ANTHROPIC_API_KEY=sk-ant-org-managed-key-67890
Nachher (Benutzer-BYOK mit Fallback)
# gateway/.env (teilweise, nicht sensibel)
AI_PROVIDER=openai
USE_BUNDLED_CREDENTIALS=false
BYOK_ENABLED=true
# Secrets separat gespeichert (niemals ins Repo eingecheckt)
# Zur Laufzeit aus sicherem Secret-Manager geladen
# OPENAI_API_KEY=sk-user-provided-key (aus Secrets injiziert)
🧪 Verifizierung
Verifizierungs-Checkliste
Führen Sie die folgenden Tests durch, um die BYOK-Implementierung zu validieren:
Test 1: Schlüsselspeicher-Validierung
// Unit-Test: Keychain-Speichervorgänge
describe('KeychainStorage', () => {
it('should store and retrieve API key', async () => {
const testKey = 'sk-test-1234567890abcdefghijklmnop';
await KeychainStorage.setApiKey('openai', testKey);
const retrieved = await KeychainStorage.getApiKey('openai');
expect(retrieved).toBe(testKey);
});
it('should reject invalid key format', async () => {
const result = KeychainStorage.validateKeyFormat('openai', 'invalid-key');
expect(result.valid).toBe(false);
expect(result.error).toContain('Invalid key format');
});
it('should mask keys correctly', async () => {
const result = KeychainStorage.validateKeyFormat(
'openai',
'sk-1234567890abcdefghijklmnopqrstuvwxyz'
);
expect(result.maskedKey).toBe('sk-...qrst');
});
});
Test 2: Gateway-Konfigurationssynchronisation
// Integrationstest: Gateway-Konfigurationssynchronisation
describe('GatewayConfigSync', () => {
it('should sync API key to cloud gateway', async () => {
const sync = new GatewayConfigSync('test-gateway-123');
const response = await sync.syncApiKey('openai', 'sk-test-key');
expect(response.success).toBe(true);
expect(response.restartRequired).toBe(true);
expect(response.appliedAt).toBeDefined();
});
it('should handle unauthorized gateway access', async () => {
// Mit ungültigem Token einrichten
const sync = new GatewayConfigSync('unauthorized-gateway');
await expect(sync.syncApiKey('openai', 'sk-test'))
.rejects.toThrow('Failed to sync API key');
});
});
Test 3: End-to-End-BYOK-Ablauf
# E2E Test-Skript
#!/bin/bash
GATEWAY_ID="test-e2e-gateway"
PROVIDER="openai"
TEST_KEY="sk-test-$(date +%s)"
echo "=== BYOK End-to-End Test ==="
# 1. Schlüssel lokal speichern
echo "1. Speichere Schlüssel in Keychain..."
# Client-seitiger Vorgang (Pseudocode)
client.setApiKey --provider $PROVIDER --key $TEST_KEY
# 2. Mit Gateway synchronisieren
echo "2. Synchronisiere mit Cloud-Gateway..."
curl -X POST "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/config/patch" \
-H "Authorization: Bearer $GATEWAY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operation": "set",
"target": "secret",
"key": "OPENAI_API_KEY",
"value": "'"$TEST_KEY"'"
}'
# 3. Überprüfen, dass Gateway Schlüssel hat
echo "3. Gateway-Konfiguration überprüfen..."
RESPONSE=$(curl -s "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/config/status" \
-H "Authorization: Bearer $GATEWAY_TOKEN")
echo $RESPONSE | jq '.secrets.OPENAI_API_KEY.configured'
# Erwartet: true
# 4. AI-Anfrage mit Benutzerschlüssel testen
echo "4. AI-Anfrage mit BYOK-Anmeldedaten testen..."
curl -X POST "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4", "messages": [{"role": "user", "content": "test"}]}'
# X-Billing-Mode-Header überprüfen, sollte BYOK anzeigen
# Erwartet: X-Billing-Mode: byok
echo "=== Test abgeschlossen ==="
Erwartete Testergebnisse
| Test | Erwartetes Ergebnis |
|---|---|
| Schlüsselspeicherung | Schlüssel abrufbar, Maskierungsanzeige zeigt korrektes Format |
| Formatvalidierung | Ungültige Schlüssel mit beschreibendem Fehler abgelehnt |
| Gateway-Synchronisation | 200 OK, restartRequired: true |
| AI-Anfrage | Anfrage erfolgreich mit benutzerbereitgestelltem Schlüssel |
⚠️ Häufige Fehler
Sicherheitsfallen
- Secrets protokollieren: API-Schlüssel niemals protokollieren, auch nicht teilweise. Stellen Sie sicher, dass alle Protokollierungsanweisungen sensible Werte schwärzen.
// ❌ Falsch logger.info(`Verwende API-Schlüssel: ${apiKey}`);// ✅ Richtig logger.debug(
Verwende API-Schlüssel für Anbieter: ${provider}); - Fehlermeldungs-Leckage: Validierungsfehler können die Schlüsselstruktur offenlegen. Schlüssel in allen Fehlerantworten maskieren.
// ❌ Falsch throw new Error(`Ungültiger Schlüssel: ${providedKey}`);// ✅ Richtig throw new Error(
Ungültiges Schlüsselformat. Schlüssel muss dem Muster für ${provider} entsprechen); - Speicherzurückhaltung: API-Schlüssel im Speicher sollten nach Möglichkeit nach der Verwendung gelöscht werden. Erwägen Sie die Verwendung sicherer String-Muster.
Plattformspezifische Probleme
| Plattform | Problem | Lösung |
|---|---|---|
| macOS | Keychain-Zugriff verweigert | Berechtigung im Bereitstellungsprofil anfordern |
| Windows | Credential Manager Fallback | Sicherstellen, dass Electrons safeStorage-API verfügbar ist |
| Linux | libsecret nicht verfügbar | Fallback mit verschlüsselter Dateispeicherung implementieren |
| Mobil (iOS) | Secure Enclave-Einschränkungen | ASAuthorizationManager für Keychain-Zugriff verwenden |
| Mobil (Android) | Keystore-Verschlüsselung | API 23+ für hardwaregestützte Speicherung erforderlich |
UX-Fallen
- Unklare Abrechnungsverantwortung: Benutzer müssen verstehen, dass sie direkt vom AI-Anbieter abgerechnet werden. Der Abrechnungshinweis ist obligatorisch.
- Keine Warnung zur Schlüsselrotation: Benutzer warnen, dass das Ändern von API-Schlüsseln einen Gateway-Neustart erfordert.
- Fehlende Behandlung der Schlüsselablauf: Einige Anbieter (Azure) haben einen Schlüsselablauf. Proaktive Warnungen implementieren.
Konfigurationsfallen
// ❌ Falle: Gebündelte Anmeldedaten überschreiben
// Wenn Gateway gebündelte Anmeldedaten UND BYOK hat, welche haben Vorrang?
// ✅ Lösung: Explizite Priorität
const getEffectiveApiKey = (provider, byokKey, bundledKey) => {
if (byokKey) {
return { source: 'byok', key: byokKey };
}
if (bundledKey) {
return { source: 'bundled', key: bundledKey };
}
throw new Error('Kein API-Schlüssel konfiguriert');
};
🔗 Zugehörige Funktionen und Fehler
Zugehörige Implementierung
- #221 - Lokales Gateway-BYOK: Bestehende Implementierung von BYOK über Einrichtungsassistent. Cloud-Gateway-BYOK sollte Kernkomponenten teilen und gleichzeitig cloud-spezifische Sicherheitsanforderungen behandeln.
- Secret-Rotationssystem: Geplante Erweiterung für automatisierte API-Schlüsselrotation (siehe Roadmap).
- Multi-Provider-Fallback: Mehrere Anbieter mit automatischem Failover konfigurieren.
Zugehörige Konfigurationsoptionen
| Einstellung | Beschreibung | Standard |
|---|---|---|
AI_PROVIDER | Aktiver AI-Anbieter | openai |
USE_BUNDLED_CREDENTIALS | Auf gebündelte Schlüssel zurückfallen | true |
BYOK_ENABLED | BYOK-Funktionsflag aktivieren | true |
KEYCHAIN_SERVICE | Keychain-Dienstkennung | openclaw.byok |
Zugehörige Dokumentation
- BYOK-Funktionsübersicht
- Architektur der sicheren Speicherung
- OpenAI-Anbieter-Einrichtung
- Anthropic-Anbieter-Einrichtung
- BYOK-Abrechnung FAQ
Zukünftige Überlegungen
- Bereichsbezogene Schlüssel: Unterstützung für anbieterspezifische Schlüssel (z.B. Azure-Endpunkt-spezifische Schlüssel) mit verbesserter Validierung.
- Team-BYOK: Enterprise-Funktion für teamübergreifende API-Schlüsselverwaltung mit Audit-Protokollierung.
- Kostenschätzung: Integration mit Anbieter-APIs zur Anzeige von Nutzungsschätzungen vor Anfragen.