Not encrypting data with Mongoose + CSFLE

I am new to CSFLE and trying to use it with a mongoose based project, but the resulting data is not being encrypted, but I am not sure why. Can anyone help here?

My code:

src/index.ts

import mongoose from 'mongoose';
import crypto from 'crypto';
import { ClientEncryption } from 'mongodb';
import { createModel } from './models/Person';

const dbUri = 'mongodb://127.0.0.1:27017/mytestdb';
let masterKey = '';

function initEncrytion (masterKey: Buffer | string) {
  const keyVaultNamespace = 'client.encryption';
  const kmsProviders = { local: { key: masterKey } };

  return {
    keyVaultNamespace,
    kmsProviders
  };
}

async function initDatabase () {
  const connection = await mongoose.connect(dbUri, {
    autoEncryption: initEncrytion(masterKey)
  });

  return connection;
}

function generateLocalMasterKey () {
  return crypto.randomBytes(96);
}

async function generateEncryptionKey () {
  const connection = await mongoose.connect(dbUri, {});
  const client = connection.connection.getClient();

  const localMasterKey = generateLocalMasterKey().toString('base64');
  const { keyVaultNamespace, kmsProviders } = initEncrytion(localMasterKey);
  const encryption = new ClientEncryption(client, { keyVaultNamespace, kmsProviders });
  const dekId = await encryption.createDataKey('local');

  console.log('Master Key', localMasterKey.length);
  console.log('Data Encryption Key ID:', dekId.toString('base64'));

  return {
    dekId, localMasterKey
  }
}

async function main () {
  // For now just lazily create a key every launch, since we are just testing things
  const { dekId, localMasterKey } = await generateEncryptionKey();
  masterKey = localMasterKey;
  const connection = await initDatabase ();

  const Person = createModel(dekId, connection);
  await Person.create({
    firstName: 'Bobby',
    lastName: 'Drop Tables',
    notes: 'Hello World'
  })
  process.exit(0);
}

main().catch(error => console.error(error));

src/models/Person.ts

import mongoose, { Mongoose, Document } from 'mongoose';
import IPerson from '../interfaces/IPerson.js';
import { UUID } from 'mongodb';

interface IPersonDB extends IPerson, Document { }

function createModel (keyId: UUID, connection: Mongoose) {
  console.log('>> keyId:', keyId);
  const PersonSchema = new mongoose.Schema(
    {
      resourceId: {
        type: String
      },
      preferredName: {
        type: String
      },
      firstName: {
        type: String
      },
      initial: {
        type: String
      },
      lastName: {
        type: String
      },
      notes: {
        type: String,
        encrypt: {
          keyId: keyId
        }
      }
    },
    {
      timestamps: true,
      encryptionType: 'queryableEncryption'
    }
  );

  return connection.model<IPersonDB>('Person', PersonSchema);
}

export { createModel };

@Andre-John_Mas1 Could you share which versions of the Node.js driver (mongodb) and mongoose packages you are using, respectively?

2 Likes

@Andre-John_Mas1 just as an FYI Mongoose recently rolled out automatic encryption (CSFLE or QE) support in 8.15.0. I’m assuming you’re using at least this version (based on the model you shared), but it’s worth confirming just to be sure :slight_smile:

Hi @Andre-John_Mas1 , when using encryption with Mongoose, encrypted models must be registered on a connection before the connection is established (see Mongoose CSFLE documentation). In your example, initDatabase is called before the model is registered.

Package versions:

  • mongodb: 6.16.0
  • mongoose: 8.15.1

Based on @Bailey_Pearson ’ s suggestion, I changed the code such that:

async function initDatabase () {
  const { dekId, localMasterKey } = await generateEncryptionKey();
  masterKey = localMasterKey;
  const connection = mongoose.createConnection();
  Person = createModel(dekId, connection);
  const autoEncryptionParams = initEncrytion(masterKey);
  console.log('autoEncryption', JSON.stringify(autoEncryptionParams, undefined, 2));

  return connection.openUri(dbUri, {
    autoEncryption: autoEncryptionParams
  });
}

// ... skipping the rest of the code

async function main () {
  await initDatabase ();

  await Person.create({
    firstName: 'Bobby',
    lastName: 'Drop Tables',
    notes: 'Hello World'
  })
  process.exit(0);
}

main().catch(error => console.error(error));

This seems a little better, though now it complains about missing mongocryptd to make all this work. From what I can see this is only available as part of the enterprise offering. If that is indeed required, then I’m tempted to explore manual encryption with symetrical encryption.

Edit: I see that we should ideally be using “Automatic Encryption Shared Library” now, but that too seems to be an enterprise thing?

I see that we should ideally be using “Automatic Encryption Shared Library” now, but that too seems to be an enterprise thing?

You can use it with Enterprise-licensed MongoDB servers or with MongoDB Atlas, or for local testing. If your cluster is running on MongoDB Atlas, you can go ahead and use the shared library (or mongocryptd if you want, but there’s really no good reason to use that anymore).

But yes, if you run a self-hosted MongoDB community deployment as your cluster, automatic encryption is unfortunately not something that is available for it.

We do deploy to MongoDB cloud (is this different from Atlas?), but I have been developing locally on my machine, so I’ll look into testing this with a sandbox instance.

I am not tied to mongocryptd in any way and using AESL is fine. Encryption is for a new service in our server, so there is no encryption at this level at this point.

@Andre-John_Mas1 If your hostname ends with .mongodb.net, then it is MongoDB Atlas and you can freely use AESL.