Unable to create Client-Side Field Level Encryption enabled connection client with ATLAS in NodeJS

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",
              },
            },
          },
        },
      };
    }
  },
};
1 Like

The issue here was my mongocryptd process wasn’t running in the background. The issue resolved after I have installed the libmongocrypt library and mongocryptd binary along with mongodb-client-encryption NPM package.

3 Likes

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.