Skip to content

Commit b925162

Browse files
authored
Added support for client side encryption shared library (#955)
JAVA-4459
1 parent a2e3beb commit b925162

File tree

78 files changed

+7212
-185
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+7212
-185
lines changed

.evergreen/.evg.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,15 @@ functions:
165165
script: |
166166
DRIVERS_TOOLS="${DRIVERS_TOOLS}" sh ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-local.sh
167167
168+
"download crypt_shared":
169+
- command: shell.exec
170+
params:
171+
background: true
172+
script: |
173+
${PREPARE_SHELL}
174+
/opt/mongodbtoolchain/v3/bin/python3 ${DRIVERS_TOOLS}/.evergreen/mongodl.py --component crypt_shared --version latest \
175+
--only=lib/mongo_crypt_v1.so --out ${PROJECT_DIRECTORY}/crypt_shared/
176+
168177
"run load-balancer":
169178
- command: shell.exec
170179
params:
@@ -229,6 +238,7 @@ functions:
229238
AZURE_TENANT_ID=${azure_tenant_id} AZURE_CLIENT_ID=${azure_client_id} AZURE_CLIENT_SECRET=${azure_client_secret} \
230239
GCP_EMAIL=${gcp_email} GCP_PRIVATE_KEY=${gcp_private_key} \
231240
REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \
241+
CRYPT_SHARED_LIB_PATH="${PROJECT_DIRECTORY}/crypt_shared/lib/mongo_crypt_v1.so" \
232242
.evergreen/run-tests.sh
233243
234244
"run load-balancer tests":
@@ -784,6 +794,7 @@ tasks:
784794
commands:
785795
- func: "start-kms-kmip-server"
786796
- func: "bootstrap mongo-orchestration"
797+
- func: "download crypt_shared"
787798
- func: "run tests"
788799

789800
- name: load-balancer-test

.evergreen/run-tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ else
144144
-Dorg.mongodb.test.azureTenantId=${AZURE_TENANT_ID} -Dorg.mongodb.test.azureClientId=${AZURE_CLIENT_ID} -Dorg.mongodb.test.azureClientSecret=${AZURE_CLIENT_SECRET} \
145145
-Dorg.mongodb.test.gcpEmail=${GCP_EMAIL} -Dorg.mongodb.test.gcpPrivateKey=${GCP_PRIVATE_KEY} \
146146
${MULTI_MONGOS_URI_SYSTEM_PROPERTY} ${API_VERSION} ${GRADLE_EXTRA_VARS} ${ASYNC_TYPE} \
147+
-Dorg.mongodb.test.crypt.shared.lib..path=${CRYPT_SHARED_LIB_PATH} \
147148
${JAVA_SYSPROP_NETTY_SSL_PROVIDER} \
148149
--stacktrace --info --continue test
149150
fi

config/checkstyle/suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
<suppress checks="MethodLength" files="PojoRoundTripTest"/>
3333
<suppress checks="MethodLength" files="AbstractUnifiedTest"/>
34+
<suppress checks="MethodLength" files="AbstractClientSideEncryptionTest"/>
3435
<suppress checks="MethodLength" files="AggregatesSearchIntegrationTest"/>
3536
<suppress checks="MethodLength" files="ConnectionString"/>
3637

driver-core/src/main/com/mongodb/AutoEncryptionSettings.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ public Builder schemaMap(final Map<String, BsonDocument> schemaMap) {
172172
/**
173173
* Sets the extra options.
174174
*
175+
* <p>
176+
* <strong>Note:</strong> When setting {@code cryptSharedLibPath}, the override path must be given as a path to the shared
177+
* crypt library file itself, and not simply the directory that contains it.
178+
* </p>
179+
*
175180
* @param extraOptions the extra options, which may not be null
176181
* @return this
177182
* @see #getExtraOptions()
@@ -218,7 +223,7 @@ public Builder encryptedFieldsMap(final Map<String, BsonDocument> encryptedField
218223
* Enable or disable automatic analysis of outgoing commands.
219224
*
220225
* <p>Set bypassQueryAnalysis to true to use explicit encryption on indexed fields
221-
* without the MongoDB Enterprise Advanced licensed csfle shared library.</p>
226+
* without the MongoDB Enterprise Advanced licensed crypt shared library.</p>
222227
*
223228
* @param bypassQueryAnalysis whether query analysis should be bypassed
224229
* @return this
@@ -413,6 +418,10 @@ public Map<String, BsonDocument> getSchemaMap() {
413418
* the system path.</li>
414419
* <li>mongocryptdSpawnArgs: Used to control the behavior of mongocryptd when the driver spawns it. By default, the driver spawns
415420
* mongocryptd with the single command line argument {@code "--idleShutdownTimeoutSecs=60"}</li>
421+
* <li>cryptSharedLibPath: Optional, override the path used to load the crypt shared library. Note: All MongoClient objects in the
422+
* same process should use the same setting for cryptSharedLibPath, as it is an error to load more that one crypt shared library
423+
* simultaneously in a single operating system process.</li>
424+
* <li>cryptSharedLibRequired: boolean, if 'true', refuse to continue encryption without a crypt shared library.</li>
416425
* </ul>
417426
*
418427
* @return the extra options map
@@ -460,7 +469,7 @@ public Map<String, BsonDocument> getEncryptedFieldsMap() {
460469
* Gets whether automatic analysis of outgoing commands is set.
461470
*
462471
* <p>Set bypassQueryAnalysis to true to use explicit encryption on indexed fields
463-
* without the MongoDB Enterprise Advanced licensed csfle shared library.</p>
472+
* without the MongoDB Enterprise Advanced licensed crypt shared library.</p>
464473
*
465474
* @return true if query analysis should be bypassed
466475
* @since 4.7

driver-core/src/main/com/mongodb/internal/capi/MongoCryptHelper.java

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@
1616

1717
package com.mongodb.internal.capi;
1818

19+
import com.mongodb.AutoEncryptionSettings;
1920
import com.mongodb.AwsCredential;
20-
import com.mongodb.Block;
21+
import com.mongodb.ClientEncryptionSettings;
2122
import com.mongodb.ConnectionString;
2223
import com.mongodb.MongoClientException;
2324
import com.mongodb.MongoClientSettings;
2425
import com.mongodb.MongoConfigurationException;
25-
import com.mongodb.connection.ClusterSettings;
26-
import com.mongodb.connection.SocketSettings;
2726
import com.mongodb.crypt.capi.MongoCryptOptions;
2827
import com.mongodb.internal.authentication.AwsCredentialHelper;
2928
import com.mongodb.lang.Nullable;
@@ -41,25 +40,41 @@
4140
import java.util.function.Supplier;
4241

4342
import static java.lang.String.format;
43+
import static java.util.Collections.emptyList;
44+
import static java.util.Collections.emptyMap;
45+
import static java.util.Collections.singletonList;
4446

4547
public final class MongoCryptHelper {
4648

47-
public static MongoCryptOptions createMongoCryptOptions(final Map<String, Map<String, Object>> kmsProviders) {
48-
return createMongoCryptOptions(kmsProviders, null, null, false);
49+
public static MongoCryptOptions createMongoCryptOptions(final ClientEncryptionSettings settings) {
50+
return createMongoCryptOptions(settings.getKmsProviders(), false, emptyList(), emptyMap(), null, null);
4951
}
5052

51-
public static MongoCryptOptions createMongoCryptOptions(final Map<String, Map<String, Object>> kmsProviders,
53+
public static MongoCryptOptions createMongoCryptOptions(final AutoEncryptionSettings settings) {
54+
return createMongoCryptOptions(
55+
settings.getKmsProviders(),
56+
settings.isBypassQueryAnalysis(),
57+
settings.isBypassAutoEncryption() ? emptyList() : singletonList("$SYSTEM"),
58+
settings.getExtraOptions(),
59+
settings.getSchemaMap(),
60+
settings.getEncryptedFieldsMap());
61+
}
62+
63+
private static MongoCryptOptions createMongoCryptOptions(
64+
final Map<String, Map<String, Object>> kmsProviders,
65+
final boolean bypassQueryAnalysis,
66+
final List<String> searchPaths,
67+
@Nullable final Map<String, Object> extraOptions,
5268
@Nullable final Map<String, BsonDocument> localSchemaMap,
53-
@Nullable final Map<String, BsonDocument> encryptedFieldsMap,
54-
final boolean bypassQueryAnalysis) {
69+
@Nullable final Map<String, BsonDocument> encryptedFieldsMap) {
5570
MongoCryptOptions.Builder mongoCryptOptionsBuilder = MongoCryptOptions.builder();
56-
57-
BsonDocument bsonKmsProviders = getKmsProvidersAsBsonDocument(kmsProviders);
58-
mongoCryptOptionsBuilder.kmsProviderOptions(bsonKmsProviders);
59-
mongoCryptOptionsBuilder.needsKmsCredentialsStateEnabled(true);
71+
mongoCryptOptionsBuilder.kmsProviderOptions(getKmsProvidersAsBsonDocument(kmsProviders));
72+
mongoCryptOptionsBuilder.bypassQueryAnalysis(bypassQueryAnalysis);
73+
mongoCryptOptionsBuilder.searchPaths(searchPaths);
74+
mongoCryptOptionsBuilder.extraOptions(toBsonDocument(extraOptions));
6075
mongoCryptOptionsBuilder.localSchemaMap(localSchemaMap);
6176
mongoCryptOptionsBuilder.encryptedFieldsMap(encryptedFieldsMap);
62-
mongoCryptOptionsBuilder.bypassQueryAnalysis(bypassQueryAnalysis);
77+
mongoCryptOptionsBuilder.needsKmsCredentialsStateEnabled(true);
6378
return mongoCryptOptionsBuilder.build();
6479
}
6580
public static BsonDocument fetchCredentials(final Map<String, Map<String, Object>> kmsProviders,
@@ -82,7 +97,7 @@ public static BsonDocument fetchCredentials(final Map<String, Map<String, Object
8297
+ " The returned value is %s.",
8398
kmsProviderName, kmsProviderCredential == null ? "null" : "empty"));
8499
}
85-
addToKmsProviderDocument(kmsProvidersDocument, kmsProviderName, kmsProviderCredential);
100+
kmsProvidersDocument.put(kmsProviderName, toBsonDocument(kmsProviderCredential));
86101
}
87102
if (kmsProvidersDocument.containsKey("aws") && kmsProvidersDocument.get("aws").asDocument().isEmpty()) {
88103
AwsCredential awsCredential = AwsCredentialHelper.obtainFromEnvironment();
@@ -101,20 +116,20 @@ public static BsonDocument fetchCredentials(final Map<String, Map<String, Object
101116

102117
private static BsonDocument getKmsProvidersAsBsonDocument(final Map<String, Map<String, Object>> kmsProviders) {
103118
BsonDocument bsonKmsProviders = new BsonDocument();
104-
for (Map.Entry<String, Map<String, Object>> entry : kmsProviders.entrySet()) {
105-
addToKmsProviderDocument(bsonKmsProviders, entry.getKey(), entry.getValue());
106-
}
119+
kmsProviders.forEach((k, v) -> bsonKmsProviders.put(k, toBsonDocument(v)));
107120
return bsonKmsProviders;
108121
}
109122

110-
private static void addToKmsProviderDocument(final BsonDocument kmsProvidersDocument, final String kmsProvider,
111-
final Map<String, Object> kmsProviderCredential) {
112-
kmsProvidersDocument.put(kmsProvider, new BsonDocumentWrapper<>(new Document(kmsProviderCredential), new DocumentCodec()));
123+
private static BsonDocument toBsonDocument(final Map<String, Object> optionsMap) {
124+
if (optionsMap == null) {
125+
return new BsonDocument();
126+
}
127+
return new BsonDocumentWrapper<>(new Document(optionsMap), new DocumentCodec());
113128
}
114129

115130
@SuppressWarnings("unchecked")
116131
public static List<String> createMongocryptdSpawnArgs(final Map<String, Object> options) {
117-
List<String> spawnArgs = new ArrayList<String>();
132+
List<String> spawnArgs = new ArrayList<>();
118133

119134
String path = options.containsKey("mongocryptdSpawnPath")
120135
? (String) options.get("mongocryptdSpawnPath")
@@ -135,18 +150,10 @@ public static List<String> createMongocryptdSpawnArgs(final Map<String, Object>
135150
public static MongoClientSettings createMongocryptdClientSettings(final String connectionString) {
136151

137152
return MongoClientSettings.builder()
138-
.applyToClusterSettings(new Block<ClusterSettings.Builder>() {
139-
@Override
140-
public void apply(final ClusterSettings.Builder builder) {
141-
builder.serverSelectionTimeout(10, TimeUnit.SECONDS);
142-
}
143-
})
144-
.applyToSocketSettings(new Block<SocketSettings.Builder>() {
145-
@Override
146-
public void apply(final SocketSettings.Builder builder) {
147-
builder.readTimeout(10, TimeUnit.SECONDS);
148-
builder.connectTimeout(10, TimeUnit.SECONDS);
149-
}
153+
.applyToClusterSettings(builder -> builder.serverSelectionTimeout(10, TimeUnit.SECONDS))
154+
.applyToSocketSettings(builder -> {
155+
builder.readTimeout(10, TimeUnit.SECONDS);
156+
builder.connectTimeout(10, TimeUnit.SECONDS);
150157
})
151158
.applyConnectionString(new ConnectionString((connectionString != null)
152159
? connectionString : "mongodb://localhost:27020"))
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.internal.capi;
18+
19+
import com.mongodb.AutoEncryptionSettings;
20+
import com.mongodb.ClientEncryptionSettings;
21+
import com.mongodb.crypt.capi.MongoCryptOptions;
22+
import org.bson.BsonDocument;
23+
import org.junit.jupiter.api.Test;
24+
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
28+
import static java.util.Collections.emptyList;
29+
import static java.util.Collections.emptyMap;
30+
import static java.util.Collections.singletonList;
31+
import static org.junit.jupiter.api.Assertions.assertEquals;
32+
33+
public class MongoCryptHelperTest {
34+
35+
@Test
36+
public void createsExpectedMongoCryptOptionsUsingClientEncryptionSettings() {
37+
38+
Map<String, Map<String, Object>> kmsProvidersRaw = new HashMap<>();
39+
kmsProvidersRaw.put("provider", new HashMap<String, Object>(){{
40+
put("test", "test");
41+
}});
42+
43+
ClientEncryptionSettings settings = ClientEncryptionSettings
44+
.builder()
45+
.kmsProviders(kmsProvidersRaw)
46+
.keyVaultNamespace("a.b")
47+
.build();
48+
MongoCryptOptions mongoCryptOptions = MongoCryptHelper.createMongoCryptOptions(settings);
49+
50+
51+
BsonDocument expectedKmsProviders = BsonDocument.parse("{provider: {test: 'test'}}");
52+
MongoCryptOptions expectedMongoCryptOptions = MongoCryptOptions
53+
.builder()
54+
.kmsProviderOptions(expectedKmsProviders)
55+
.needsKmsCredentialsStateEnabled(true)
56+
.build();
57+
58+
assertMongoCryptOptions(expectedMongoCryptOptions, mongoCryptOptions);
59+
}
60+
61+
@Test
62+
public void createsExpectedMongoCryptOptionsUsingAutoEncryptionSettings() {
63+
64+
Map<String, Map<String, Object>> kmsProvidersRaw = new HashMap<>();
65+
kmsProvidersRaw.put("provider", new HashMap<String, Object>(){{
66+
put("test", "test");
67+
}});
68+
69+
AutoEncryptionSettings.Builder autoEncryptionSettingsBuilder = AutoEncryptionSettings
70+
.builder()
71+
.kmsProviders(kmsProvidersRaw)
72+
.keyVaultNamespace("a.b");
73+
MongoCryptOptions mongoCryptOptions = MongoCryptHelper.createMongoCryptOptions(autoEncryptionSettingsBuilder.build());
74+
75+
BsonDocument expectedKmsProviders = BsonDocument.parse("{provider: {test: 'test'}}");
76+
MongoCryptOptions.Builder mongoCryptOptionsBuilder = MongoCryptOptions
77+
.builder()
78+
.kmsProviderOptions(expectedKmsProviders)
79+
.needsKmsCredentialsStateEnabled(true)
80+
.encryptedFieldsMap(emptyMap())
81+
.localSchemaMap(emptyMap())
82+
.searchPaths(singletonList("$SYSTEM"));
83+
84+
assertMongoCryptOptions(mongoCryptOptionsBuilder.build(), mongoCryptOptions);
85+
86+
// Ensure search Paths is empty when bypassAutoEncryption is true
87+
autoEncryptionSettingsBuilder.bypassAutoEncryption(true);
88+
mongoCryptOptions = MongoCryptHelper.createMongoCryptOptions(autoEncryptionSettingsBuilder.build());
89+
assertMongoCryptOptions(mongoCryptOptionsBuilder.searchPaths(emptyList()).build(), mongoCryptOptions);
90+
}
91+
92+
void assertMongoCryptOptions(final MongoCryptOptions expected, final MongoCryptOptions actual) {
93+
assertEquals(expected.getAwsKmsProviderOptions(), actual.getAwsKmsProviderOptions(), "AwsKmsProviderOptions not equal");
94+
assertEquals(expected.getEncryptedFieldsMap(), actual.getEncryptedFieldsMap(), "EncryptedFieldsMap not equal");
95+
assertEquals(expected.getExtraOptions(), actual.getExtraOptions(), "ExtraOptions not equal");
96+
assertEquals(expected.getKmsProviderOptions(), actual.getKmsProviderOptions(), "KmsProviderOptions not equal");
97+
assertEquals(expected.getLocalKmsProviderOptions(), actual.getLocalKmsProviderOptions(), "LocalKmsProviderOptions not equal");
98+
assertEquals(expected.getLocalSchemaMap(), actual.getLocalSchemaMap(), "LocalSchemaMap not equal");
99+
assertEquals(expected.getSearchPaths(), actual.getSearchPaths(), "SearchPaths not equal");
100+
assertEquals(expected.isBypassQueryAnalysis(), actual.isBypassQueryAnalysis(), "isBypassQueryAnalysis not equal");
101+
assertEquals(expected.isNeedsKmsCredentialsStateEnabled(), actual.isNeedsKmsCredentialsStateEnabled(), "isNeedsKmsCredentialsStateEnabled not equal");
102+
}
103+
}

0 commit comments

Comments
 (0)