diff options
author | sergey.galtsev <sergey.galtsev@mongodb.com> | 2022-04-01 22:07:58 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-04-01 23:07:50 +0000 |
commit | eea86b56a99c0bde0c387ab45d42a4ac156e9750 (patch) | |
tree | 4494de09163b7fe7dd8675c5a6ddffe6d5c95d50 | |
parent | 9eaa9b280cf9c35d83507e3ca3a364721195f5d5 (diff) | |
download | mongo-eea86b56a99c0bde0c387ab45d42a4ac156e9750.tar.gz |
SERVER-63736 validate only fields listed in config are indexed
-rw-r--r-- | src/mongo/crypto/fle_crypto.cpp | 23 | ||||
-rw-r--r-- | src/mongo/crypto/fle_crypto.h | 5 | ||||
-rw-r--r-- | src/mongo/crypto/fle_crypto_test.cpp | 97 | ||||
-rw-r--r-- | src/mongo/db/fle_crud.cpp | 16 |
4 files changed, 95 insertions, 46 deletions
diff --git a/src/mongo/crypto/fle_crypto.cpp b/src/mongo/crypto/fle_crypto.cpp index 0dacb9c0751..9303bcbb61f 100644 --- a/src/mongo/crypto/fle_crypto.cpp +++ b/src/mongo/crypto/fle_crypto.cpp @@ -1990,10 +1990,29 @@ ESCDerivedFromDataTokenAndContentionFactorToken EDCServerPayloadInfo::getESCToke payload.getEscDerivedToken()); } +void EDCServerCollection::validateEncryptedFieldInfo(BSONObj& obj, + const EncryptedFieldConfig& efc) { + stdx::unordered_set<std::string> indexedFields; + for (auto f : efc.getFields()) { + if (f.getQueries().has_value()) { + indexedFields.insert(f.getPath().toString()); + } + } + + visitEncryptedBSON(obj, [&indexedFields](ConstDataRange cdr, StringData fieldPath) { + auto [encryptedTypeBinding, subCdr] = fromEncryptedConstDataRange(cdr); + if (encryptedTypeBinding == EncryptedBinDataType::kFLE2InsertUpdatePayload) { + uassert(6373601, + str::stream() << "Field '" << fieldPath + << "' is encrypted, but absent from schema", + indexedFields.contains(fieldPath.toString())); + } + }); +} + + std::vector<EDCServerPayloadInfo> EDCServerCollection::getEncryptedFieldInfo(BSONObj& obj) { std::vector<EDCServerPayloadInfo> fields; - // TODO (SERVER-63736) - Validate only fields listed in EncryptedFieldConfig are indexed - visitEncryptedBSON(obj, [&fields](ConstDataRange cdr, StringData fieldPath) { collectEDCServerInfo(&fields, cdr, fieldPath); }); diff --git a/src/mongo/crypto/fle_crypto.h b/src/mongo/crypto/fle_crypto.h index ab812b169ac..1092a817d82 100644 --- a/src/mongo/crypto/fle_crypto.h +++ b/src/mongo/crypto/fle_crypto.h @@ -994,6 +994,11 @@ struct FLEDeleteToken { class EDCServerCollection { public: /** + * Validate that payload is compatible with schema + */ + static void validateEncryptedFieldInfo(BSONObj& obj, const EncryptedFieldConfig& efc); + + /** * Get information about all FLE2InsertUpdatePayload payloads */ static std::vector<EDCServerPayloadInfo> getEncryptedFieldInfo(BSONObj& obj); diff --git a/src/mongo/crypto/fle_crypto_test.cpp b/src/mongo/crypto/fle_crypto_test.cpp index 8997f88e58c..01d5e1c13a7 100644 --- a/src/mongo/crypto/fle_crypto_test.cpp +++ b/src/mongo/crypto/fle_crypto_test.cpp @@ -598,10 +598,15 @@ std::vector<char> generatePlaceholder( return v; } - -BSONObj encryptDocument(BSONObj obj, FLEKeyVault* keyVault) { +BSONObj encryptDocument(BSONObj obj, + FLEKeyVault* keyVault, + const EncryptedFieldConfig* efc = nullptr) { auto result = FLEClientCrypto::transformPlaceholders(obj, keyVault); + if (nullptr != efc) { + EDCServerCollection::validateEncryptedFieldInfo(result, *efc); + } + // Start Server Side auto serverPayload = EDCServerCollection::getEncryptedFieldInfo(result); @@ -1080,43 +1085,39 @@ bool vectorContains(const std::vector<T>& vec, Func func) { EncryptedFieldConfig getTestEncryptedFieldConfig() { constexpr auto schema = R"({ - "escCollection": "esc", - "eccCollection": "ecc", - "ecocCollection": "ecoc", - "fields": [ - { - "keyId": - { - "$uuid": "12345678-1234-9876-1234-123456789012" - } - , - "path": "encrypted", - "bsonType": "string", - "queries": {"queryType": "equality"} - - }, - { - "keyId": - { - "$uuid": "12345678-1234-9876-1234-123456789013" - } - , - "path": "nested.encrypted", - "bsonType": "string", - "queries": {"queryType": "equality"} - - }, - { - "keyId": - { - "$uuid": "12345678-1234-9876-1234-123456789014" - } - , - "path": "nested.notindexed", - "bsonType": "string" - } - ] -})"; + "escCollection": "esc", + "eccCollection": "ecc", + "ecocCollection": "ecoc", + "fields": [ + { + "keyId": { + "$uuid": "12345678-1234-9876-1234-123456789012" + }, + "path": "encrypted", + "bsonType": "string", + "queries": { + "queryType": "equality" + } + }, + { + "keyId": { + "$uuid": "12345678-1234-9876-1234-123456789013" + }, + "path": "nested.encrypted", + "bsonType": "string", + "queries": { + "queryType": "equality" + } + }, + { + "keyId": { + "$uuid": "12345678-1234-9876-1234-123456789014" + }, + "path": "nested.notindexed", + "bsonType": "string" + } + ] + })"; return EncryptedFieldConfig::parse(IDLParserErrorContext("root"), fromjson(schema)); } @@ -1433,7 +1434,7 @@ TEST(EDC, ValidateDocument) { builder.appendBinData("notindexed", buf.size(), BinDataType::Encrypt, buf.data()); } - auto finalDoc = encryptDocument(builder.obj(), &keyVault); + auto finalDoc = encryptDocument(builder.obj(), &keyVault, &efc); // Positive - Encrypted Doc FLEClientCrypto::validateDocument(finalDoc, efc, &keyVault); @@ -1495,6 +1496,22 @@ TEST(EDC, ValidateDocument) { } } +TEST(EDC, NonMatchingSchema) { + EncryptedFieldConfig efc = getTestEncryptedFieldConfig(); + + TestKeyVault keyVault; + + BSONObjBuilder builder; + builder.append("plainText", "sample"); + auto doc = BSON("a" + << "not really a secret"); + auto element = doc.firstElement(); + auto buf = generatePlaceholder(element, Operation::kInsert); + builder.appendBinData("not-encrypted", buf.size(), BinDataType::Encrypt, buf.data()); + + ASSERT_THROWS_CODE(encryptDocument(builder.obj(), &keyVault, &efc), DBException, 6373601); +} + BSONObj encryptUpdateDocument(BSONObj obj, FLEKeyVault* keyVault) { auto result = FLEClientCrypto::transformPlaceholders(obj, keyVault); diff --git a/src/mongo/db/fle_crud.cpp b/src/mongo/db/fle_crud.cpp index 2451133e2f4..2a6e2243bb9 100644 --- a/src/mongo/db/fle_crud.cpp +++ b/src/mongo/db/fle_crud.cpp @@ -195,11 +195,17 @@ std::pair<FLEBatchResult, write_ops::InsertCommandReply> processInsert( const write_ops::InsertCommandRequest& insertRequest, GetTxnCallback getTxns) { + auto edcNss = insertRequest.getNamespace(); + auto ei = insertRequest.getEncryptionInformation().get(); + + auto efc = EncryptionInformationHelpers::getAndValidateSchema(edcNss, ei); + auto documents = insertRequest.getDocuments(); // TODO - how to check if a document will be too large??? uassert(6371202, "Only single insert batches are supported in FLE2", documents.size() == 1); auto document = documents[0]; + EDCServerCollection::validateEncryptedFieldInfo(document, efc); auto serverPayload = std::make_shared<std::vector<EDCServerPayloadInfo>>( EDCServerCollection::getEncryptedFieldInfo(document)); @@ -209,10 +215,6 @@ std::pair<FLEBatchResult, write_ops::InsertCommandReply> processInsert( FLEBatchResult::kNotProcessed, write_ops::InsertCommandReply()}; } - auto ei = insertRequest.getEncryptionInformation().get(); - - auto edcNss = insertRequest.getNamespace(); - auto efc = EncryptionInformationHelpers::getAndValidateSchema(insertRequest.getNamespace(), ei); write_ops::InsertCommandReply reply; std::shared_ptr<txn_api::TransactionWithRetries> trun = getTxns(opCtx); @@ -712,6 +714,8 @@ write_ops::UpdateCommandReply processUpdate(FLEQueryInterface* queryImpl, if (updateModification.type() == write_ops::UpdateModification::Type::kModifier) { auto updateModifier = updateModification.getUpdateModifier(); + auto setObject = updateModifier.getObjectField("$set"); + EDCServerCollection::validateEncryptedFieldInfo(setObject, efc); serverPayload = EDCServerCollection::getEncryptedFieldInfo(updateModifier); processFieldsForInsert(queryImpl, edcNss, serverPayload, efc); @@ -723,6 +727,7 @@ write_ops::UpdateCommandReply processUpdate(FLEQueryInterface* queryImpl, pushUpdate, write_ops::UpdateModification::ClassicTag(), false)); } else { auto replacementDocument = updateModification.getUpdateReplacement(); + EDCServerCollection::validateEncryptedFieldInfo(replacementDocument, efc); serverPayload = EDCServerCollection::getEncryptedFieldInfo(replacementDocument); processFieldsForInsert(queryImpl, edcNss, serverPayload, efc); @@ -869,6 +874,8 @@ write_ops::FindAndModifyCommandReply processFindAndModify( if (updateModification.type() == write_ops::UpdateModification::Type::kModifier) { auto updateModifier = updateModification.getUpdateModifier(); + auto setObject = updateModifier.getObjectField("$set"); + EDCServerCollection::validateEncryptedFieldInfo(setObject, efc); serverPayload = EDCServerCollection::getEncryptedFieldInfo(updateModifier); processFieldsForInsert(queryImpl, edcNss, serverPayload, efc); @@ -879,6 +886,7 @@ write_ops::FindAndModifyCommandReply processFindAndModify( pushUpdate, write_ops::UpdateModification::ClassicTag(), false); } else { auto replacementDocument = updateModification.getUpdateReplacement(); + EDCServerCollection::validateEncryptedFieldInfo(replacementDocument, efc); serverPayload = EDCServerCollection::getEncryptedFieldInfo(replacementDocument); processFieldsForInsert(queryImpl, edcNss, serverPayload, efc); |