summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErwin Pe <erwin.pe@mongodb.com>2022-03-08 15:31:04 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-03-08 16:48:00 +0000
commit29f892aa2852dfcac90929da98cae45c381eb4c8 (patch)
tree790f19e687c6496d82c60243bc905963d7fa648f
parentd4af42c0e63638a4b3b191959299150722c3a3fc (diff)
downloadmongo-29f892aa2852dfcac90929da98cae45c381eb4c8.tar.gz
SERVER-63466 Fail shardCollection if an indexed encrypted field is used as a shard key
-rw-r--r--jstests/sharding/shard_encrypted_collection.js77
-rw-r--r--src/mongo/crypto/SConscript5
-rw-r--r--src/mongo/crypto/encryption_fields_util.cpp47
-rw-r--r--src/mongo/crypto/encryption_fields_util.h12
-rw-r--r--src/mongo/crypto/encryption_fields_util_test.cpp83
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/create_indexes.cpp27
-rw-r--r--src/mongo/db/field_ref.cpp5
-rw-r--r--src/mongo/db/field_ref.h5
-rw-r--r--src/mongo/db/field_ref_test.cpp31
-rw-r--r--src/mongo/db/s/SConscript2
-rw-r--r--src/mongo/db/s/create_collection_coordinator.cpp2
-rw-r--r--src/mongo/db/s/refine_collection_shard_key_coordinator.cpp4
-rw-r--r--src/mongo/db/s/resharding/resharding_recipient_service.cpp4
-rw-r--r--src/mongo/db/s/shard_key_util.cpp44
-rw-r--r--src/mongo/db/s/shard_key_util.h5
16 files changed, 337 insertions, 17 deletions
diff --git a/jstests/sharding/shard_encrypted_collection.js b/jstests/sharding/shard_encrypted_collection.js
new file mode 100644
index 00000000000..b52ae355e1d
--- /dev/null
+++ b/jstests/sharding/shard_encrypted_collection.js
@@ -0,0 +1,77 @@
+// Verify valid and invalid scenarios for sharding an encrypted collection
+
+/**
+ * @tags: [
+ * featureFlagFLE2,
+ * requires_fcv_60,
+ * ]
+ */
+(function() {
+'use strict';
+
+const st = new ShardingTest({shards: 1, mongos: 1});
+const mongos = st.s0;
+const kDbName = 'db';
+
+const sampleEncryptedFields = {
+ "fields": [
+ {
+ "path": "firstName",
+ "keyId": UUID("11d58b8a-0c6c-4d69-a0bd-70c6d9befae9"),
+ "bsonType": "string",
+ "queries": {"queryType": "equality"}
+ },
+ {
+ "path": "paymentMethods.creditCards.number",
+ "keyId": UUID("12341234-1234-1234-1234-123412341234"),
+ "bsonType": "string",
+ "queries": {"queryType": "equality"}
+ },
+ ]
+};
+
+// Set up the encrypted collection & enable sharding
+assert.commandWorked(
+ mongos.getDB(kDbName).createCollection("basic", {encryptedFields: sampleEncryptedFields}));
+assert.commandWorked(mongos.adminCommand({enableSharding: kDbName}));
+
+function testShardingCommand(command) {
+ jsTestLog("Testing command: " + command);
+ let res = null;
+ let commandObj = {};
+ commandObj[command] = kDbName + '.basic';
+
+ jsTestLog('Fail ' + command + ' if shard key is an encrypted field');
+ commandObj['key'] = {firstName: 1};
+ res = mongos.adminCommand(commandObj);
+ assert.commandFailedWithCode(
+ res, ErrorCodes.InvalidOptions, command + " on encrypted field passed");
+
+ commandObj['key'] = {lastName: 1, firstName: "hashed", middleName: 1};
+ res = mongos.adminCommand(commandObj);
+ assert.commandFailedWithCode(
+ res, ErrorCodes.InvalidOptions, command + " on encrypted field passed");
+
+ jsTestLog('Fail ' + command + ' if shard key is a prefix of an encrypted field');
+ commandObj['key'] = {"paymentMethods.creditCards": 1};
+ res = mongos.adminCommand(commandObj);
+ assert.commandFailedWithCode(
+ res, ErrorCodes.InvalidOptions, command + " on prefix of encrypted field passed");
+
+ jsTestLog('Fail ' + command + ' if shard key has a prefix matching an encrypted field');
+ commandObj['key'] = {"paymentMethods.creditCards.number.lastFour": 1};
+ res = mongos.adminCommand(commandObj);
+ assert.commandFailedWithCode(
+ res, ErrorCodes.InvalidOptions, command + " on key with encrypted field prefix passed");
+
+ jsTestLog('Test ' + command + ' on non-encrypted field works');
+ commandObj['key'] = {lastName: 1};
+ assert.commandWorked(mongos.adminCommand(commandObj));
+}
+
+testShardingCommand("shardCollection");
+testShardingCommand("reshardCollection");
+testShardingCommand("refineCollectionShardKey");
+
+st.stop();
+})();
diff --git a/src/mongo/crypto/SConscript b/src/mongo/crypto/SConscript
index 78dc83d6596..1fa5cac2668 100644
--- a/src/mongo/crypto/SConscript
+++ b/src/mongo/crypto/SConscript
@@ -91,9 +91,11 @@ env.Library(
env.Library(
target="fle_crypto",
source=[
+ "encryption_fields_util.cpp",
"fle_crypto.cpp",
],
LIBDEPS=[
+ '$BUILD_DIR/mongo/db/common',
'$BUILD_DIR/mongo/idl/idl_parser',
],
LIBDEPS_PRIVATE=[
@@ -120,9 +122,10 @@ env.CppUnitTest(
target='crypto_test',
source=[
'aead_encryption_test.cpp',
+ 'encryption_fields_util_test.cpp',
+ 'fle_crypto_test.cpp',
'mechanism_scram_test.cpp',
'sha1_block_test.cpp',
- 'fle_crypto_test.cpp',
'sha256_block_test.cpp',
'sha512_block_test.cpp',
'symmetric_crypto_test.cpp',
diff --git a/src/mongo/crypto/encryption_fields_util.cpp b/src/mongo/crypto/encryption_fields_util.cpp
new file mode 100644
index 00000000000..bf7bfbeccf1
--- /dev/null
+++ b/src/mongo/crypto/encryption_fields_util.cpp
@@ -0,0 +1,47 @@
+/**
+ * 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.
+ */
+
+#include "mongo/crypto/encryption_fields_util.h"
+
+#include <algorithm>
+
+namespace mongo {
+
+boost::optional<EncryptedFieldMatchResult> findMatchingEncryptedField(
+ const FieldRef& key, const std::vector<FieldRef>& encryptedFields) {
+ auto itr = std::find_if(encryptedFields.begin(),
+ encryptedFields.end(),
+ [&key](const auto& field) { return key.fullyOverlapsWith(field); });
+ if (itr == encryptedFields.end()) {
+ return boost::none;
+ }
+ return {{*itr, key.numParts() <= itr->numParts()}};
+}
+
+} // namespace mongo
diff --git a/src/mongo/crypto/encryption_fields_util.h b/src/mongo/crypto/encryption_fields_util.h
index ad4419d6acf..7230d08fa48 100644
--- a/src/mongo/crypto/encryption_fields_util.h
+++ b/src/mongo/crypto/encryption_fields_util.h
@@ -28,9 +28,13 @@
*/
#pragma once
+#include <boost/optional.hpp>
+
#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsontypes.h"
+#include "mongo/crypto/encryption_fields_gen.h"
+#include "mongo/db/field_ref.h"
#include "mongo/util/assert_util.h"
namespace mongo {
@@ -78,4 +82,12 @@ inline bool isFLE2UnindexedSupportedType(BSONType type) {
return isFLE2EqualityIndexedSupportedType(type);
}
+struct EncryptedFieldMatchResult {
+ FieldRef encryptedField;
+ bool keyIsPrefixOrEqual;
+};
+
+boost::optional<EncryptedFieldMatchResult> findMatchingEncryptedField(
+ const FieldRef& key, const std::vector<FieldRef>& encryptedFields);
+
} // namespace mongo
diff --git a/src/mongo/crypto/encryption_fields_util_test.cpp b/src/mongo/crypto/encryption_fields_util_test.cpp
new file mode 100644
index 00000000000..f223ce02277
--- /dev/null
+++ b/src/mongo/crypto/encryption_fields_util_test.cpp
@@ -0,0 +1,83 @@
+/**
+ * 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.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/crypto/encryption_fields_util.h"
+
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+TEST(FLEUtils, FindMatchingEncryptedField) {
+ FieldRef fieldAbc("a.b.c"_sd);
+ FieldRef fieldFoo("foo"_sd);
+ FieldRef fieldXyz("x.y.z"_sd);
+ FieldRef fieldEmpty;
+ std::vector<FieldRef> encryptedFields = {fieldAbc, fieldFoo, fieldEmpty, fieldXyz};
+
+ // no-op if no encrypted fields
+ auto result = findMatchingEncryptedField(fieldFoo, {});
+ ASSERT_FALSE(result);
+
+ // empty fields never match
+ result = findMatchingEncryptedField(fieldEmpty, encryptedFields);
+ ASSERT_FALSE(result);
+
+ // no match
+ result = findMatchingEncryptedField(FieldRef("foobar"), encryptedFields);
+ ASSERT_FALSE(result);
+
+ result = findMatchingEncryptedField(FieldRef("a.b.cd"), encryptedFields);
+ ASSERT_FALSE(result);
+
+ // prefix match
+ result = findMatchingEncryptedField(FieldRef("a"), encryptedFields);
+ ASSERT(result);
+ ASSERT(result->keyIsPrefixOrEqual);
+ ASSERT(result->encryptedField == fieldAbc);
+
+ result = findMatchingEncryptedField(FieldRef("x.y"), encryptedFields);
+ ASSERT(result);
+ ASSERT(result->keyIsPrefixOrEqual);
+ ASSERT(result->encryptedField == fieldXyz);
+
+ result = findMatchingEncryptedField(FieldRef("foo.bar.baz"), encryptedFields);
+ ASSERT(result);
+ ASSERT_FALSE(result->keyIsPrefixOrEqual);
+ ASSERT(result->encryptedField == fieldFoo);
+
+ // exact match
+ result = findMatchingEncryptedField(fieldFoo, encryptedFields);
+ ASSERT(result);
+ ASSERT(result->keyIsPrefixOrEqual);
+ ASSERT(result->encryptedField == fieldFoo);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index e8eb26176a2..8e6d0105adb 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -370,6 +370,7 @@ env.Library(
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/base',
'$BUILD_DIR/mongo/crypto/encrypted_field_config',
+ '$BUILD_DIR/mongo/crypto/fle_crypto',
'$BUILD_DIR/mongo/db/api_parameters',
'$BUILD_DIR/mongo/db/catalog/catalog_helpers',
'$BUILD_DIR/mongo/db/catalog/collection_query_info',
diff --git a/src/mongo/db/commands/create_indexes.cpp b/src/mongo/db/commands/create_indexes.cpp
index 66d463e220f..584275b01a5 100644
--- a/src/mongo/db/commands/create_indexes.cpp
+++ b/src/mongo/db/commands/create_indexes.cpp
@@ -35,7 +35,7 @@
#include <vector>
#include "mongo/base/string_data.h"
-#include "mongo/crypto/encryption_fields_gen.h"
+#include "mongo/crypto/encryption_fields_util.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/catalog/clustered_collection_util.h"
#include "mongo/db/catalog/collection.h"
@@ -243,23 +243,18 @@ void checkEncryptedFieldIndexRestrictions(OperationContext* opCtx,
// Do not allow unique indexes on encrypted fields, or prefixes of encrypted fields.
auto keyObject = index[IndexDescriptor::kKeyPatternFieldName].Obj();
for (const auto& keyElement : keyObject) {
-
- FieldRef keyFieldRef(keyElement.fieldNameStringData());
-
- for (const auto& encryptedFieldRef : encryptedFieldRefs) {
- auto common = keyFieldRef.commonPrefixSize(encryptedFieldRef);
- uassert(
- 6346502,
+ auto match = findMatchingEncryptedField(FieldRef(keyElement.fieldNameStringData()),
+ encryptedFieldRefs);
+ uassert(6346502,
str::stream()
<< "Unique indexes are not allowed on, or a prefix of, the encrypted field "
- << encryptedFieldRef.dottedField(),
- common != keyFieldRef.numParts());
- uassert(6346503,
- str::stream() << "Unique indexes are not allowed on keys whose prefix is "
- "the encrypted field "
- << encryptedFieldRef.dottedField(),
- common != encryptedFieldRef.numParts());
- }
+ << match->encryptedField.dottedField(),
+ !match || !match->keyIsPrefixOrEqual);
+ uassert(6346503,
+ str::stream() << "Unique indexes are not allowed on keys whose prefix is "
+ "the encrypted field "
+ << match->encryptedField.dottedField(),
+ !match || match->keyIsPrefixOrEqual);
}
}
}
diff --git a/src/mongo/db/field_ref.cpp b/src/mongo/db/field_ref.cpp
index 80a16b62d6e..ae4a8596deb 100644
--- a/src/mongo/db/field_ref.cpp
+++ b/src/mongo/db/field_ref.cpp
@@ -221,6 +221,11 @@ bool FieldRef::isPrefixOfOrEqualTo(const FieldRef& other) const {
return isPrefixOf(other) || *this == other;
}
+bool FieldRef::fullyOverlapsWith(const FieldRef& other) const {
+ auto common = commonPrefixSize(other);
+ return common && (common == numParts() || common == other.numParts());
+}
+
FieldIndex FieldRef::commonPrefixSize(const FieldRef& other) const {
if (_parts.size() == 0 || other._parts.size() == 0) {
return 0;
diff --git a/src/mongo/db/field_ref.h b/src/mongo/db/field_ref.h
index 59a00cb8148..b2923a84502 100644
--- a/src/mongo/db/field_ref.h
+++ b/src/mongo/db/field_ref.h
@@ -141,6 +141,11 @@ public:
bool isPrefixOfOrEqualTo(const FieldRef& other) const;
/**
+ * Returns true if 'this' is a prefix of, or equal to, 'other', or vice versa.
+ */
+ bool fullyOverlapsWith(const FieldRef& other) const;
+
+ /**
* Returns the number of field parts in the prefix that 'this' and 'other' share.
*/
FieldIndex commonPrefixSize(const FieldRef& other) const;
diff --git a/src/mongo/db/field_ref_test.cpp b/src/mongo/db/field_ref_test.cpp
index 5c2d679c080..f631b6bdb4e 100644
--- a/src/mongo/db/field_ref_test.cpp
+++ b/src/mongo/db/field_ref_test.cpp
@@ -230,6 +230,37 @@ TEST(PrefixSize, Empty) {
ASSERT_EQUALS(empty.commonPrefixSize(fieldA), 0U);
}
+TEST(FullyOverlapsWith, Normal) {
+ FieldRef fieldA("a"), fieldB("a.b"), fieldC("a.b.c");
+ FieldRef fieldD("a.b.d");
+ ASSERT(fieldA.fullyOverlapsWith(fieldA));
+ ASSERT(fieldA.fullyOverlapsWith(fieldB));
+ ASSERT(fieldA.fullyOverlapsWith(fieldC));
+ ASSERT(fieldA.fullyOverlapsWith(fieldD));
+ ASSERT(fieldB.fullyOverlapsWith(fieldA));
+ ASSERT(fieldB.fullyOverlapsWith(fieldB));
+ ASSERT(fieldB.fullyOverlapsWith(fieldC));
+ ASSERT(fieldB.fullyOverlapsWith(fieldD));
+ ASSERT(fieldC.fullyOverlapsWith(fieldA));
+ ASSERT(fieldC.fullyOverlapsWith(fieldB));
+ ASSERT(fieldC.fullyOverlapsWith(fieldC));
+
+ ASSERT_FALSE(fieldD.fullyOverlapsWith(fieldC));
+ ASSERT_FALSE(fieldC.fullyOverlapsWith(fieldD));
+}
+
+TEST(FullyOverlapsWith, NoCommonality) {
+ FieldRef fieldA("a.b.c"), fieldB("b.c.d");
+ ASSERT_FALSE(fieldA.fullyOverlapsWith(fieldB));
+ ASSERT_FALSE(fieldB.fullyOverlapsWith(fieldA));
+}
+
+TEST(FullyOverlapsWith, Empty) {
+ FieldRef field("a"), empty;
+ ASSERT_FALSE(field.fullyOverlapsWith(empty));
+ ASSERT_FALSE(empty.fullyOverlapsWith(field));
+}
+
TEST(Equality, Simple1) {
FieldRef a("a.b");
ASSERT(a.equalsDottedField("a.b"));
diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript
index e5ccb605749..d5bf37c7a5c 100644
--- a/src/mongo/db/s/SConscript
+++ b/src/mongo/db/s/SConscript
@@ -154,6 +154,8 @@ env.Library(
],
LIBDEPS_PRIVATE=[
'$BUILD_DIR/mongo/client/clientdriver_minimal',
+ '$BUILD_DIR/mongo/crypto/encrypted_field_config',
+ '$BUILD_DIR/mongo/crypto/fle_crypto',
'$BUILD_DIR/mongo/db/catalog/catalog_helpers',
'$BUILD_DIR/mongo/db/index_builds_coordinator_interface',
'$BUILD_DIR/mongo/db/repl/image_collection_entry',
diff --git a/src/mongo/db/s/create_collection_coordinator.cpp b/src/mongo/db/s/create_collection_coordinator.cpp
index 2b82eb49fc9..0ad4b92db60 100644
--- a/src/mongo/db/s/create_collection_coordinator.cpp
+++ b/src/mongo/db/s/create_collection_coordinator.cpp
@@ -759,6 +759,8 @@ void CreateCollectionCoordinator::_createCollectionAndIndexes(OperationContext*
}
}
+ shardkeyutil::validateShardKeyIsNotEncrypted(opCtx, nss(), *_shardKeyPattern);
+
const auto indexCreated = shardkeyutil::validateShardKeyIndexExistsOrCreateIfPossible(
opCtx,
nss(),
diff --git a/src/mongo/db/s/refine_collection_shard_key_coordinator.cpp b/src/mongo/db/s/refine_collection_shard_key_coordinator.cpp
index 5bfad53a42a..2e084f74dab 100644
--- a/src/mongo/db/s/refine_collection_shard_key_coordinator.cpp
+++ b/src/mongo/db/s/refine_collection_shard_key_coordinator.cpp
@@ -35,6 +35,7 @@
#include "mongo/db/commands.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/s/dist_lock_manager.h"
+#include "mongo/db/s/shard_key_util.h"
#include "mongo/db/s/sharding_ddl_util.h"
#include "mongo/logv2/log.h"
#include "mongo/s/catalog_cache.h"
@@ -130,6 +131,9 @@ ExecutorFuture<void> RefineCollectionShardKeyCoordinator::_runImpl(
checkCollectionUUIDMismatch(opCtx, nss(), *coll, _doc.getCollectionUUID());
}
+ shardkeyutil::validateShardKeyIsNotEncrypted(
+ opCtx, nss(), ShardKeyPattern(_newShardKey.toBSON()));
+
const auto cm = uassertStatusOK(
Grid::get(opCtx)->catalogCache()->getShardedCollectionRoutingInfoWithRefresh(
opCtx, nss()));
diff --git a/src/mongo/db/s/resharding/resharding_recipient_service.cpp b/src/mongo/db/s/resharding/resharding_recipient_service.cpp
index d6d22888511..2af2050a1f3 100644
--- a/src/mongo/db/s/resharding/resharding_recipient_service.cpp
+++ b/src/mongo/db/s/resharding/resharding_recipient_service.cpp
@@ -522,6 +522,10 @@ void ReshardingRecipientService::RecipientStateMachine::
_metadata.getTempReshardingNss(),
"validating shard key index for reshardCollection"_sd,
[&] {
+ shardkeyutil::validateShardKeyIsNotEncrypted(
+ opCtx.get(),
+ _metadata.getTempReshardingNss(),
+ ShardKeyPattern(_metadata.getReshardingKey()));
shardkeyutil::validateShardKeyIndexExistsOrCreateIfPossible(
opCtx.get(),
_metadata.getTempReshardingNss(),
diff --git a/src/mongo/db/s/shard_key_util.cpp b/src/mongo/db/s/shard_key_util.cpp
index e5719861559..d263712fa91 100644
--- a/src/mongo/db/s/shard_key_util.cpp
+++ b/src/mongo/db/s/shard_key_util.cpp
@@ -32,6 +32,8 @@
#include "mongo/db/s/shard_key_util.h"
#include "mongo/bson/simple_bsonelement_comparator.h"
+#include "mongo/crypto/encryption_fields_util.h"
+#include "mongo/db/catalog_raii.h"
#include "mongo/db/hasher.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/query/collation/collator_factory_interface.h"
@@ -202,6 +204,48 @@ bool validateShardKeyIndexExistsOrCreateIfPossible(OperationContext* opCtx,
return true;
}
+// TODO: SERVER-64187 move calls to validateShardKeyIsNotEncrypted into
+// validateShardKeyIndexExistsOrCreateIfPossible
+void validateShardKeyIsNotEncrypted(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const ShardKeyPattern& shardKeyPattern) {
+ if (!gFeatureFlagFLE2.isEnabledAndIgnoreFCV()) {
+ return;
+ }
+
+ AutoGetCollection collection(opCtx, nss, MODE_IS, AutoGetCollectionViewMode::kViewsPermitted);
+ if (!collection || collection.getView()) {
+ return;
+ }
+
+ const auto& encryptConfig = collection->getCollectionOptions().encryptedFieldConfig;
+ if (!encryptConfig) {
+ // this collection is not encrypted
+ return;
+ }
+
+ auto& encryptedFields = encryptConfig->getFields();
+ std::vector<FieldRef> encryptedFieldRefs;
+ std::transform(encryptedFields.begin(),
+ encryptedFields.end(),
+ std::back_inserter(encryptedFieldRefs),
+ [](auto& path) { return FieldRef(path.getPath()); });
+
+ for (const auto& keyFieldRef : shardKeyPattern.getKeyPatternFields()) {
+ auto match = findMatchingEncryptedField(*keyFieldRef, encryptedFieldRefs);
+ uassert(ErrorCodes::InvalidOptions,
+ str::stream() << "Sharding is not allowed on keys that are equal to, or a "
+ "prefix of, the encrypted field "
+ << match->encryptedField.dottedField(),
+ !match || !match->keyIsPrefixOrEqual);
+ uassert(
+ ErrorCodes::InvalidOptions,
+ str::stream() << "Sharding is not allowed on keys whose prefix is the encrypted field "
+ << match->encryptedField.dottedField(),
+ !match || match->keyIsPrefixOrEqual);
+ }
+}
+
std::vector<BSONObj> ValidationBehaviorsShardCollection::loadIndexes(
const NamespaceString& nss) const {
const bool includeBuildUUIDs = false;
diff --git a/src/mongo/db/s/shard_key_util.h b/src/mongo/db/s/shard_key_util.h
index cbce71b0540..dd5363df700 100644
--- a/src/mongo/db/s/shard_key_util.h
+++ b/src/mongo/db/s/shard_key_util.h
@@ -165,5 +165,10 @@ bool validShardKeyIndexExists(OperationContext* opCtx,
const boost::optional<BSONObj>& defaultCollation,
bool unique,
const ShardKeyValidationBehaviors& behaviors);
+
+void validateShardKeyIsNotEncrypted(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const ShardKeyPattern& shardKeyPattern);
+
} // namespace shardkeyutil
} // namespace mongo