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
相关产品推荐
相关产品推荐

