From aec6790f4dfd184b6f6d9743746ec721b67b7dca Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 19 Apr 2022 15:55:33 +0200 Subject: [PATCH 1/2] feat(NODE-4136): adjust Node.js bindings for shared library spec Specifically, pass a default `csfleSearchPaths` list of `['$SYSTEM']`, and add support for `csfleRequired`. As drive-by fixes, fix a typo that currently (unintentionally) leads to `_mongocryptdClient` and `_mongocryptdManager` being initialized even if `bypassAutoEncryption` was specified, and adjust the tests and the `.teardown()` method for those cases. --- bindings/node/lib/autoEncrypter.js | 15 +++++++++-- bindings/node/test/autoEncrypter.test.js | 33 +++++++++++++++++++++--- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/bindings/node/lib/autoEncrypter.js b/bindings/node/lib/autoEncrypter.js index f827109c7..9f36ef800 100644 --- a/bindings/node/lib/autoEncrypter.js +++ b/bindings/node/lib/autoEncrypter.js @@ -125,16 +125,23 @@ module.exports = function (modules) { } if (options.extraOptions && options.extraOptions.csfleSearchPaths) { + // Only for driver testing mongoCryptOptions.csfleSearchPaths = options.extraOptions.csfleSearchPaths; + } else if (!this._bypassEncryption) { + mongoCryptOptions.csfleSearchPaths = ['$SYSTEM']; } Object.assign(mongoCryptOptions, { cryptoCallbacks }); this._mongocrypt = new mc.MongoCrypt(mongoCryptOptions); this._contextCounter = 0; + if (options.extraOptions && options.extraOptions.csfleRequired && !this.csfleVersionInfo) { + throw new MongoError('`csfleRequired` set but no csfle shared library loaded'); + } + // Only instantiate mongocryptd manager/client once we know for sure // that we are not using the CSFLE shared library. - if (!this._bypassAutoEncryption && !this.csfleVersionInfo) { + if (!this._bypassEncryption && !this.csfleVersionInfo) { this._mongocryptdManager = new MongocryptdManager(options.extraOptions); this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, { useNewUrlParser: true, @@ -181,7 +188,11 @@ module.exports = function (modules) { * @param {Function} callback Invoked when the mongocryptd client either successfully disconnects or errors */ teardown(force, callback) { - this._mongocryptdClient.close(force, callback); + if (this._mongocryptdClient) { + this._mongocryptdClient.close(force, callback); + } else { + callback(); + } } /** diff --git a/bindings/node/test/autoEncrypter.test.js b/bindings/node/test/autoEncrypter.test.js index 9710eb1f8..3713628fc 100644 --- a/bindings/node/test/autoEncrypter.test.js +++ b/bindings/node/test/autoEncrypter.test.js @@ -585,7 +585,7 @@ describe('AutoEncrypter', function () { const client = new MockClient(); this.mc = new AutoEncrypter(client, encryptionOptions); - const localMcdm = this.mc._mongocryptdManager; + const localMcdm = this.mc._mongocryptdManager || { spawn: () => {} }; sandbox.spy(localMcdm, 'spawn'); this.mc.init(err => { @@ -640,7 +640,30 @@ describe('AutoEncrypter', function () { }); describe('CSFLE shared library', function () { - it('should load a shared library by specifying its path', function () { + it('should fail if no library can be found in the search path and csfleRequired is set', function () { + // NB: This test has to be run before the tests/without having previously + // loaded a CSFLE shared library below to get the right error path. + const client = new MockClient(); + try { + new AutoEncrypter(client, { + keyVaultNamespace: 'admin.datakeys', + logger: () => {}, + kmsProviders: { + aws: { accessKeyId: 'example', secretAccessKey: 'example' }, + local: { key: Buffer.alloc(96) } + }, + extraOptions: { + csfleSearchPaths: ['/nonexistent'], + csfleRequired: true + } + }); + expect.fail('missed exception'); + } catch (err) { + expect(err.message).to.include('`csfleRequired` set but no csfle shared library loaded'); + } + }); + + it('should load a shared library by specifying its path', function (done) { const client = new MockClient(); this.mc = new AutoEncrypter(client, { keyVaultNamespace: 'admin.datakeys', @@ -661,9 +684,11 @@ describe('AutoEncrypter', function () { version: BigInt(0x000600020001000), versionStr: 'stubbed-mongo_csfle' }); + + this.mc.teardown(true, done); }); - it('should load a shared library by specifying a search path', function () { + it('should load a shared library by specifying a search path', function (done) { const client = new MockClient(); this.mc = new AutoEncrypter(client, { keyVaultNamespace: 'admin.datakeys', @@ -684,6 +709,8 @@ describe('AutoEncrypter', function () { version: BigInt(0x000600020001000), versionStr: 'stubbed-mongo_csfle' }); + + this.mc.teardown(true, done); }); }); }); From 95984c3a62d4126b46c1232fb6a5dfc4acbf223c Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Thu, 21 Apr 2022 17:08:00 +0200 Subject: [PATCH 2/2] fixup: add bindings for bypassQueryAnalysis and encryptedFieldsMap --- bindings/node/lib/autoEncrypter.js | 10 ++++++++++ bindings/node/src/mongocrypt.cc | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/bindings/node/lib/autoEncrypter.js b/bindings/node/lib/autoEncrypter.js index 9f36ef800..5dbce92e7 100644 --- a/bindings/node/lib/autoEncrypter.js +++ b/bindings/node/lib/autoEncrypter.js @@ -108,6 +108,12 @@ module.exports = function (modules) { : this._bson.serialize(options.schemaMap); } + if (options.encryptedFieldsMap) { + mongoCryptOptions.encryptedFieldsMap = Buffer.isBuffer(options.encryptedFieldsMap) + ? options.encryptedFieldsMap + : this._bson.serialize(options.encryptedFieldsMap); + } + if (options.kmsProviders) { mongoCryptOptions.kmsProviders = !Buffer.isBuffer(options.kmsProviders) ? this._bson.serialize(options.kmsProviders) @@ -131,6 +137,10 @@ module.exports = function (modules) { mongoCryptOptions.csfleSearchPaths = ['$SYSTEM']; } + if (options.bypassQueryAnalysis) { + mongoCryptOptions.bypassQueryAnalysis = options.bypassQueryAnalysis; + } + Object.assign(mongoCryptOptions, { cryptoCallbacks }); this._mongocrypt = new mc.MongoCrypt(mongoCryptOptions); this._contextCounter = 0; diff --git a/bindings/node/src/mongocrypt.cc b/bindings/node/src/mongocrypt.cc index caa8681a5..d614ac941 100644 --- a/bindings/node/src/mongocrypt.cc +++ b/bindings/node/src/mongocrypt.cc @@ -395,6 +395,20 @@ MongoCrypt::MongoCrypt(const CallbackInfo& info) } } + if (options.Has("encryptedFieldsMap")) { + Napi::Value encryptedFieldsMapBuffer = options["encryptedFieldsMap"]; + + if (!encryptedFieldsMapBuffer.IsBuffer()) { + throw TypeError::New(Env(), "Option `encryptedFieldsMap` must be a Buffer"); + } + + std::unique_ptr encryptedFieldsMapBinary( + BufferToBinary(encryptedFieldsMapBuffer.As())); + if (!mongocrypt_setopt_encrypted_field_config_map(_mongo_crypt.get(), encryptedFieldsMapBinary.get())) { + throw TypeError::New(Env(), errorStringFromStatus(_mongo_crypt.get())); + } + } + if (options.Has("logger")) { SetCallback("logger", options["logger"]); if (!mongocrypt_setopt_log_handler( @@ -438,6 +452,10 @@ MongoCrypt::MongoCrypt(const CallbackInfo& info) options.Get("csflePath").ToString().Utf8Value().c_str()); } + if (options.Get("bypassQueryAnalysis").ToBoolean()) { + mongocrypt_setopt_bypass_query_analysis(_mongo_crypt.get()); + } + mongocrypt_setopt_use_need_kms_credentials_state(_mongo_crypt.get()); // Initialize after all options are set.