UNPKG

@confluentinc/schemaregistry

Version:
187 lines (186 loc) 8.24 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DekRegistryClient = void 0; const lru_cache_1 = require("lru-cache"); const async_mutex_1 = require("async-mutex"); const rest_service_1 = require("../../../rest-service"); const json_stringify_deterministic_1 = __importDefault(require("json-stringify-deterministic")); const mock_dekregistry_client_1 = require("./mock-dekregistry-client"); class DekRegistryClient { constructor(config) { this.clientConfig = config; const cacheOptions = { max: config.cacheCapacity !== undefined ? config.cacheCapacity : 1000, ...(config.cacheLatestTtlSecs !== undefined && { ttl: config.cacheLatestTtlSecs * 1000 }), }; this.restService = new rest_service_1.RestService(config.baseURLs, config.isForward, config.createAxiosDefaults, config.basicAuthCredentials, config.bearerAuthCredentials, config.maxRetries, config.retriesWaitMs, config.retriesMaxWaitMs); this.kekCache = new lru_cache_1.LRUCache(cacheOptions); this.dekCache = new lru_cache_1.LRUCache(cacheOptions); this.kekMutex = new async_mutex_1.Mutex(); this.dekMutex = new async_mutex_1.Mutex(); } static newClient(config) { const url = config.baseURLs[0]; if (url.startsWith("mock://")) { return new mock_dekregistry_client_1.MockDekRegistryClient(config); } return new DekRegistryClient(config); } config() { return this.clientConfig; } async registerKek(name, kmsType, kmsKeyId, shared, kmsProps, doc) { const cacheKey = (0, json_stringify_deterministic_1.default)({ name, deleted: false }); return await this.kekMutex.runExclusive(async () => { const kek = this.kekCache.get(cacheKey); if (kek) { return kek; } const request = { name, kmsType, kmsKeyId, ...kmsProps && { kmsProps }, ...doc && { doc }, shared, }; const response = await this.restService.handleRequest('/dek-registry/v1/keks', 'POST', request); this.kekCache.set(cacheKey, response.data); return response.data; }); } async getKek(name, deleted = false) { const cacheKey = (0, json_stringify_deterministic_1.default)({ name, deleted }); return await this.kekMutex.runExclusive(async () => { const kek = this.kekCache.get(cacheKey); if (kek) { return kek; } name = encodeURIComponent(name); const response = await this.restService.handleRequest(`/dek-registry/v1/keks/${name}?deleted=${deleted}`, 'GET'); this.kekCache.set(cacheKey, response.data); return response.data; }); } async registerDek(kekName, subject, algorithm, version = 1, encryptedKeyMaterial) { const cacheKey = (0, json_stringify_deterministic_1.default)({ kekName, subject, version, algorithm, deleted: false }); return await this.dekMutex.runExclusive(async () => { let dek = this.dekCache.get(cacheKey); if (dek) { return dek; } const request = { subject, version, algorithm, ...encryptedKeyMaterial && { encryptedKeyMaterial }, }; dek = await this.createDek(kekName, request); this.dekCache.set(cacheKey, dek); this.dekCache.delete((0, json_stringify_deterministic_1.default)({ kekName, subject, version: -1, algorithm, deleted: false })); this.dekCache.delete((0, json_stringify_deterministic_1.default)({ kekName, subject, version: -1, algorithm, deleted: true })); return dek; }); } async createDek(kekName, request) { try { // Try newer API with subject in the path const encodedKekName = encodeURIComponent(kekName); const encodedSubject = encodeURIComponent(request.subject || ''); const path = `/dek-registry/v1/keks/${encodedKekName}/deks/${encodedSubject}`; const response = await this.restService.handleRequest(path, 'POST', request); return response.data; } catch (error) { if (error.response && error.response.status === 405) { // Try fallback to older API that does not have subject in the path const encodedKekName = encodeURIComponent(kekName); const path = `/dek-registry/v1/keks/${encodedKekName}/deks`; const response = await this.restService.handleRequest(path, 'POST', request); return response.data; } else { throw error; } } } async getDek(kekName, subject, algorithm, version = 1, deleted = false) { const cacheKey = (0, json_stringify_deterministic_1.default)({ kekName, subject, version, algorithm, deleted }); return await this.dekMutex.runExclusive(async () => { const dek = this.dekCache.get(cacheKey); if (dek) { return dek; } kekName = encodeURIComponent(kekName); subject = encodeURIComponent(subject); const response = await this.restService.handleRequest(`/dek-registry/v1/keks/${kekName}/deks/${subject}/versions/${version}?algorithm=${algorithm}&deleted=${deleted}`, 'GET'); this.dekCache.set(cacheKey, response.data); return response.data; }); } async getDekEncryptedKeyMaterialBytes(dek) { if (!dek.encryptedKeyMaterial) { return null; } if (!dek.encryptedKeyMaterialBytes) { await this.dekMutex.runExclusive(async () => { if (!dek.encryptedKeyMaterialBytes) { try { const bytes = Buffer.from(dek.encryptedKeyMaterial, 'base64'); dek.encryptedKeyMaterialBytes = bytes; } catch (err) { if (err instanceof Error) { throw new Error(`Failed to decode base64 string: ${err.message}`); } throw new Error(`Unknown error: ${err}`); } } }); } return dek.encryptedKeyMaterialBytes; } async getDekKeyMaterialBytes(dek) { if (!dek.keyMaterial) { return null; } if (!dek.keyMaterialBytes) { await this.dekMutex.runExclusive(async () => { if (!dek.keyMaterialBytes) { try { const bytes = Buffer.from(dek.keyMaterial, 'base64'); dek.keyMaterialBytes = bytes; } catch (err) { if (err instanceof Error) { throw new Error(`Failed to decode base64 string: ${err.message}`); } throw new Error(`Unknown error: ${err}`); } } }); } return dek.keyMaterialBytes; } async setDekKeyMaterial(dek, keyMaterialBytes) { await this.dekMutex.runExclusive(async () => { if (keyMaterialBytes) { const str = keyMaterialBytes.toString('base64'); dek.keyMaterial = str; } }); } async close() { return; } //Cache methods for testing async checkLatestDekInCache(kekName, subject, algorithm) { const cacheKey = (0, json_stringify_deterministic_1.default)({ kekName, subject, version: -1, algorithm, deleted: false }); const cachedDek = this.dekCache.get(cacheKey); return cachedDek !== undefined; } } exports.DekRegistryClient = DekRegistryClient;