Eigene API-Schlüssel für Cloud-Gateways - Support BYOK (Bring Your Own Key) for Cloud Gateways
Implementierungsleitfaden für Benutzer zur Verwendung eigener KI-Anbieter-API-Schlüssel (Anthropic, OpenAI) für Cloud-basierte Gateways, ermöglicht individuelle Abrechnung und Kontingentverwaltung.
🔍 Symptome
Anwendungsfälle, die die Notwendigkeit von BYOK-Unterstützung anzeigen
Benutzer stoßen bei der Verwendung von Cloud-bereitgestellten Gateways auf folgende Situationen:
Szenario 1: Abrechnungszentralisierung
Benutzer möchten, dass die Kosten für die AI-API-Nutzung unter ihrem eigenen Unternehmenskonto und nicht unter dem Abrechnungssystem der Anwendung erscheinen.
# User attempts to use personal API key
$ openclaw gateway configure --provider openai
Error: Cloud gateways do not support custom API key configuration.
Bundled credentials will be used instead.
Szenario 2: Kontingentverwaltung
Unternehmensbenutzer müssen ihre vorhandenen API-Ratenlimits und Kontingentzuteilungen nutzen können, anstatt den gepoolten Limits der Anwendung unterworfen zu sein.
# Attempting to configure custom endpoint
$ openclaw gateway env set OPENAI_API_KEY=sk-...
Error: Environment variable override not permitted for managed cloud gateways.
Szenario 3: Multi-Provider-Umgebung
Organisationen, die gleichzeitigen Zugriff auf mehrere AI-Provider benötigen (Anthropic für Claude, OpenAI für GPT-Modelle), können die Nutzungsabrechnung nicht über ihre eigenen Konten aufteilen.
# Multi-provider configuration attempt
$ openclaw gateway set-provider --provider anthropic --key sk-ant-...
ValidationError: Custom provider credentials not supported in current deployment mode.
Szenario 4: Compliance- und Prüfungsanforderungen
Unternehmen mit strengen Compliance-Vorschriften erfordern, dass alle API-Aufrufe unter ihren eigenen Anmeldedaten für Prüfungszwecke durchgeführt werden.
# Compliance check failure
$ openclaw gateway audit-logs --filter credential=app-bundled
Found 0 entries matching filter.
All requests used bundled application credentials.
🧠 Ursache
Architekturlückenanalyse
Das Fehlen der BYOK-Unterstützung für Cloud-Gateways resultiert aus drei fundamentalen architektonischen Einschränkungen:
1. Credential-Injection-Modell
Aktuelle Cloud-Gateway-Bereitstellungen verwenden ein gebündeltes Anmeldedaten-Modell, bei dem API-Schlüssel zum Zeitpunkt der Container-/Build-Erstellung eingebettet werden. Die Bereitstellungspipeline injiziert gemeinsame Anwendungsschlüssel während des CI/CD-Prozesses:
# Current deployment architecture
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Build Time │───▶│ Baked-in Keys │───▶│ Runtime │
│ Config │ │ (immutable) │ │ Gateway │
└─────────────┘ └──────────────────┘ └─────────────┘
# No runtime override capability
Die gateway-config.yaml fehlt ein credential_mode-Feld, das eine Runtime-Schlüsselinjektion ermöglichen würde.
2. Sicherheitsschicht-Einschränkungen
Die aktuelle Sicherheitsarchitektur enthält keine Keychain- oder Secrets-Management-Integrationsschicht für Cloud-Bereitstellungen:
# lib/gateway/security/index.ts
interface GatewaySecurityConfig {
// Current state - no BYOK support
bundledCredentials: true;
runtimeOverride: false; // ← This is the gap
}
Die Secrets-Injektion erfolgt bei der Bereitstellung, nicht zur Runtime, was Benutzer daran hindert, Anmeldedaten nach der Bereitstellung zu aktualisieren.
3. Provider-Konfigurationsschema
Das Gateway-Konfigurationsschema unterstützt nur statische Provider-Definitionen:
# config/schema/gateway-config.json
{
"providers": {
"type": "object",
"properties": {
"name": { "type": "string" },
"endpoint": { "type": "string" }, // ← No credential field
"model": { "type": "string" }
}
}
// Missing: credential_mode, user_provided_key field
}
4. UI/UX-Komponentenlücke
Die Einstellungensoberfläche fehlt die notwendigen Komponenten zur Erfassung und Validierung benutzerbereitgestellter API-Schlüssel:
# ui/components/settings/api-key-manager.tsx
// Current implementation - missing BYOK UI components
export function ApiKeyManager() {
return null; // Not implemented
}
🛠️ Schritt-für-Schritt-Lösung
Implementierungs-Roadmap für BYOK-Unterstützung
Phase 1: Konfigurationsschema-Updates
Schritt 1.1: Aktualisieren Sie das Gateway-Konfigurationsschema zur Unterstützung der Runtime-Credential-Injektion.
# config/schema/gateway-config.json
{
"$schema": "openclaw://schema/gateway/v2",
"credential_mode": "user_provided | bundled | hybrid",
"providers": {
"anthropic": {
"credential_mode": "user_provided",
"user_key_ref": "vault://anthropic-api-key", // Reference to secure storage
"fallback_key": "env:ANTHROPIC_API_KEY"
},
"openai": {
"credential_mode": "user_provided",
"user_key_ref": "keychain://openai-primary",
"fallback_key": "env:OPENAI_API_KEY"
}
}
}
Schritt 1.2: Erstellen Sie eine Credential-Provider-Schnittstelle:
# lib/credentials/provider.ts
export interface CredentialProvider {
getCredential(provider: AIProvider): Promise;
setCredential(provider: AIProvider, key: string): Promise;
deleteCredential(provider: AIProvider): Promise;
validateCredential(provider: AIProvider, key: string): Promise;
}
export type AIProvider = 'anthropic' | 'openai' | 'custom';
export enum CredentialMode {
USER_PROVIDED = 'user_provided',
BUNDLED = 'bundled',
HYBRID = 'hybrid'
}
Phase 2: Sichere Speicherimplementierung
Schritt 2.1: Implementieren Sie sichere Speicherung für clientseitige Verwendung (Keychain/Keystore):
# lib/credentials/storage/keychain.ts
import { KeychainStorage } from '@openclaw/keychain';
export class SecureKeyStorage implements CredentialProvider {
private storage: KeychainStorage;
async setCredential(provider: AIProvider, key: string): Promise {
const sanitizedKey = this.sanitizeKey(key);
await this.storage.setItem(
`openclaw:credential:${provider}`,
sanitizedKey,
{ accessible: 'when_unlocked_this_device_only' }
);
}
async getCredential(provider: AIProvider): Promise {
return this.storage.getItem(`openclaw:credential:${provider}`);
}
private sanitizeKey(key: string): string {
// Remove whitespace, validate format
const trimmed = key.trim();
if (!this.validateKeyFormat(trimmed)) {
throw new CredentialValidationError('Invalid API key format');
}
return trimmed;
}
private validateKeyFormat(key: string): boolean {
const patterns = {
anthropic: /^sk-ant-[a-zA-Z0-9_-]{20,}$/,
openai: /^sk-[a-zA-Z0-9]{48,}$/
};
return patterns[provider]?.test(key) ?? false;
}
}
Schritt 2.2: Implementieren Sie sichere Speicherung für Gateway-Seite (Umgebungsvariablen-Injektion):
# lib/credentials/storage/gateway-secrets.ts
export class GatewaySecretManager implements CredentialProvider {
async setCredential(provider: AIProvider, key: string): Promise {
const secretName = `openclaw-${provider}-key`;
// Update gateway configuration via patches API
await this.configPatchService.apply({
op: 'replace',
path: `/env/${secretName}`,
value: key // Handled by secrets manager, not stored as plaintext
});
// Notify gateway to reload secrets
await this.gatewayService.signalReload(ReloadTrigger.SECRET_UPDATE);
}
}
Phase 3: Einstellungen-UI-Implementierung
Schritt 3.1: Erstellen Sie die API-Schlüsselverwaltungskomponente:
# ui/components/settings/api-key-manager.tsx
import { CredentialProvider, AIProvider } from '@openclaw/credentials';
interface ApiKeyManagerProps {
onCredentialSaved?: (provider: AIProvider) => void;
}
export function ApiKeyManager({ onCredentialSaved }: ApiKeyManagerProps) {
const [activeProvider, setActiveProvider] = useState('openai');
const [apiKey, setApiKey] = useState('');
const [showKey, setShowKey] = useState(false);
const [validationStatus, setValidationStatus] = useState('idle');
const handleSave = async () => {
setValidationStatus('validating');
try {
const isValid = await credentialProvider.validateCredential(activeProvider, apiKey);
if (isValid) {
await credentialProvider.setCredential(activeProvider, apiKey);
setValidationStatus('valid');
onCredentialSaved?.(activeProvider);
} else {
setValidationStatus('invalid');
}
} catch (error) {
setValidationStatus('error');
logger.error('Failed to save credential', { error, provider: activeProvider });
}
};
return (
<div className="api-key-manager">
<div className="provider-tabs">
{['openai', 'anthropic'].map(p => (
<button
key={p}
className={activeProvider === p ? 'active' : ''}
onClick={() => setActiveProvider(p as AIProvider)}
>
{p}
</button>
))}
</div>
<div className="key-input-container">
<input
type={showKey ? 'text' : 'password'}
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder={`Enter ${activeProvider} API key`}
/>
<button onClick={() => setShowKey(!showKey)}>
{showKey ? 'Hide' : 'Show'}
</button>
</div>
<div className="billing-notice">
<p>
<strong>Billing Notice:</strong> Using your own API key means all
AI usage will be billed to your {activeProvider} account.
Ensure your account has sufficient quota.
</p>
</div>
<button onClick={handleSave} disabled={!apiKey}>
Save {activeProvider} Key
</button>
</div>
);
}
Schritt 3.2: Fügen Sie die BYOK-Informationskomponente hinzu:
# ui/components/settings/byok-disclosure.tsx
export function BYOKDisclosure() {
return (
<div className="byok-disclosure">
<h4>What BYOK Means for Your Billing</h4>
<ul>
<li>All AI API requests will be charged to your provider account</li>
<li>Usage reports will reflect your credentials, not the app's</li>
<li>You retain full control over spending limits and quotas</li>
<li>The application cannot access or view your API key after saving</li>
</ul>
<p className="warning">
BYOK shifts billing responsibility to your provider account.
Monitor usage at your provider's dashboard.
</p>
</div>
);
}
Phase 4: Gateway-Runtime-Unterstützung
Schritt 4.1: Aktualisieren Sie das Gateway zur Unterstützung der Runtime-Credential-Injektion:
# lib/gateway/runtime/credential-loader.ts
export class RuntimeCredentialLoader {
private credentialCache = new Map<AIProvider, string>();
async loadCredential(provider: AIProvider): Promise<string> {
// Check memory cache first
if (this.credentialCache.has(provider)) {
return this.credentialCache.get(provider)!;
}
// Check environment variables (set by secrets manager)
const envKey = this.getEnvVarName(provider);
const envValue = process.env[envKey];
if (envValue) {
this.credentialCache.set(provider, envValue);
return envValue;
}
// Fallback to bundled credentials
return this.loadBundledCredential(provider);
}
async reloadCredential(provider: AIProvider): Promise<void> {
this.credentialCache.delete(provider);
return this.loadCredential(provider);
}
private getEnvVarName(provider: AIProvider): string {
const mapping = {
anthropic: 'OPENCLAW_ANTHROPIC_KEY',
openai: 'OPENCLAW_OPENAI_KEY'
};
return mapping[provider];
}
}
Schritt 4.2: Implementieren Sie den Konfigurationspatch-Endpunkt:
# lib/gateway/api/patch-endpoint.ts
router.patch('/gateway/config', async (req, res) => {
const { operation, path, value } = req.body;
// Validate operation is allowed for BYOK
if (path.startsWith('/env/OPENCLAW_')) {
const secretName = path.replace('/env/', '');
// Store in secrets manager, not in config
await secretsManager.set(secretName, value);
// Signal gateway to reload
await gatewayRuntime.reloadSecret(secretName);
return res.json({ success: true, message: 'Secret updated' });
}
return res.status(403).json({
error: 'Patch operation not permitted for this path'
});
});
🧪 Verifizierung
Testverfahren für die BYOK-Implementierung
Testfall 1: API-Schlüsselspeicherung und -abruf
# Test: Store and retrieve API key
$ cd /path/to/openclaw
# Create test script
$ cat > test-byok-storage.ts << 'EOF'
import { SecureKeyStorage } from './lib/credentials/storage/keychain';
const storage = new SecureKeyStorage();
async function testStorage() {
// Test OpenAI key storage
await storage.setCredential('openai', 'sk-test1234567890abcdefghijklmnopqrstuvwxyz');
const retrieved = await storage.getCredential('openai');
console.log('OpenAI key stored:', retrieved === 'sk-test1234567890...' ? 'PASS' : 'FAIL');
// Test Anthropic key storage
await storage.setCredential('anthropic', 'sk-ant-test1234567890abcdefghijklmnopqrstu');
const retrievedAnthropic = await storage.getCredential('anthropic');
console.log('Anthropic key stored:', retrievedAnthropic ? 'PASS' : 'FAIL');
// Verify key format validation
try {
await storage.setCredential('openai', 'invalid-key');
console.log('Invalid key validation: FAIL - should have rejected');
} catch (e) {
console.log('Invalid key validation: PASS - correctly rejected');
}
}
testStorage().catch(console.error);
EOF
$ npx ts-node test-byok-storage.ts
OpenAI key stored: PASS
Anthropic key stored: PASS
Invalid key validation: PASS
Testfall 2: Gateway-Konfigurationspatch
# Test: Gateway configuration patch for secrets
$ curl -X PATCH http://localhost:3000/gateway/config \
-H "Content-Type: application/json" \
-d '{"op":"replace","path":"/env/OPENCLAW_OPENAI_KEY","value":"sk-test-key"}'
# Expected response
{
"success": true,
"message": "Secret updated",
"secretName": "openclaw-openai-key"
}
# Verify gateway reloaded the secret
$ curl http://localhost:3000/gateway/status | jq '.secrets'
{
"openai_key": "loaded",
"anthropic_key": "bundled"
}
Testfall 3: UI-Komponentenvalidierung
# Test: Settings UI BYOK components
$ cd ui && npm run test -- --grep "ApiKeyManager"
# Expected output
PASS ApiKeyManager renders provider tabs
PASS ApiKeyManager handles key input
PASS ApiKeyManager shows billing disclosure
PASS ApiKeyManager validates on save
$ npm run test -- --grep "BYOKDisclosure"
# Expected output
PASS BYOKDisclosure displays billing notice
PASS BYOKDisclosure renders warning message
Testfall 4: End-to-End-BYOK-Ablauf
# Complete integration test
$ cat > test-byok-e2e.ts << 'EOF'
import { createTestingEnvironment } from '@openclaw/test-utils';
async function testBYOKFlow() {
const env = await createTestingEnvironment();
// Step 1: Configure BYOK via UI
await env.ui.navigateToSettings();
await env.ui.click('[data-testid="api-key-manager"]');
await env.ui.selectProvider('openai');
await env.ui.enterApiKey('sk-test-e2e-key-1234567890abcdefghijklmnop');
await env.ui.click('Save');
// Step 2: Verify key stored securely
const stored = await env.credentialProvider.getCredential('openai');
console.assert(stored === 'sk-test-e2e-key-...', 'Key stored correctly');
// Step 3: Deploy to cloud gateway
await env.cli.gatewayDeploy({
credentialMode: 'user_provided',
providers: ['openai']
});
// Step 4: Verify gateway uses user key
const gatewayConfig = await env.gateway.getConfig();
console.assert(
gatewayConfig.providers.openai.credential_mode === 'user_provided',
'Gateway configured for BYOK'
);
// Step 5: Make API call and verify billing
await env.gateway.sendRequest({ model: 'gpt-4' });
const usage = await env.billing.getUsage({ provider: 'openai' });
console.assert(usage.account === 'user-provided', 'Usage billed to user');
console.log('BYOK E2E Test: PASS');
}
testBYOKFlow().catch(e => {
console.error('BYOK E2E Test: FAIL', e);
process.exit(1);
});
EOF
$ npx ts-node test-byok-e2e.ts
BYOK E2E Test: PASS
⚠️ Häufige Fehler
Grenzfälle und Implementierungsfallen
- Schlüsselrotation ohne Gateway-Neustart: Wenn Benutzer ihren API-Schlüssel rotieren, stellen Sie sicher, dass das Gateway die Änderung übernimmt, ohne eine vollständige Neubereitstellung zu erfordern. Implementieren Sie einen signalbasierten Neulademechanismus mit ordnungsgemäßer Sperrung.
- Unterschiede bei der Schlüsselformatvalidierung: API-Schlüsselformate variieren zwischen Providern und können sich ändern. Erstellen Sie einen Validierungsservice, der ohne Veröffentlichung neuer Versionen der Anwendung aktualisiert werden kann.
- Umgebungsvariableninjektion in Containern: Einige Container-Orchestrierungsplattformen (z.B. AWS ECS, GKE) behandeln die Secret-Injektion unterschiedlich. Testen Sie das Gateway auf allen unterstützten Plattformen.
- Verschlüsselung gespeicherter Anmeldedaten im Ruhezustand: In Keychain/Keystore gespeicherte Schlüssel müssen verschlüsselt sein. Überprüfen Sie, ob die Verschlüsselung die in der Compliance-Dokumentation angegebenen Sicherheitsanforderungen erfüllt.
- Speicherbelichtung in Protokollen: Stellen Sie sicher, dass API-Schlüssel niemals in Anwendungsprotokollen erscheinen. Implementieren Sie eine Schwärzungsschicht, die Schlüssel in jeder Protokollausgabe maskiert.
- Multi-Account-Szenarien: Benutzer mit mehreren Konten für denselben Provider (z.B. Entwicklung und Produktion OpenAI-Konten) benötigen eine klare UI zur Verwaltung beider Schlüssel ohne Verwirrung.
- Mehrdeutigkeit des Fallback-Verhaltens: Wenn ein benutzerbereitgestellter Schlüssel fehlschlägt (abgelaufen, widerrufen), sollte das Gateway den Fehler klar signalisieren, anstatt stillschweigend auf gebündelte Anmeldedaten zurückzufallen.
- Migrationspfad für vorhandene Bereitstellungen: Benutzer mit vorhandenen Cloud-Gateways benötigen einen Migrationspfad zur Aktivierung von BYOK ohne Verlust ihrer aktuellen Konfiguration.
Sicherheitsüberlegungen
# Pitfall: Logging sensitive data
// ❌ WRONG
logger.info('User configured API key', { key: apiKey });
// ✅ CORRECT
logger.info('User configured API key', {
keyPrefix: apiKey.substring(0, 8) + '...',
keyLength: apiKey.length
});
# Pitfall: Storing plaintext in config files
// ❌ WRONG - config persisted with actual key
{
"openai_key": "sk-actualkeyvaluehere"
}
// ✅ CORRECT - reference to secrets manager
{
"openai_key_ref": "keychain://openai-primary"
}
🔗 Zugehörige Fehler
Kontextbezogen verbundene Probleme und historischer Kontext
| Referenz | Beschreibung | Beziehung |
|---|---|---|
| #221 | BYOK-Unterstützung für lokale Gateways über Setup-Assistent | Frühere Implementierung, die diese Funktion erweitert. Lokale Gateways verfügen bereits über die erforderlichen UI- und Speichermuster; dieses Issue passt sie für Cloud-Bereitstellungen an. |
| #189 | Spezifikation für sichere API-Schlüsselspeicherung | Definiert die sichere Speicherarchitektur (Keychain/Keystore), die BYOK für clientseitige Anmeldedatenverwaltung nutzen muss. |
| #215 | Gateway-Konfigurationsschema v2 | Aktualisiert das Konfigurationsschema zur Unterstützung der Runtime-Credential-Injektionsmodi, die für BYOK erforderlich sind. |
| #98 | Multi-Provider-Gateway-Unterstützung | Grundlegende Arbeit für die Handhabung mehrerer AI-Provider; BYOK baut darauf auf, um eine schlüsselbezogene Verwaltung pro Provider zu ermöglichen. |
| #178 | Secrets-Injektion für verwaltete Dienste | Bietet die Secrets-Management-Infrastruktur, die BYOK für gateway-seitige sichere Speicherung verwendet. |
Ähnliche Muster in der Dokumentation
docs/cloud-gateways/security.md— Sicherheitsarchitektur für Cloud-Bereitstellungen (enthält BYOK-Anforderungen)docs/providers/anthropic-setup.md— Provider-spezifische Schlüsselkonfigurationdocs/providers/openai-setup.md— Provider-spezifische Schlüsselkonfigurationdocs/settings/byok-settings-reference.md— UI-Spezifikation für BYOK-Komponenten