summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Benvenuto <mark.benvenuto@mongodb.com>2022-02-07 10:22:05 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-02-09 20:10:05 +0000
commit7fabf68464b333a5ff269ce8386dfbbd9ab49ac8 (patch)
treea406dde55b1bdacfe0734dadf0f281581a6beb03
parentc6e6615a5d4ead5afe5e961dbb7dc5998153ff60 (diff)
downloadmongo-7fabf68464b333a5ff269ce8386dfbbd9ab49ac8.tar.gz
SERVER-63384 Add support for encrypted fields to create collectionr5.3.0-alpha4
-rw-r--r--jstests/core/create_encrypted_collection.js42
-rw-r--r--src/mongo/bson/bsontypes.cpp10
-rw-r--r--src/mongo/bson/bsontypes.h6
-rw-r--r--src/mongo/crypto/encryption_fields.idl73
-rw-r--r--src/mongo/crypto/encryption_fields_util.h81
-rw-r--r--src/mongo/db/catalog/SConscript2
-rw-r--r--src/mongo/db/catalog/collection_options.cpp47
-rw-r--r--src/mongo/db/catalog/collection_options.h7
-rw-r--r--src/mongo/db/catalog/collection_options_test.cpp219
-rw-r--r--src/mongo/db/catalog/collection_options_validation.cpp59
-rw-r--r--src/mongo/db/catalog/collection_options_validation.h4
-rw-r--r--src/mongo/db/catalog/create_collection.cpp3
-rw-r--r--src/mongo/db/commands/create.idl6
-rw-r--r--src/mongo/s/commands/cluster_create_cmd.cpp3
14 files changed, 557 insertions, 5 deletions
diff --git a/jstests/core/create_encrypted_collection.js b/jstests/core/create_encrypted_collection.js
new file mode 100644
index 00000000000..84187857e78
--- /dev/null
+++ b/jstests/core/create_encrypted_collection.js
@@ -0,0 +1,42 @@
+// Verify valid and invalid scenarios for create encrypted collection
+
+/**
+ * @tags: [
+ * featureFlagFLE2,
+ * assumes_against_mongod_not_mongos
+ * ]
+ */
+(function() {
+'use strict';
+
+const isFLE2Enabled = TestData == undefined || TestData.setParameters.featureFlagFLE2;
+
+if (!isFLE2Enabled) {
+ return;
+}
+
+let dbTest = db.getSiblingDB('create_encrypted_collection_db');
+
+dbTest.basic.drop();
+
+assert.commandWorked(dbTest.createCollection("basic", {
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "firstName",
+ "keyId": UUID("11d58b8a-0c6c-4d69-a0bd-70c6d9befae9"),
+ "bsonType": "string",
+ "queries": {"queryType": "equality"} // allow single object or array
+ },
+
+ ]
+ }
+}));
+
+const result = dbTest.getCollectionInfos({name: "basic"});
+print("result" + tojson(result));
+const ef = result[0].options.encryptedFields;
+assert.eq(ef.escCollection, "fle2.basic.esc");
+assert.eq(ef.eccCollection, "fle2.basic.ecc");
+assert.eq(ef.ecocCollection, "fle2.basic.ecoc");
+}());
diff --git a/src/mongo/bson/bsontypes.cpp b/src/mongo/bson/bsontypes.cpp
index 59fb2abba96..8be3c5e1d04 100644
--- a/src/mongo/bson/bsontypes.cpp
+++ b/src/mongo/bson/bsontypes.cpp
@@ -137,6 +137,16 @@ BSONType typeFromName(StringData name) {
return *typeAlias;
}
+Status isValidBSONTypeName(StringData typeName) {
+ try {
+ typeFromName(typeName);
+ } catch (const ExceptionFor<ErrorCodes::BadValue>& ex) {
+ return ex.toStatus();
+ }
+
+ return Status::OK();
+}
+
std::ostream& operator<<(std::ostream& stream, BSONType type) {
return stream << typeName(type);
}
diff --git a/src/mongo/bson/bsontypes.h b/src/mongo/bson/bsontypes.h
index 3463285f636..0dc2fe8a4ab 100644
--- a/src/mongo/bson/bsontypes.h
+++ b/src/mongo/bson/bsontypes.h
@@ -135,6 +135,12 @@ std::ostream& operator<<(std::ostream& stream, BSONType type);
*/
bool isValidBSONType(int type);
+/**
+ * IDL callback validator
+ */
+Status isValidBSONTypeName(StringData typeName);
+
+
inline bool isNumericBSONType(BSONType type) {
switch (type) {
case NumberDouble:
diff --git a/src/mongo/crypto/encryption_fields.idl b/src/mongo/crypto/encryption_fields.idl
index f9724f16c91..1ba43cde430 100644
--- a/src/mongo/crypto/encryption_fields.idl
+++ b/src/mongo/crypto/encryption_fields.idl
@@ -31,8 +31,81 @@ global:
imports:
- "mongo/idl/basic_types.idl"
+enums:
+ QueryType:
+ description: "query types"
+ type: string
+ values:
+ Equality: "equality"
+ # Range: "range"
+
feature_flags:
featureFlagFLE2:
description: "Enable FLE2 support"
cpp_varname: gFeatureFlagFLE2
default: false
+
+structs:
+
+ QueryTypeConfig:
+ description: "Information about query support for a field"
+ strict: true
+ fields:
+ queryType:
+ description: "Type of supported queries"
+ type: QueryType
+ unstable: true
+ contention:
+ description: "Contention factor for field, 0 means it has extremely high set number of distinct values"
+ type: long
+ default: 0
+ unstable: true
+ validator: { gte: 0 }
+
+ EncryptedField:
+ description: "Information about encrypted fields"
+ strict: true
+ fields:
+ keyId:
+ description: "UUID of key in key vault to use for encryption"
+ type: uuid
+ unstable: true
+ path:
+ description: "Path to field to encrypt"
+ type: string
+ unstable: true
+ bsonType:
+ description: "BSON type of field to encrypt"
+ type: string
+ validator: { callback: "isValidBSONTypeName" }
+ unstable: true
+ queries:
+ description: "List of supported query types"
+ type:
+ variant: [QueryTypeConfig, array<QueryTypeConfig>]
+ optional: true
+ unstable: true
+
+ EncryptedFieldConfig:
+ description: "Information about encrypted fields and state collections"
+ strict: true
+ fields:
+ escCollection:
+ description: "Encrypted State Collection name, defaults to <collection>.esc"
+ type: string
+ optional: true
+ unstable: true
+ eccCollection:
+ description: "Encrypted Cache Collection name, defaults to <collection>.ecc"
+ type: string
+ optional: true
+ unstable: true
+ ecocCollection:
+ description: "Encrypted Compaction Collection name, defaults to <collection>.ecoc"
+ type: string
+ optional: true
+ unstable: true
+ fields:
+ description: "Array of encrypted fields"
+ type: array<EncryptedField>
+ unstable: true
diff --git a/src/mongo/crypto/encryption_fields_util.h b/src/mongo/crypto/encryption_fields_util.h
new file mode 100644
index 00000000000..ad4419d6acf
--- /dev/null
+++ b/src/mongo/crypto/encryption_fields_util.h
@@ -0,0 +1,81 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+#pragma once
+
+#include "mongo/base/status.h"
+#include "mongo/base/string_data.h"
+#include "mongo/bson/bsontypes.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+
+inline bool isFLE2EqualityIndexedSupportedType(BSONType type) {
+ switch (type) {
+ case BinData:
+ case Code:
+ case RegEx:
+ case String:
+
+ case NumberInt:
+ case NumberLong:
+ case Bool:
+ case bsonTimestamp:
+ case Date:
+ case jstOID:
+ return true;
+
+ // Deprecated
+ case Symbol:
+ case CodeWScope:
+ case DBRef:
+
+ // Non-deterministic
+ case Array:
+ case Object:
+ case NumberDecimal:
+ case NumberDouble:
+
+ // Singletons
+ case EOO:
+ case jstNULL:
+ case MaxKey:
+ case MinKey:
+ case Undefined:
+ return false;
+ default:
+ MONGO_UNREACHABLE;
+ }
+}
+
+// Unindexed is the same as equality
+inline bool isFLE2UnindexedSupportedType(BSONType type) {
+ return isFLE2EqualityIndexedSupportedType(type);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/catalog/SConscript b/src/mongo/db/catalog/SConscript
index b5bf4ebbe09..384775dd7c2 100644
--- a/src/mongo/db/catalog/SConscript
+++ b/src/mongo/db/catalog/SConscript
@@ -41,8 +41,10 @@ env.Library(
],
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/crypto/encrypted_field_config',
'$BUILD_DIR/mongo/db/query/collation/collator_interface',
'$BUILD_DIR/mongo/db/query/query_knobs',
+ '$BUILD_DIR/mongo/db/server_options_core',
'$BUILD_DIR/mongo/db/timeseries/timeseries_options',
'$BUILD_DIR/mongo/idl/basic_types',
'$BUILD_DIR/mongo/idl/feature_flag',
diff --git a/src/mongo/db/catalog/collection_options.cpp b/src/mongo/db/catalog/collection_options.cpp
index 2b2f16bf9ea..2db236f1ecb 100644
--- a/src/mongo/db/catalog/collection_options.cpp
+++ b/src/mongo/db/catalog/collection_options.cpp
@@ -59,6 +59,24 @@ long long adjustCappedMaxDocs(long long cappedMaxDocs) {
}
return cappedMaxDocs;
}
+
+void setEncryptedDefaultEncryptedCollectionNames(const NamespaceString& ns,
+ EncryptedFieldConfig* config) {
+ auto prefix = std::string("fle2.") + ns.coll();
+
+ if (!config->getEscCollection()) {
+ config->setEscCollection(StringData(prefix + ".esc"));
+ }
+
+ if (!config->getEccCollection()) {
+ config->setEccCollection(StringData(prefix + ".ecc"));
+ }
+
+ if (!config->getEcocCollection()) {
+ config->setEcocCollection(StringData(prefix + ".ecoc"));
+ }
+}
+
} // namespace
bool CollectionOptions::isView() const {
@@ -258,6 +276,18 @@ StatusWith<CollectionOptions> CollectionOptions::parse(const BSONObj& options, P
} catch (const DBException& ex) {
return ex.toStatus();
}
+ } else if (fieldName == "encryptedFields") {
+ if (e.type() != mongo::Object) {
+ return {ErrorCodes::TypeMismatch, "'encryptedFields' must be a document"};
+ }
+
+ try {
+ collectionOptions.encryptedFieldConfig =
+ collection_options_validation::processAndValidateEncryptedFields(
+ EncryptedFieldConfig::parse({"CollectionOptions::parse"}, e.Obj()));
+ } catch (const DBException& ex) {
+ return ex.toStatus();
+ }
} else if (!createdOn24OrEarlier && !mongo::isGenericArgument(fieldName)) {
return Status(ErrorCodes::InvalidOptions,
str::stream()
@@ -273,7 +303,8 @@ StatusWith<CollectionOptions> CollectionOptions::parse(const BSONObj& options, P
return collectionOptions;
}
-CollectionOptions CollectionOptions::fromCreateCommand(const CreateCommand& cmd) {
+CollectionOptions CollectionOptions::fromCreateCommand(const NamespaceString& nss,
+ const CreateCommand& cmd) {
CollectionOptions options;
options.validationLevel = cmd.getValidationLevel();
@@ -345,6 +376,10 @@ CollectionOptions CollectionOptions::fromCreateCommand(const CreateCommand& cmd)
if (auto temp = cmd.getTemp()) {
options.temp = *temp;
}
+ if (auto encryptedFieldConfig = cmd.getEncryptedFields()) {
+ options.encryptedFieldConfig = std::move(*encryptedFieldConfig);
+ setEncryptedDefaultEncryptedCollectionNames(nss, options.encryptedFieldConfig.get_ptr());
+ }
return options;
}
@@ -448,6 +483,10 @@ void CollectionOptions::appendBSON(BSONObjBuilder* builder,
if (timeseries && shouldAppend(CreateCommand::kTimeseriesFieldName)) {
builder->append(CreateCommand::kTimeseriesFieldName, timeseries->toBSON());
}
+
+ if (encryptedFieldConfig && shouldAppend(CreateCommand::kEncryptedFieldsFieldName)) {
+ builder->append(CreateCommand::kEncryptedFieldsFieldName, encryptedFieldConfig->toBSON());
+ }
}
bool CollectionOptions::matchesStorageOptions(const CollectionOptions& other,
@@ -532,6 +571,12 @@ bool CollectionOptions::matchesStorageOptions(const CollectionOptions& other,
return false;
}
+ if ((encryptedFieldConfig && other.encryptedFieldConfig &&
+ encryptedFieldConfig->toBSON().woCompare(other.encryptedFieldConfig->toBSON()) != 0) ||
+ (encryptedFieldConfig == boost::none) != (other.encryptedFieldConfig == boost::none)) {
+ return false;
+ }
+
if (expireAfterSeconds != other.expireAfterSeconds) {
return false;
}
diff --git a/src/mongo/db/catalog/collection_options.h b/src/mongo/db/catalog/collection_options.h
index 52e0506bc43..641a0510fa5 100644
--- a/src/mongo/db/catalog/collection_options.h
+++ b/src/mongo/db/catalog/collection_options.h
@@ -34,6 +34,7 @@
#include <boost/optional.hpp>
#include "mongo/base/status.h"
+#include "mongo/crypto/encryption_fields_gen.h"
#include "mongo/db/catalog/clustered_collection_options_gen.h"
#include "mongo/db/catalog/collection_options_gen.h"
#include "mongo/db/jsobj.h"
@@ -79,7 +80,8 @@ struct CollectionOptions {
/**
* Converts a client "create" command invocation.
*/
- static CollectionOptions fromCreateCommand(const CreateCommand& cmd);
+ static CollectionOptions fromCreateCommand(const NamespaceString& nss,
+ const CreateCommand& cmd);
/**
* Serialize to BSON. The 'includeUUID' parameter is used for the listCollections command to do
@@ -161,5 +163,8 @@ struct CollectionOptions {
// The options that define the time-series collection, or boost::none if not a time-series
// collection.
boost::optional<TimeseriesOptions> timeseries;
+
+ // The options for collections with encrypted fields
+ boost::optional<EncryptedFieldConfig> encryptedFieldConfig;
};
} // namespace mongo
diff --git a/src/mongo/db/catalog/collection_options_test.cpp b/src/mongo/db/catalog/collection_options_test.cpp
index 385310f4f87..d6632c3456f 100644
--- a/src/mongo/db/catalog/collection_options_test.cpp
+++ b/src/mongo/db/catalog/collection_options_test.cpp
@@ -34,6 +34,7 @@
#include <limits>
#include "mongo/db/json.h"
+#include "mongo/idl/server_parameter_test_util.h"
#include "mongo/unittest/unittest.h"
namespace mongo {
@@ -353,4 +354,222 @@ TEST(CollectionOptions, NExtentsNoError) {
// Check that $nExtents does not cause an error for backwards compatability
assertGet(CollectionOptions::parse(fromjson("{$nExtents: 'a'}")));
}
+
+#define ASSERT_STATUS_CODE(CODE, EXPRESSION) ASSERT_EQUALS(CODE, (EXPRESSION).getStatus().code())
+
+// Duplicate fields is not allowed
+TEST(FLECollectionOptions, MultipleFields) {
+ RAIIServerParameterControllerForTest featureFlagController("featureFlagFLE2", true);
+
+ ASSERT_STATUS_CODE(6338402, CollectionOptions::parse(fromjson(R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "name.first",
+ "keyId": { '$uuid': '11d58b8a-0c6c-4d69-a0bd-70c6d9befae9' },
+ "bsonType": "string",
+ "queries": {"queryType": "equality"}
+ },
+ {
+ "path": "name.first",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": "string",
+ "queries": [{"queryType": "equality"}]
+ }
+ ]
+ }})")));
+}
+
+// Duplicate key ids are bad, it breaks the design
+TEST(FLECollectionOptions, DuplicateKeyIds) {
+ RAIIServerParameterControllerForTest featureFlagController("featureFlagFLE2", true);
+
+ ASSERT_STATUS_CODE(6338401, CollectionOptions::parse(fromjson(R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "name.first",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": "string",
+ "queries": {"queryType": "equality"}
+ },
+ {
+ "path": "name.last",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": "string",
+ "queries": [{"queryType": "equality"}]
+ }
+ ]
+ }})")));
+}
+
+TEST(FLECollectionOptions, ConflictingPrefixes) {
+ RAIIServerParameterControllerForTest featureFlagController("featureFlagFLE2", true);
+
+ ASSERT_STATUS_CODE(6338403, CollectionOptions::parse(fromjson(R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "name",
+ "keyId": { '$uuid': '11d58b8a-0c6c-4d69-a0bd-70c6d9befae9' },
+ "bsonType": "string",
+ "queries": {"queryType": "equality"}
+ },
+ {
+ "path": "name.first",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": "string",
+ "queries": [{"queryType": "equality"}]
+ }
+ ]
+ }})")));
+
+ ASSERT_STATUS_CODE(6338403, CollectionOptions::parse(fromjson(R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "a.b.c",
+ "keyId": { '$uuid': '11d58b8a-0c6c-4d69-a0bd-70c6d9befae9' },
+ "bsonType": "string",
+ "queries": {"queryType": "equality"}
+ },
+ {
+ "path": "a.b.c.d.e",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": "string",
+ "queries": [{"queryType": "equality"}]
+ }
+ ]
+ }})")));
+
+ ASSERT_STATUS_CODE(6338403, CollectionOptions::parse(fromjson(R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "a.b.c.d.e.f",
+ "keyId": { '$uuid': '11d58b8a-0c6c-4d69-a0bd-70c6d9befae9' },
+ "bsonType": "string",
+ "queries": {"queryType": "equality"}
+ },
+ {
+ "path": "a.b.c.d",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": "string",
+ "queries": [{"queryType": "equality"}]
+ }
+ ]
+ }})")));
+}
+
+TEST(FLECollectionOptions, DuplicateQueryTypes) {
+
+
+ RAIIServerParameterControllerForTest featureFlagController("featureFlagFLE2", true);
+
+ ASSERT_STATUS_CODE(6338404, CollectionOptions::parse(fromjson(R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "name.first",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": "string",
+ "queries": [{"queryType": "equality"}, {"queryType": "equality"}]
+ }
+ ]
+ }})")));
+}
+
+TEST(FLECollectionOptions, AllowedTypes) {
+ RAIIServerParameterControllerForTest featureFlagController("featureFlagFLE2", true);
+
+ std::vector<std::string> types({
+ "string",
+ "binData",
+ "objectId",
+ "bool",
+ "date",
+ "regex",
+ "javascript",
+ "int",
+ "timestamp",
+ "long",
+ });
+
+ for (const auto& type : types) {
+ ASSERT_OK(CollectionOptions::parse(fromjson(str::stream() << R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "name.first",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": ")" << type << R"("
+ }
+ ]
+ }})"))
+ .getStatus());
+
+ ASSERT_OK(CollectionOptions::parse(fromjson(str::stream() << R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "firstName",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": ")" << type << R"(",
+ "queries": {"queryType": "equality"}
+ }
+ ]
+ }
+ }})"))
+ .getStatus());
+ }
+}
+
+
+TEST(FLECollectionOptions, DisAllowedTypes) {
+ RAIIServerParameterControllerForTest featureFlagController("featureFlagFLE2", true);
+
+ std::vector<std::string> types({
+ "minKey",
+ "missing",
+ "double",
+ "object",
+ "array",
+ "null",
+ "undefined",
+ "dbPointer",
+ "symbol",
+ "javascriptWithScope",
+ "decimal",
+ "maxKey",
+ });
+
+ for (const auto& type : types) {
+ ASSERT_NOT_OK(CollectionOptions::parse(fromjson(str::stream() << R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "name.first",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": ")" << type << R"("
+ }
+ ]
+ }})"))
+ .getStatus());
+
+ ASSERT_NOT_OK(CollectionOptions::parse(fromjson(str::stream() << R"({
+ encryptedFields: {
+ "fields": [
+ {
+ "path": "firstName",
+ "keyId": { '$uuid': '5f34e99a-b214-451f-b6f6-d3d28e933d15' },
+ "bsonType": ")" << type << R"(",
+ "queries": {"queryType": "equality"}
+ }
+ ]
+ }
+ }})"))
+ .getStatus());
+ }
+}
+
} // namespace mongo
diff --git a/src/mongo/db/catalog/collection_options_validation.cpp b/src/mongo/db/catalog/collection_options_validation.cpp
index fd01b1e876c..116bb86d261 100644
--- a/src/mongo/db/catalog/collection_options_validation.cpp
+++ b/src/mongo/db/catalog/collection_options_validation.cpp
@@ -28,6 +28,7 @@
*/
#include "mongo/db/catalog/collection_options_validation.h"
+#include "mongo/crypto/encryption_fields_util.h"
namespace mongo::collection_options_validation {
Status validateStorageEngineOptions(const BSONObj& storageEngine) {
@@ -54,4 +55,62 @@ Status validateStorageEngineOptions(const BSONObj& storageEngine) {
}
return Status::OK();
}
+
+EncryptedFieldConfig processAndValidateEncryptedFields(EncryptedFieldConfig config) {
+
+ if (!gFeatureFlagFLE2.isEnabledAndIgnoreFCV()) {
+ uasserted(6338408, "Feature flag FLE2 is not enabled");
+ }
+
+ stdx::unordered_set<UUID, UUID::Hash> keys(config.getFields().size());
+ std::vector<std::string> fieldPaths;
+ fieldPaths.reserve(config.getFields().size());
+
+ for (const auto& field : config.getFields()) {
+ UUID keyId = field.getKeyId();
+
+ // Duplicate key ids are bad, it breaks the design
+ uassert(6338401, "Duplicate key ids are not allowed", keys.count(keyId) == 0);
+ keys.insert(keyId);
+
+ BSONType type = typeFromName(field.getBsonType());
+
+ for (const auto& path : fieldPaths) {
+ uassert(6338402, "Duplicate paths are not allowed", field.getPath() != path);
+ // Cannot have indexes on "a" and "a.b"
+ uassert(6338403,
+ str::stream() << "Conflicting index paths found as one is a prefix of another '"
+ << field.getPath() << "' and '" << path << "'",
+ !field.getPath().startsWith(path) &&
+ !StringData(path).startsWith(field.getPath()));
+ }
+ fieldPaths.push_back(field.getPath().toString());
+
+ if (field.getQueries().has_value()) {
+ auto queriesVariant = field.getQueries().get();
+
+ auto queries = stdx::get_if<std::vector<mongo::QueryTypeConfig>>(&queriesVariant);
+ if (queries) {
+ // If the user specified multiple queries, verify they are unique
+ // TODO - once other index types are added we will need to enhance this check
+ uassert(6338404,
+ "Only 1 equality queryType can be specified per field",
+ queries->size() == 1);
+ }
+
+ uassert(6338405,
+ str::stream() << "Type '" << typeName(type)
+ << "' is not a supported equality indexed type",
+ isFLE2EqualityIndexedSupportedType(type));
+ } else {
+ uassert(6338406,
+ str::stream() << "Type '" << typeName(type)
+ << "' is not a supported unindexed type",
+ isFLE2UnindexedSupportedType(type));
+ }
+ }
+
+ return config;
+}
+
} // namespace mongo::collection_options_validation
diff --git a/src/mongo/db/catalog/collection_options_validation.h b/src/mongo/db/catalog/collection_options_validation.h
index db453589f88..09f8e67b742 100644
--- a/src/mongo/db/catalog/collection_options_validation.h
+++ b/src/mongo/db/catalog/collection_options_validation.h
@@ -31,7 +31,11 @@
#include "mongo/base/status.h"
#include "mongo/bson/bsonobj.h"
+#include "mongo/crypto/encryption_fields_gen.h"
namespace mongo::collection_options_validation {
Status validateStorageEngineOptions(const BSONObj& storageEngine);
+
+EncryptedFieldConfig processAndValidateEncryptedFields(EncryptedFieldConfig config);
+
} // namespace mongo::collection_options_validation
diff --git a/src/mongo/db/catalog/create_collection.cpp b/src/mongo/db/catalog/create_collection.cpp
index d5c4caef4cf..89357d9280b 100644
--- a/src/mongo/db/catalog/create_collection.cpp
+++ b/src/mongo/db/catalog/create_collection.cpp
@@ -667,7 +667,6 @@ Status createCollection(OperationContext* opCtx,
return createCollection(opCtx, nss, collectionOptions, idIndex);
}
-
} // namespace
Status createCollection(OperationContext* opCtx,
@@ -684,7 +683,7 @@ Status createCollection(OperationContext* opCtx,
Status createCollection(OperationContext* opCtx,
const NamespaceString& ns,
const CreateCommand& cmd) {
- auto options = CollectionOptions::fromCreateCommand(cmd);
+ auto options = CollectionOptions::fromCreateCommand(ns, cmd);
auto idIndex = std::exchange(options.idIndex, {});
bool hasExplicitlyDisabledClustering = cmd.getClusteredIndex() &&
stdx::holds_alternative<bool>(*cmd.getClusteredIndex()) &&
diff --git a/src/mongo/db/commands/create.idl b/src/mongo/db/commands/create.idl
index 9ba1e6a82a3..2e156b4f1b5 100644
--- a/src/mongo/db/commands/create.idl
+++ b/src/mongo/db/commands/create.idl
@@ -32,6 +32,7 @@ global:
- "mongo/db/commands/create_command_validation.h"
imports:
+ - "mongo/crypto/encryption_fields.idl"
- "mongo/db/auth/access_checks.idl"
- "mongo/db/auth/action_type.idl"
- "mongo/db/catalog/collection_options.idl"
@@ -196,6 +197,11 @@ commands:
type: safeInt64
optional: true
unstable: false
+ encryptedFields:
+ description: "The number of seconds after which old data should be deleted."
+ type: EncryptedFieldConfig
+ optional: true
+ unstable: false
temp:
description: "DEPRECATED"
type: safeBool
diff --git a/src/mongo/s/commands/cluster_create_cmd.cpp b/src/mongo/s/commands/cluster_create_cmd.cpp
index a82a85ad02b..b7c6d5ea88b 100644
--- a/src/mongo/s/commands/cluster_create_cmd.cpp
+++ b/src/mongo/s/commands/cluster_create_cmd.cpp
@@ -144,7 +144,8 @@ public:
!opCtx->inMultiDocumentTransaction()) {
// NamespaceExists will cause multi-document transactions to implicitly abort, so
// mongos should surface this error to the client.
- auto options = CollectionOptions::fromCreateCommand(cmd);
+ auto options = CollectionOptions::fromCreateCommand(cmd.getNamespace(), cmd);
+
checkCollectionOptions(opCtx, cmd.getNamespace(), options);
} else {
uassertStatusOK(createStatus);