O MongoDB 7.0 introduz o Queryable Encryption para cenários que exigem maior segurança de banco de dados.
Informações básicas
Os recursos de criptografia transparente de dados (TDE) e criptografia de disco do ApsaraDB for MongoDB são soluções de criptografia em repouso. Eles atendem às seguintes finalidades:
Proteção de dados: protege os dados nos discos contra acesso não autorizado. Mesmo que um usuário mal-intencionado tenha acesso físico aos HDDs ou SSDs onde os dados estão armazenados, ele não consegue acessar os dados não criptografados.
Prevenção de vazamentos: se um dispositivo de armazenamento for roubado ou perdido, a criptografia impede que usuários não autorizados acessem dados confidenciais.
Requisitos de conformidade: diversos padrões e regulamentos do setor exigem que as empresas criptografem dados sensíveis, como dados pessoais de usuários e informações financeiras. A criptografia em repouso ajuda as empresas a cumprir essas exigências regulatórias.
Os arquivos de backup de uma instância do ApsaraDB for MongoDB com TDE ou criptografia de disco ativada são criptografados.
Com a criptografia em repouso, os dados carregados na memória permanecem em texto simples. Para proteger totalmente seus dados, recomendamos implementar medidas de segurança adicionais, como criptografia de rede (SSL/TLS), controle de acesso ao banco de dados, auditoria e monitoramento. Para evitar riscos de segurança decorrentes de acesso interno de O&M às instâncias do Elastic Compute Service (ECS) que hospedam seus serviços de banco de dados, a Alibaba Cloud fornece autorização do cliente e auditoria obrigatória.
Caso necessite de proteção adicional além da criptografia em repouso, utilize o recurso Queryable Encryption lançado oficialmente no MongoDB 7.0.
Introdução
O Queryable Encryption foi disponibilizado como prévia no MongoDB 6.0 e lançado oficialmente no MongoDB 7.0.
O Queryable Encryption mantém os dados criptografados até que cheguem ao cliente. As consultas são enviadas ao servidor junto com a chave de criptografia gerenciada pelo Key Management Service (KMS). O servidor consulta e retorna os dados em texto cifrado. Após o retorno ao cliente, os dados são descriptografados com a chave e exibidos em texto simples.
O Queryable Encryption oferece as seguintes capacidades:
Criptografa dados sensíveis no lado do cliente. Somente o cliente pode obter a chave de criptografia.
Mantém a criptografia dos dados durante todo o ciclo de vida, incluindo transmissão, armazenamento, uso, auditoria e backup.
Suporta consultas expressivas em dados criptografados, incluindo consultas de igualdade, intervalo, prefixo, sufixo e substring.
Reforça a privacidade dos dados. Apenas usuários autorizados com acesso à aplicação no servidor e às chaves de criptografia podem visualizar os dados em texto simples.
Simplifica o desenvolvimento de aplicações. Os desenvolvedores podem usar as capacidades nativas de criptografia do banco de dados para garantir segurança e conformidade.
Reduz preocupações com segurança ao armazenar dados sensíveis no ApsaraDB for MongoDB.
O MongoDB Community Edition não suporta criptografia automática, o que o diferencia da Enterprise Edition (Atlas).
Para mais informações sobre versões de drivers e bancos de dados criptografados, consulte Compatibilidade do Queryable Encryption.
Limites
-
Comandos de diagnóstico e logs de consulta em uma coleção criptografada são redigidos ou ocultados, o que limita a análise de problemas:
Comandos aplicáveis a uma coleção criptografada, como
aggregate, count, find, insert, update, and delete, não são registrados em logs de consulta lenta nem em profilers.Os resultados de comandos de diagnóstico, como
collStats, currentOp, top, or $planCacheStats, são redigidos e alguns campos ficam ocultos.
A contenção entre campos criptografados pode aumentar a latência de escrita. O fator de contenção padrão é 8.
Coleções de metadados que excedem 1 GB devem ser compactadas manualmente. Para mais detalhes, consulte Compactação de coleção de metadados.
O objeto
encryptedFieldsMapnão pode ser alterado, incluindo os campos de consulta nele contidos.O Queryable Encryption é suportado apenas por instâncias de replica set ou sharded cluster.
Não é possível ler dados criptografados em nós secundários.
Documentos não podem ser atualizados em lote com o comando
updateMany or bulkWrite, e os parâmetros no comandofindAndModifysão limitados.A operação upsert não é suportada. Quando o upsert é acionado, campos criptografados não são inseridos.
A Criptografia no Nível de Campo do Lado do Cliente (CSFLE) e o Queryable Encryption não podem ser ativados na mesma coleção. Uma coleção com CSFLE ativado ou uma coleção não criptografada não pode ser convertida para uma coleção com Queryable Encryption.
O Queryable Encryption só pode ser ativado em novas coleções vazias.
Uma coleção que contém campos criptografados não pode ser renomeada, e os campos não podem ser renomeados com o comando
$rename.Se
jsonSchemafor especificado durante a criação de uma coleção criptografada, a palavra-chaveencryptnão poderá ser incluída.Views, coleções de séries temporais e coleções com limite de tamanho (capped collections) não são suportadas.
Índices TTL ou índices únicos não são suportados.
Não é possível desativar o
jsonSchema.Exclua a coleção usando um MongoClient com Queryable Encryption ativado. Caso contrário, os metadados permanecerão no servidor.
O Queryable Encryption não suporta collation. A collation bloqueia a ordenação normal de campos criptografados.
O campo
_idnão pode ser especificado como um campo criptografado.Apenas um número limitado de comandos e operadores é suportado. Para mais informações, consulte Operações suportadas para Queryable Encryption.
Preparativos
Este guia utiliza uma instância ECS como cliente para demonstrar o procedimento. Se o seu ambiente de teste já possuir as dependências necessárias, ignore as etapas correspondentes. Este guia usa o driver Node.js para a demonstração porque o mongosh suporta apenas criptografia automática, enquanto o MongoDB Community Edition suporta apenas criptografia explícita.
-
Instale o Node.js e o npm.
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash - sudo yum install nodejs node -v npm -v -
Instale o driver oficial do Node.js para MongoDB.
mkdir node_quickstart cd node_quickstart npm init -y npm install mongodb@6.6 -
Instale a biblioteca libmongocrypt.
vi /etc/yum.repos.d/libmongocrypt.repo // Add the following content to the file: [libmongocrypt] name=libmongocrypt repository baseurl=https://libmongocrypt.s3.amazonaws.com/yum/redhat/8/libmongocrypt/1.8/x86_64 gpgcheck=1 enabled=1 gpgkey=https://pgp.mongodb.com/libmongocrypt.asc // Install the library sudo yum install -y libmongocrypt -
Instale o pacote mongodb-client-encryption, do qual o driver Node.js depende.
sudo yum groupinstall 'Development Tools' npm install mongodb-client-encryption -
Instale o mongosh e configure a variável de ambiente MONGODB_URI.
wget https://repo.mongodb.org/yum/redhat/8/mongodb-org/7.0/x86_64/RPMS/mongodb-mongosh-2.2.5.x86_64.rpm yum install -y ./mongodb-mongosh-2.2.5.x86_64.rpm export MONGODB_URI="mongodb://root:xxxxxx@dds-2zef23cef14b4f142.mongodb.pre.rds.aliyuncs.com:3717,dds-2zef23cef14b4f141.mongodb.pre.rds.aliyuncs.com:3717/admin?replicaSet=mgset-855706" // Test the connection mongosh ${MONGODB_URI} -
Obtenha a biblioteca compartilhada de criptografia automática.
No Download Center, selecione o cliente correspondente à sua máquina e versão de distribuição e escolha o pacote crypt_shared. Para mais informações, consulte Download do MongoDB Enterprise Server.
// Decompress the local directory to obtain the lib/mongo_crypt_v1.so file. tar -xzvf mongo_crypt_shared_v1-linux-x86_64-enterprise-rhel80-7.0.9.tgz
Procedimento
O MongoDB Community Edition não suporta criptografia automática. Portanto, este artigo demonstra o processo de criptografia explícita.
Acesse o ambiente REPL do Node.js e execute as etapas a seguir:
node -i -e "const MongoClient = require('mongodb').MongoClient; const ClientEncryption = require('mongodb').ClientEncryption;"
-
Crie uma chave mestra do cliente (CMK).
NotaO exemplo a seguir mostra as configurações de amostra de um provedor KMS local. Recomendamos não usar essas configurações em ambiente de produção.
Gere uma CMK de 96 bytes e armazene-a no arquivo
customer-master-key.txtdo sistema de arquivos local.const fs = require("fs"); const crypto = require("crypto"); try { fs.writeFileSync("customer-master-key.txt", crypto.randomBytes(96)); } catch (err) { console.error(err); }Este exemplo gera a CMK usando uma chamada crypto do Node.js. Alternativamente, gere a CMK de 96 bytes em um shell usando
/dev/urandom:echo $(head -c 96 /dev/urandom | base64 | tr -d '\n') -
Inicialize as variáveis.
// KMS provider name should be one of the following: "aws", "gcp", "azure", "kmip" or "local" const kmsProviderName = "local"; const uri = process.env.MONGODB_URI; const keyVaultDatabaseName = "encryption"; const keyVaultCollectionName = "__keyVault"; const keyVaultNamespace = "encryption.__keyVault"; const encryptedDatabaseName = "medicalRecords"; const encryptedCollectionName = "patients";No código de exemplo anterior, as seguintes variáveis são inicializadas:
kmsProviderName: o nome do provedor KMS. Neste exemplo, usa-selocal.uri: a URI do MongoDB. É possível definir a URI do MongoDB pela variável de ambienteMONGODB_URIou especificá-la diretamente.keyVaultDatabaseName: o nome do banco de dados que armazena as chaves de criptografia de dados (DEKs).keyVaultCollectionName: o nome da coleção que armazena as DEKs. Esta coleção deve ser diferente de uma coleção regular.keyVaultNamespace: equivale à variávelkeyVaultDatabaseNameoukeyVaultCollectionName.encryptedDatabaseName: o nome do banco de dados que armazena dados criptografados.encryptedCollectionName: o nome da coleção que armazena dados criptografados.
-
Crie um índice único na coleção que armazena as DEKs.
const keyVaultClient = new MongoClient(uri); await keyVaultClient.connect(); const keyVaultDB = keyVaultClient.db(keyVaultDatabaseName); // Delete the database with the same name as the database that stores DEKs to prevent excess data. await keyVaultDB.dropDatabase(); const keyVaultColl = keyVaultDB.collection(keyVaultCollectionName); await keyVaultColl.createIndex( { keyAltNames: 1 }, { unique: true, partialFilterExpression: { keyAltNames: { $exists: true } }, } ); // double check await keyVaultColl.indexes(); -
Crie uma coleção criptografada.
-
Obtenha a CMK criada e especifique o provedor KMS.
const localMasterKey = fs.readFileSync("./customer-master-key.txt"); kmsProviders = {local: {key: localMasterKey}}; -
Crie uma DEK.
NotaAntes de executar esta etapa, certifique-se de que o usuário especificado na variável
uritenha a permissão dbAdmin nos bancos de dadosencryption._keyVaultemedicalRecords.const clientEnc = new ClientEncryption(keyVaultClient, { keyVaultNamespace: keyVaultNamespace, kmsProviders: kmsProviders, }); const dek1 = await clientEnc.createDataKey(kmsProviderName, { keyAltNames: ["dataKey1"], }); const dek2 = await clientEnc.createDataKey(kmsProviderName, { keyAltNames: ["dataKey2"], }); -
Especifique os campos a serem criptografados e configure a DEK criada.
const encryptedFieldsMap = { [`${encryptedDatabaseName}.${encryptedCollectionName}`]: { fields: [ { keyId: dek1, path: "patientId", bsonType: "int", queries: { queryType: "equality" }, }, { keyId: dek2, path: "medications", bsonType: "array", }, ], }, }; -
Especifique a biblioteca compartilhada de criptografia automática e crie um MongoClient.
const extraOptions = {cryptSharedLibPath: "/root/lib/mongo_crypt_v1.so"}; const encClient = new MongoClient(uri, { autoEncryption: { keyVaultNamespace, kmsProviders, extraOptions, encryptedFieldsMap, }, }); await encClient.connect(); -
Crie uma coleção criptografada.
const newEncDB = encClient.db(encryptedDatabaseName); await newEncDB.dropDatabase(); await newEncDB.createCollection(encryptedCollectionName);
-
-
Crie um MongoClient para criptografar operações de leitura e escrita.
-
Especifique a coleção que armazena a DEK criada.
const eDB = "encryption"; const eKV = "__keyVault"; const keyVaultNamespace = `${eDB}.${eKV}`; const secretDB = "medicalRecords"; const secretCollection = "patients"; -
Especifique a CMK criada.
ImportanteNão use o arquivo de chave local em ambiente de produção.
const fs = require("fs"); const path = "./customer-master-key.txt"; const localMasterKey = fs.readFileSync(path); const kmsProviders = { local: { key: localMasterKey, }, }; -
Obtenha a DEK criada.
NotaO nome da DEK deve ser idêntico ao nome da DEK criada na subetapa secundária da Etapa 4.
const uri = process.env.MONGODB_URI;; const unencryptedClient = new MongoClient(uri); await unencryptedClient.connect(); const keyVaultClient = unencryptedClient.db(eDB).collection(eKV); const dek1 = await keyVaultClient.findOne({ keyAltNames: "dataKey1" }); const dek2 = await keyVaultClient.findOne({ keyAltNames: "dataKey2" }); -
Especifique a biblioteca compartilhada de criptografia automática e crie um MongoClient.
const extraOptions = { cryptSharedLibPath: "/root/lib/mongo_crypt_v1.so", }; const encryptedClient = new MongoClient(uri, { autoEncryption: { kmsProviders: kmsProviders, keyVaultNamespace: keyVaultNamespace, bypassQueryAnalysis: true, keyVaultClient: unencryptedClient, extraOptions: extraOptions, }, }); await encryptedClient.connect(); -
Crie um objeto ClientEncryption.
const encryption = new ClientEncryption(unencryptedClient, { keyVaultNamespace, kmsProviders, });
-
-
Insira um documento contendo campos criptografados na coleção criptografada criada.
const patientId = 12345678; const medications = ["Atorvastatin", "Levothyroxine"]; const indexedInsertPayload = await encryption.encrypt(patientId, { algorithm: "Indexed", keyId: dek1._id, contentionFactor: 1, }); const unindexedInsertPayload = await encryption.encrypt(medications, { algorithm: "Unindexed", keyId: dek2._id, }); const encryptedColl = encryptedClient.db(secretDB).collection(secretCollection); await encryptedColl.insertOne({ firstName: "Jon", patientId: indexedInsertPayload, medications: unindexedInsertPayload, }); -
Execute uma consulta no nível de campo na coleção criptografada criada.
const findPayload = await encryption.encrypt(patientId, { algorithm: "Indexed", keyId: dek1._id, queryType: "equality", contentionFactor: 1, }); console.log(await encryptedColl.findOne({ patientId: findPayload }));O exemplo a seguir mostra o documento retornado.
> console.log(await encryptedColl.findOne({ patientId: findPayload })); { _id: new ObjectId('6645b56f58abf955ebd95caf'), firstName: 'Jon', patientId: 12345678, medications: [ 'Atorvastatin', 'Levothyroxine' ], __safeContent__: [ Binary.createFromBase64('IrPf972hlhDvnasQH6rIAW6BqERo0ZEgC6C0/zNQiIY=', 0) ] } -
Utilize o cliente que contém opções criptografadas para acessar campos criptografados. Caso contrário, o acesso a esses campos não será possível.
Use o cliente
unencryptedClient, que não possui criptografia, para a consulta no nível de campo.console.log(await unencryptedClient.db(secretDB).collection(secretCollection).findOne());O exemplo a seguir mostra o documento retornado. Os campos sensíveis aparecem como dados binários.
> console.log(await unencryptedClient.db(secretDB).collection(secretCollection).findOne()); { _id: new ObjectId('6645b56f58abf955ebd95caf'), firstName: 'Jon', patientId: Binary.createFromBase64('DtmNrEDyTEBDidZxWkbGU/MQdYNxwmnqYj5tSr9uhHbwWj8bsSD3TWlZ8aMMvw6FY00cmdc1QLLoEX3NwlKRhz0zax9LcQhN3vKUf4eq3hAfBYWkyOQxsiwbPsU0AiXnMV+qM6J2p2JZGLrDvxfbTY+obBmRqdvlgJ51dKmYopvDNToWBXDQkqAis9/3vaGWE0+dqxAfqsgBboGoRGjRkSALoI7/M1CIhvmpds5LR7/232uI4f5QDbk0JVfjnI0Doov6b0GrAXe9', 6), medications: Binary.createFromBase64('EKqAnwBkWUBon0Qf9sZHVIkEphUdfDK/aqYPs5M1Xc58CkojwX0kvC+KjwYyEozia41F5cnD9NFBwnVuDJUaqjTLc1YwG1DEIUZdcYCMf3JiureqA0voYP3gZxPyFmf/h1DS80Jz+g', 6), __safeContent__: [ Binary.createFromBase64('IrPf972hlhDvnasQH6rIAW6BqERo0ZEgC6CO/zNQiIY=', 0) ] }Também é possível usar o mongosh para acessar externamente os campos criptografados. Isso simula o acesso à coleção criptografada criada sem a chave do cliente.
// In a separate terminal session, connect directly to the MongoDB URI with mongosh. mongosh ${MONGODB_URI} db.getSiblingDB("medicalRecords").patients.findOne()O exemplo a seguir mostra o documento retornado.
mgset-855706 [primary] admin> db.getSiblingDB("medicalRecords").patients.findOne() { _id: ObjectId('6645b56f58abf955ebd95caf'), firstName: 'Jon', patientId: Binary.createFromBase64('DtmNrEDyTEBYDidZxWkbGU/MQdYNxwmnqYj5tSr9uhHbwWj8bsSD3TWlZ8aMMvw6fYO0cmdc1QLoEX3Nw1KRhz0zax9LcQhN3vKUf4eq3hAfBYWkyQxsiwbPsU0AiXnMV+qM6JZGLrDvxfbTY+obBmRqdvlJ51dKmYopvDNTowBXdQkqAis9/3vaGWEO+dqxAfqsgBboGoRGjRkSALoI7/M1CIhvmpds5LR7/232uI4f5QDbk0JVfjnIODoov6b0GrAXe9', 6), medications: Binary.createFromBase64('EKqAnwBkWUBon0Qf9sZHVIkEphUdfdk/aqYPs5M1Xc58CkojX0kvC+KjwYyEozia41F5cnD9NFBwnVuDJUaqjTLc1YwG1DEIUZdcYMf3liuregA0qvoYP3qZxPyFmf/h1IJz+g', 6), __safeContent__: [ Binary.createFromBase64('IrPf972hlhDvnasQH6rIAW6BqERo0ZEgC6CO/zNQiIY=', 0) ] }