diff --git a/lib/Config.js b/lib/Config.js index 6e1e853889..6660a6a50d 100644 --- a/lib/Config.js +++ b/lib/Config.js @@ -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); @@ -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" @@ -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, diff --git a/lib/kms/wrapper.js b/lib/kms/wrapper.js index 4a927f9d94..b081715798 100644 --- a/lib/kms/wrapper.js +++ b/lib/kms/wrapper.js @@ -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; @@ -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'); } @@ -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 @@ -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; @@ -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 });