I’ve been facing an issue in creating CSFLE enabled client with MongoDB ATLAS Cluster. I’m following the official Client-Side Field Level Encryption Guide. I’ve directly checked out the csfle-guides/nodejs github repository and followed the directions under the README. The only thing I changed in the code was to add the MongoDB ATLAS Connection URL as the database connection string from (helpers.js/ Line: 15).
The regularClient connection works fine with ATLAS without any issue. I have even created the Key Vault and the Data Key and stored it on ATLAS using the regularClient connection. But when trying to create a CSFLE Enabled Client connection the program fails with the following error,
{ MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27020
at Timeout.waitQueueMember.timer.setTimeout [as _onTimeout] (/Users/ravindu/Documents/Private Projects/csfle-guides/nodejs/node_modules/mongodb/lib/core/sdam/topology.js:438:30)
at ontimeout (timers.js:498:11)
at tryOnTimeout (timers.js:323:5)
at Timer.listOnTimeout (timers.js:290:5)
name: 'MongoServerSelectionError',
reason:
TopologyDescription {
type: 'Unknown',
setName: null,
maxSetVersion: null,
maxElectionId: null,
servers: Map { 'localhost:27020' => [Object] },
stale: false,
compatible: true,
compatibilityError: null,
logicalSessionTimeoutMinutes: null,
heartbeatFrequencyMS: 10000,
localThresholdMS: 15,
commonWireVersion: null } }
Though the connection URL is set to ATLAS it tries to connect to a localhost node, even after changing the connection URL.
MongoDB ATLAS Cluster Version: 4.4.3
My helper.js file (Only change is in line 15 - Connection String)
const mongodb = require("mongodb");
const { ClientEncryption } = require("mongodb-client-encryption");
const { MongoClient, Binary } = mongodb;
module.exports = {
CsfleHelper: class {
constructor({
provider = null,
kmsProviders = null,
masterKey = null,
keyAltNames = "demo-data-key",
keyDB = "encryption",
keyColl = "__keyVault",
schema = null,
connectionString = "mongodb+srv://my_username:my_password@clfle-test-proj-1.zkvwq.mongodb.net/test?retryWrites=true&w=majority",
mongocryptdBypassSpawn = false,
mongocryptdSpawnPath = "mongocryptd",
} = {}) {
if (kmsProviders === null) {
throw new Error("kmsProviders is required");
}
if (provider === null) {
throw new Error("provider is required");
}
if (provider !== "local" && masterKey === null) {
throw new Error("masterKey is required");
}
this.kmsProviders = kmsProviders;
this.masterKey = masterKey;
this.provider = provider;
this.keyAltNames = keyAltNames;
this.keyDB = keyDB;
this.keyColl = keyColl;
this.keyVaultNamespace = `${keyDB}.${keyColl}`;
this.schema = schema;
this.connectionString = connectionString;
this.mongocryptdBypassSpawn = mongocryptdBypassSpawn;
this.mongocryptdSpawnPath = mongocryptdSpawnPath;
this.regularClient = null;
this.csfleClient = null;
}
/**
* Creates a unique, partial index in the key vault collection
* on the ``keyAltNames`` field.
*
* @param {MongoClient} client
*/
async ensureUniqueIndexOnKeyVault(client) {
try {
await client
.db(this.keyDB)
.collection(this.keyColl)
.createIndex("keyAltNames", {
unique: true,
partialFilterExpression: {
keyAltNames: {
$exists: true,
},
},
});
} catch (e) {
throw new Error(e);
}
}
/**
* In the guide, https://docs.mongodb.com/ecosystem/use-cases/client-side-field-level-encryption-guide/,
* we create the data key and then show that it is created by
* retreiving it using a findOne query. Here, in implementation, we only
* create the key if it doesn't already exist, ensuring we only have one
* local data key.
*
* @param {MongoClient} client
*/
async findOrCreateDataKey(client) {
const encryption = new ClientEncryption(client, {
keyVaultNamespace: this.keyVaultNamespace,
kmsProviders: this.kmsProviders,
});
await this.ensureUniqueIndexOnKeyVault(client);
let dataKey = await client
.db(this.keyDB)
.collection(this.keyColl)
.findOne({ keyAltNames: { $in: [this.keyAltNames] } });
if (dataKey === null) {
dataKey = await encryption.createDataKey(this.provider, {
masterKey: this.masterKey,
});
return dataKey.toString("base64");
}
return dataKey["_id"].toString("base64");
}
async getRegularClient() {
const client = new MongoClient(this.connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
return await client.connect();
}
async getCsfleEnabledClient(schemaMap = null) {
if (schemaMap === null) {
throw new Error(
"schemaMap is a required argument. Build it using the CsfleHelper.createJsonSchemaMap method"
);
}
const client = new MongoClient(this.connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
monitorCommands: true,
autoEncryption: {
keyVaultNamespace: this.keyVaultNamespace,
kmsProviders: this.kmsProviders,
schemaMap,
},
});
return await client.connect();
}
createJsonSchemaMap(dataKey) {
return {
"medicalRecords.patients": {
bsonType: "object",
encryptMetadata: {
keyId: [new Binary(Buffer.from(dataKey, "base64"), 4)],
},
properties: {
insurance: {
bsonType: "object",
properties: {
policyNumber: {
encrypt: {
bsonType: "int",
algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
},
},
},
},
medicalRecords: {
encrypt: {
bsonType: "array",
algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Random",
},
},
bloodType: {
encrypt: {
bsonType: "string",
algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Random",
},
},
ssn: {
encrypt: {
bsonType: "int",
algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
},
},
},
},
};
}
},
};