You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mongoose未生效CSFLE配置问题求助

解决Mongoose结合CSFLE写入Mongo Atlas时数据明文存储的问题

核心问题分析

你的代码存在几个关键问题导致加密未生效:

  • 连接与Model定义顺序颠倒:Mongoose需要先建立带加密配置的连接,再基于该连接定义Model,否则Model无法继承加密规则。
  • DEK格式错误:Schema中encrypt.keyId需要传入Binary类型的UUID,而非base64字符串。
  • Schema冗余配置:encryptionType: 'csfle'不是Schema的合法配置项,应移除。

修正步骤

1. 调整连接初始化顺序

先调用db.openUri建立加密连接,再基于该连接创建Model。

2. 修正DEK的格式转换

将getOrCreateDEK返回的base64字符串转换为MongoDB的Binary类型(使用mongodb库的Binary工具)。

3. 移除Schema的无效配置

删除Schema选项中的encryptionType: 'csfle'。

4. 确保Crypt库路径正确

Lambda环境中要确认mongo_crypt_v1.so的实际路径,建议将库文件放在Lambda部署包的根目录或通过Lambda层部署。

修正后的完整代码

import { Handler } from 'aws-lambda';
import { ClientEncryption, KMSProviders, MongoClient, Binary } from 'mongodb';
import mongoose, { Schema } from 'mongoose';
import path from 'path';

const keyVaultDatabase = 'encryption';
const keyVaultCollection = '__keyVault';
const keyVaultNamespace = `${keyVaultDatabase}.${keyVaultCollection}`;
const UUID_SUBTYPE = 4; // MongoDB UUID子类型

export const handler: Handler = async () => {
  try {
    const dekBase64 = await getOrCreateDEK();
    // 将base64格式的DEK转换为Binary类型的UUID
    const dekUUID = new Binary(Buffer.from(dekBase64, 'base64'), UUID_SUBTYPE);

    // 先建立加密连接
    const connection = mongoose.createConnection();
    const db = connection.useDb(process.env.MONGO_DB_NAME!);
    await db.openUri(getMongoURI(), getEncryptedConnectionOptions());

    // 基于已建立的加密连接定义Schema和Model
    const encryptSchema = new Schema({
      name: String,
      ssn: {
        type: String,
        encrypt: {
          keyId: dekUUID,
          queries: 'equality',
          algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
        }
      },
      sequence: Number,
      requestingAgentCode: [String]
    });

    const encryptModel = db.model('work_requests', encryptSchema);

    await encryptModel.create({
      name: 'Alan',
      ssn: '12345',
      sequence: 1234567,
      requestingAgentCode: ['23413412']
    });

    return {
      statusCode: 201,
      body: JSON.stringify({ message: '数据加密并保存成功' })
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({
        message: '内部服务器错误',
        error: (error as Error)?.message
      })
    };
  }
};

function getMongoURI(): string {
  return `${process.env.MONGODB_CONNECTION_STRING!}/?authSource=%24external&authMechanism=MONGODB-AWS&retryWrites=true`;
}

function getKmsProviders(): KMSProviders {
  return {
    aws: {
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
      sessionToken: process.env.AWS_SESSION_TOKEN!
    }
  };
}

function getEncryptedConnectionOptions() {
  return {
    serverSelectionTimeoutMS: 5000,
    autoEncryption: {
      keyVaultNamespace: keyVaultNamespace,
      kmsProviders: getKmsProviders(),
      // 若使用Lambda层部署crypt库,路径改为'/opt/lib/mongo_crypt_v1.so'
      cryptSharedLibPath: path.resolve(__dirname, './lib/mongo_crypt_v1.so')
    }
  };
}

function getConnectionOptions() {
  return {
    serverSelectionTimeoutMS: 5000
  };
}

async function getOrCreateDEK(): Promise<string> {
  const keyAltName = 'ddbplus-csfle-dek';

  const keyVaultClient = new MongoClient(getMongoURI(), getConnectionOptions());
  await keyVaultClient.connect();

  const keyVaultDB = keyVaultClient.db(keyVaultDatabase);
  const keyVaultColl = keyVaultDB.collection(keyVaultCollection);

  const existingKey = await keyVaultColl.findOne({ keyAltNames: { $in: [keyAltName] } });
  if (existingKey) {
    await keyVaultClient.close();
    return existingKey._id.toString('base64');
  }

  await keyVaultColl.createIndex(
    { keyAltNames: 1 },
    {
      unique: true,
      partialFilterExpression: { keyAltNames: { $exists: true } }
    }
  );

  const encryption = new ClientEncryption(keyVaultClient, {
    keyVaultNamespace,
    kmsProviders: getKmsProviders()
  });

  const keyUUID = await encryption.createDataKey('aws', {
    masterKey: {
      region: process.env.AWS_KEY_REGION!,
      key: process.env.AWS_KEY_ARN!
    },
    keyAltNames: [keyAltName]
  });

  await keyVaultClient.close();
  return keyUUID.toString('base64');
}

额外注意事项

  • Lambda层部署Crypt库:将mongo_crypt_v1.so打包到AWS Lambda层中可避免部署包过大,此时路径需改为/opt/lib/mongo_crypt_v1.so。
  • 权限验证:确保Lambda执行角色拥有访问AWS KMS密钥的权限,以及Mongo Atlas的读写权限。
  • 连接关闭:在getOrCreateDEK中使用await keyVaultClient.close()确保连接正确关闭,避免资源泄漏。

内容的提问来源于stack exchange,提问作者Alan Thompson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 14:04:52