Skip to content
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

Add AWS KMS client connector #5624

Open
wants to merge 4 commits into
base: development/7.70
Choose a base branch
from
Open
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
73 changes: 72 additions & 1 deletion lib/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ class Config extends EventEmitter {
}
const tlsFilePath = (tlsFileName[0] === '/')
? tlsFileName
: path.join(this._basepath, tlsFileName);
: path.join(this._basePath, tlsFileName);
let tlsFileContent;
try {
tlsFileContent = fs.readFileSync(tlsFilePath);
Expand All @@ -502,6 +502,19 @@ class Config extends EventEmitter {
return tlsFileContent;
}

// Load TLS file or array of files
// if tlsFilename is a string, result will be a Buffer containing the file content
// if tlsFilename is an array of string, result will be an array of Buffer
_loadTlsFileArray(tlsFileName) {
let res;
if (Array.isArray(tlsFileName)) {
res = tlsFileName.map(this._loadTlsFile);
} else {
res = this._loadTlsFile(tlsFileName);
}
return res;
}

/**
* Parse list of endpoints.
* @param {string[] | undefined} listenOn - List of string of the form "ip:port"
Expand Down Expand Up @@ -1092,6 +1105,64 @@ class Config extends EventEmitter {
}
}

// Use env variables as default values.
// We use the same env variables as the AWS CLI does but prefixed with "KMS_",
// allowing distinct endpoints betweens AWS compatibility components.
// Please note that if no config is specified here, the AWS Client
// seems to fallback on the local AWS configuration files
// (those contained in ~/.aws directory)
this.kmsAWS = {
region: process.env.KMS_AWS_REGION || process.env.KMS_AWS_DEFAULT_REGION,
endpoint: process.env.KMS_AWS_ENDPOINT_URL_KMS || process.env.KMS_AWS_ENDPOINT_URL,
ak: process.env.KMS_AWS_ACCESS_KEY_ID,
sk: process.env.KMS_AWS_SECRET_ACCESS_KEY,
};
if (config.kmsAWS) {
const { region, endpoint, ak, sk, tls } = config.kmsAWS;
if (region) {
this.kmsAWS.region = region;
}
if (endpoint) {
this.kmsAWS.endpoint = endpoint;
}
/* Configure credentials.
Currently only support AK+SK authentication, both must be supplied.
*/
if (ak && sk) {
this.kmsAWS.ak = ak;
this.kmsAWS.sk = sk;
}

if (tls) {
this.kmsAWS.tls = {};
if (tls.rejectUnauthorized !== undefined) {
assert(typeof tls.rejectUnauthorized === 'boolean');
this.kmsAWS.tls.rejectUnauthorized = tls.rejectUnauthorized;
}
// min & max TLS: One of 'TLSv1.3', 'TLSv1.2', 'TLSv1.1', or 'TLSv1'
// (see https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions)
if (tls.minVersion !== undefined) {
assert(typeof tls.minVersion === 'string',
'bad config: KMS AWS TLS minVersion must be a string');
this.kmsAWS.tls.minVersion = tls.minVersion;
}
if (tls.maxVersion !== undefined) {
assert(typeof tls.maxVersion === 'string',
'bad config: KMS AWS TLS maxVersion must be a string');
this.kmsAWS.tls.maxVersion = tls.maxVersion;
}
if (tls.ca !== undefined) {
this.kmsAWS.tls.ca = this._loadTlsFileArray(tls.ca);
}
if (tls.cert !== undefined) {
this.kmsAWS.tls.cert = this._loadTlsFileArray(tls.cert);
}
if (tls.key !== undefined) {
this.kmsAWS.tls.key = this._loadTlsFileArray(tls.key);
}
}
}

this.healthChecks = defaultHealthChecks;
if (config.healthChecks && config.healthChecks.allowFrom) {
assert(config.healthChecks.allowFrom instanceof Array,
Expand Down
81 changes: 50 additions & 31 deletions lib/kms/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const logger = require('../utilities/logger');
const inMemory = require('./in_memory/backend').backend;
const file = require('./file/backend');
const KMIPClient = require('arsenal').network.kmipClient;
const AWSClient = require('arsenal').network.awsClient;
const Common = require('./common');
let scalityKMS;
let scalityKMSImpl;
Expand Down Expand Up @@ -42,6 +43,10 @@ if (config.backends.kms === 'mem') {
}
client = new KMIPClient(kmipConfig);
implName = 'kmip';
} else if (config.backends.kms === 'aws') {
const awsConfig = { kmsAWS: config.kmsAWS };
client = new AWSClient(awsConfig);
implName = 'aws';
} else {
throw new Error('KMS backend is not configured');
}
Expand Down Expand Up @@ -131,19 +136,6 @@ class KMS {
});
}

/**
*
* @param {object} log - logger object
* @returns {buffer} newKey - a data key
*/
static createDataKey(log) {
log.debug('creating a new data key');
const newKey = Common.createDataKey();
log.trace('data key created by the kms');
return newKey;
}


/**
* createCipherBundle
* @param {object} serverSideEncryptionInfo - info for encryption
Expand All @@ -162,8 +154,6 @@ class KMS {
*/
static createCipherBundle(serverSideEncryptionInfo,
log, cb) {
const dataKey = this.createDataKey(log);

const { algorithm, configuredMasterKeyId, masterKeyId: bucketMasterKeyId } = serverSideEncryptionInfo;

let masterKeyId = bucketMasterKeyId;
Expand All @@ -181,27 +171,56 @@ class KMS {
};

async.waterfall([
function cipherDataKey(next) {
log.debug('ciphering a data key');
return client.cipherDataKey(cipherBundle.cryptoScheme,
cipherBundle.masterKeyId,
dataKey, log, (err, cipheredDataKey) => {
if (err) {
log.debug('error from kms',
{ implName, error: err });
return next(err);
}
log.trace('data key ciphered by the kms');
return next(null, cipheredDataKey);
});
function generateDataKey(next) {
/* There are 2 ways of generating a datakey :
- using the generateDataKey of the KMS backend if it exists
(currently only implemented for the AWS KMS backend). This is
the prefered solution since a dedicated KMS should offer a better
entropy for generating random content.
- using local random number generation, and then use the KMS to
encrypt the datakey. This method is used when the KMS backend doesn't
provide the generateDataKey method.
*/
let res;
if (client.generateDataKey) {
log.debug('creating a data key using the KMS');
res = client.generateDataKey(cipherBundle.cryptoScheme,
cipherBundle.masterKeyId,
log, (err, plainTextDataKey, cipheredDataKey) => {
if (err) {
log.debug('error from kms',
{ implName, error: err });
return next(err);
}
log.trace('data key generated by the kms');
return next(null, plainTextDataKey, cipheredDataKey);
});
} else {
log.debug('creating a new data key');
const dataKey = Common.createDataKey();

log.debug('ciphering the data key');
res = client.cipherDataKey(cipherBundle.cryptoScheme,
cipherBundle.masterKeyId,
dataKey, log, (err, cipheredDataKey) => {
if (err) {
log.debug('error from kms',
{ implName, error: err });
return next(err);
}
log.trace('data key ciphered by the kms');
return next(null, dataKey, cipheredDataKey);
});
}
return res;
},
function createCipher(cipheredDataKey, next) {
function createCipher(plainTextDataKey, cipheredDataKey, next) {
log.debug('creating a cipher');
cipherBundle.cipheredDataKey =
cipheredDataKey.toString('base64');
return Common.createCipher(cipherBundle.cryptoScheme,
dataKey, 0, log, (err, cipher) => {
dataKey.fill(0);
plainTextDataKey, 0, log, (err, cipher) => {
plainTextDataKey.fill(0);
if (err) {
log.debug('error from kms',
{ implName, error: err });
Expand Down