@confluentinc/schemaregistry
Version:
Node.js client for Confluent Schema Registry
187 lines (186 loc) • 8.24 kB
JavaScript
"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;