April 20, 2026 • Version: v1.2.0+

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

TestErwartetes Ergebnis
SchlüsselspeicherungSchlüssel abrufbar, Maskierungsanzeige zeigt korrektes Format
FormatvalidierungUngültige Schlüssel mit beschreibendem Fehler abgelehnt
Gateway-Synchronisation200 OK, restartRequired: true
AI-AnfrageAnfrage 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

PlattformProblemLösung
macOSKeychain-Zugriff verweigertBerechtigung im Bereitstellungsprofil anfordern
WindowsCredential Manager FallbackSicherstellen, dass Electrons safeStorage-API verfügbar ist
Linuxlibsecret nicht verfügbarFallback mit verschlüsselter Dateispeicherung implementieren
Mobil (iOS)Secure Enclave-EinschränkungenASAuthorizationManager für Keychain-Zugriff verwenden
Mobil (Android)Keystore-VerschlüsselungAPI 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

EinstellungBeschreibungStandard
AI_PROVIDERAktiver AI-Anbieteropenai
USE_BUNDLED_CREDENTIALSAuf gebündelte Schlüssel zurückfallentrue
BYOK_ENABLEDBYOK-Funktionsflag aktivierentrue
KEYCHAIN_SERVICEKeychain-Dienstkennungopenclaw.byok

Zugehörige Dokumentation

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.

Belege & Quellen

Diese Troubleshooting-Anleitung wurde automatisch von der FixClaw Intelligence Pipeline aus Community-Diskussionen synthetisiert.