Skip to content

ARSN-479: Multi ip kms KMIP with simple round robin #2321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions lib/network/KMSInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

import type * as werelogs from 'werelogs';
// Logger should probably by RequestLogger instead

export interface KMSInterface {
createBucketKey(
bucketName: string,
logger: werelogs.Logger,
cb: (err?: Error | null, keyId?: string) => void,
): void

destroyBucketKey(
bucketKeyId: string,
logger: werelogs.Logger,
cb: (err?: Error | null) => void,
): void

generateDataKey?(
cryptoScheme: number,
masterKeyId: string,
logger: werelogs.Logger,
cb: (err: Error | null, plainTextDataKey?: Buffer, cipheredDataKey?: Buffer) => void,
): void

cipherDataKey(
cryptoScheme: number,
masterKeyId: string,
plainTextDataKey: Buffer,
logger: werelogs.Logger,
cb: (err: Error | null, cipheredDataKey?: Buffer) => void,
): void

decipherDataKey(
cryptoScheme: number,
masterKeyId: string,
cipheredDataKey: Buffer,
logger: werelogs.Logger,
cb: (err: Error | null, plainTextDataKey?: Buffer) => void,
): void

healthcheck?(
logger: werelogs.Logger,
cb: (err: Error | null) => void
): void
}
1 change: 1 addition & 0 deletions lib/network/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const probe = { ProbeServer };
export { default as RoundRobin } from './RoundRobin';
export { default as kmip } from './kmip';
export { default as kmipClient } from './kmip/Client';
export { default as kmipClusterClient } from './kmip/ClusterClient';
export { default as KmsAWSClient } from './kmsAWS/Client';
export * as rpc from './rpc/rpc';
export * as level from './rpc/level-net';
56 changes: 42 additions & 14 deletions lib/network/kmip/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import TlsTransport from './transport/tls';
import KMIP from '.';
import * as werelogs from 'werelogs';
import { arsenalErrorKMIP } from '../utils'
import { KMSInterface } from '../KMSInterface';

const CRYPTOGRAPHIC_OBJECT_TYPE = 'Symmetric Key';
const CRYPTOGRAPHIC_ALGORITHM = 'AES';
Expand Down Expand Up @@ -53,6 +54,7 @@ const searchFilter = {
* @param cb - The callback triggered after the negotiation.
*/
function _negotiateProtocolVersion(client: any, logger: werelogs.Logger, cb: any) {
const startDate = Date.now();
return client.kmip.request(logger, 'Discover Versions', [
KMIP.Structure('Protocol Version', [
KMIP.Integer('Protocol Version Major', 1),
Expand All @@ -67,10 +69,14 @@ function _negotiateProtocolVersion(client: any, logger: werelogs.Logger, cb: any
KMIP.Integer('Protocol Version Minor', 2),
]),
], (err, response) => {
const kmipLog = {
host: client.host,
latencyMs: Date.now() - startDate
};
if (err) {
const error = arsenalErrorKMIP(err);
logger.error('KMIP::negotiateProtocolVersion',
{ error,
{ error, kmip: kmipLog,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
Expand All @@ -82,7 +88,7 @@ function _negotiateProtocolVersion(client: any, logger: werelogs.Logger, cb: any
majorVersions.length !== minorVersions.length) {
const error = arsenalErrorKMIP('No suitable protocol version');
logger.error('KMIP::negotiateProtocolVersion',
{ error,
{ error, kmip: kmipLog,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
Expand All @@ -99,13 +105,18 @@ function _negotiateProtocolVersion(client: any, logger: werelogs.Logger, cb: any
* @param cb - The callback triggered after the extension mapping
*/
function _mapExtensions(client: any, logger: werelogs.Logger, cb: any) {
const startDate = Date.now();
return client.kmip.request(logger, 'Query', [
KMIP.Enumeration('Query Function', 'Query Extension Map'),
], (err, response) => {
const kmipLog = {
host: client.host,
latencyMs: Date.now() - startDate
};
if (err) {
const error = arsenalErrorKMIP(err);
logger.error('KMIP::mapExtensions',
{ error,
{ error, kmip: kmipLog,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
Expand All @@ -114,7 +125,7 @@ function _mapExtensions(client: any, logger: werelogs.Logger, cb: any) {
if (extensionNames.length !== extensionTags.length) {
const error = arsenalErrorKMIP('Inconsistent extension list');
logger.error('KMIP::mapExtensions',
{ error,
{ error, kmip: kmipLog,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
Expand All @@ -132,25 +143,31 @@ function _mapExtensions(client: any, logger: werelogs.Logger, cb: any) {
* @param cb - The callback triggered after the information discovery
*/
function _queryServerInformation(client: any, logger: werelogs.Logger, cb: any) {
const startDate = Date.now();
client.kmip.request(logger, 'Query', [
KMIP.Enumeration('Query Function', 'Query Server Information'),
], (err, response) => {
const kmipLog = {
host: client.host,
latencyMs: Date.now() - startDate
};
if (err) {
const error = arsenalErrorKMIP(err);
logger.warn('KMIP::queryServerInformation',
{ error });
{ error, kmip: kmipLog });
/* no error returned, caller can keep going */
return cb();
}
client._setVendorIdentification(
response.lookup(searchFilter.vendorIdentification)[0]);
client._setServerInformation(
JSON.stringify(response.lookup(searchFilter.serverInformation)[0]));
response.lookup(searchFilter.serverInformation)[0]);

logger.info('KMIP Server identified',
{ vendorIdentification: client.vendorIdentification,
serverInformation: client.serverInformation,
negotiatedProtocolVersion: client.kmip.protocolVersion });
negotiatedProtocolVersion: client.kmip.protocolVersion,
kmip: kmipLog });
return cb();
});
}
Expand All @@ -166,14 +183,19 @@ function _queryServerInformation(client: any, logger: werelogs.Logger, cb: any)
* @param cb - The callback triggered after the information discovery
*/
function _queryOperationsAndObjects(client: any, logger: werelogs.Logger, cb: any) {
const startDate = Date.now();
return client.kmip.request(logger, 'Query', [
KMIP.Enumeration('Query Function', 'Query Operations'),
KMIP.Enumeration('Query Function', 'Query Objects'),
], (err, response) => {
const kmipLog = {
host: client.host,
latencyMs: Date.now() - startDate
};
if (err) {
const error = arsenalErrorKMIP(err);
logger.error('KMIP::queryOperationsAndObjects',
{ error,
{ error, kmip: kmipLog,
vendorIdentification: client.vendorIdentification });
return cb(error);
}
Expand Down Expand Up @@ -204,21 +226,23 @@ function _queryOperationsAndObjects(client: any, logger: werelogs.Logger, cb: an
supportsEncrypt, supportsDecrypt,
supportsActivate, supportsRevoke,
supportsCreate, supportsDestroy,
supportsQuery, supportsSymmetricKeys });
supportsQuery, supportsSymmetricKeys,
kmip: kmipLog });
} else {
logger.info('KMIP Server provides the necessary feature set',
{ vendorIdentification: client.vendorIdentification });
{ vendorIdentification: client.vendorIdentification,
kmip: kmipLog });
}
return cb();
});
}


export default class Client {
export default class Client implements KMSInterface {
options: any;
vendorIdentification: string;
serverInformation: any[];
kmip: KMIP;
host: string;

/**
* Construct a high level KMIP driver suitable for cloudserver
Expand Down Expand Up @@ -255,6 +279,7 @@ export default class Client {
CodecClass: any,
TransportClass: any,
) {
this.host = options.kmip.transport.tls.host;
this.options = options.kmip.client || {};
this.vendorIdentification = '';
this.serverInformation = [];
Expand Down Expand Up @@ -567,20 +592,23 @@ export default class Client {
}

healthcheck(logger, cb) {
const kmipLog = { host: this.host };
// the bucket does not have to exist, just passing a common bucket name here
this.createBucketKey('kmip-healthcheck-test-bucket', logger, (err, bucketKeyId) => {
if (err) {
logger.error('KMIP::healthcheck: failure to create a test bucket key', {
error: err,
error: err, kmip: kmipLog,
});
return cb(err);
}
logger.debug('KMIP::healthcheck: success creating a test bucket key');
logger.debug('KMIP::healthcheck: success creating a test bucket key',
{ kmip: kmipLog });
this.destroyBucketKey(bucketKeyId, logger, err => {
if (err) {
logger.error('KMIP::healthcheck: failure to remove the test bucket key', {
bucketKeyId,
error: err,
kmip: kmipLog,
});
}
});
Expand Down
99 changes: 99 additions & 0 deletions lib/network/kmip/ClusterClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict'; // eslint-disable-line
/* eslint new-cap: "off" */

import TTLVCodec from './codec/ttlv';
import TlsTransport from './transport/tls';
import KMIPClient from './Client';
import { KMSInterface } from '../KMSInterface';
import type { Logger } from 'werelogs';
import async from 'async';

export default class ClusterClient implements KMSInterface {
private readonly clients: KMIPClient[];
private roundRobinIndex = 0;

/**
* Construct a high level cluster of KMIP drivers suitable for cloudserver
* @param options - Instance options
* @param options.kmip - Low level driver options
* @param options.kmip.client - This high level driver options
* @param options.kmip.client.compoundCreateActivate -
* Depends on the server's ability. False offers the best
* compatibility. True does not offer a significant
* performance gain, but can be useful in case of unreliable
* time synchronization between the client and the server.
* @param options.kmip.client.bucketNameAttributeName -
* Depends on the server's ability. Not specifying this
* offers the best compatibility and disable the attachement
* of the bucket name as a key attribute.
* @param options.kmip.codec - KMIP Codec options
* @param options.kmip.transport - KMIP Transport options
* @param CodecClass - diversion for the Codec class,
* defaults to TTLVCodec
* @param TransportClass - diversion for the Transport class,
* defaults to TlsTransport
*/
constructor(
options: {
kmip: {
codec: any;
transport: any[];
client: {
compoundCreateActivate: any;
bucketNameAttributeName: any;
};
}
},
CodecClass: any,
TransportClass: any,
) {
const { codec, client } = options.kmip;
this.clients = options.kmip.transport.map(transport => new KMIPClient(
{ kmip: { codec, transport, client } },
CodecClass || TTLVCodec,
TransportClass || TlsTransport,
));
}

next() {
if (this.roundRobinIndex >= this.clients.length) {
this.roundRobinIndex = 0;
}
return this.clients[this.roundRobinIndex++];
}


createBucketKey(...args: Parameters<KMSInterface['createBucketKey']>) {
const client = this.next();
client.createBucketKey.apply(client, args);
}

destroyBucketKey(...args: Parameters<KMSInterface['destroyBucketKey']>) {
const client = this.next();
client.destroyBucketKey.apply(client, args);
}

cipherDataKey(...args: Parameters<KMSInterface['cipherDataKey']>) {
const client = this.next();
client.cipherDataKey.apply(client, args);
}

decipherDataKey(...args: Parameters<KMSInterface['decipherDataKey']>) {
const client = this.next();
client.decipherDataKey.apply(client, args);
}

clusterHealthcheck(logger: Logger, cb: (err: Error | null) => void) {
async.parallel<any, Error>(
this.clients.map(c => (next) => c.healthcheck(logger, next)),
(err, results) => {
cb(err ?? null);
}
)
}

healthcheck(...args: Parameters<Required<KMSInterface>['healthcheck']>) {
// for now check health of every member
this.clusterHealthcheck.apply(this, args);
}
}
2 changes: 1 addition & 1 deletion lib/network/kmip/codec/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ class MyCodec {

## Encoding specification links

### TTLV Encoding Baseline Profile
### TTLV (Tag, Type, Length, Value) Encoding Baseline Profile

[TTLV Encoding Specification](http://docs.oasis-open.org/kmip/spec/v1.4/os/kmip-spec-v1.4-os.html#_Toc490660911)

Expand Down
1 change: 1 addition & 0 deletions lib/network/kmip/codec/ttlv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function _throwError(logger: werelogs.Logger, msg: string, data?: LogDictionary)
throw Error(msg);
}

/** TTLV = Tag, Type, Length, Value */
export default function TTLVCodec() {
if (!new.target) {
// @ts-ignore
Expand Down
Loading
Loading