Skip to content

Commit be4afcd

Browse files
authored
Support NEED_KMS_CREDENTIALS state for CSFLE (#889)
JAVA-4503
1 parent 47fec07 commit be4afcd

File tree

11 files changed

+319
-15
lines changed

11 files changed

+319
-15
lines changed

.evergreen/.evg.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,17 @@ functions:
659659
set +o xtrace
660660
MONGODB_URI="${MONGODB_URI}" KMS_TLS_ERROR_TYPE=${KMS_TLS_ERROR_TYPE} .evergreen/run-kms-tls-tests.sh
661661
662+
"run-csfle-aws-from-environment-test":
663+
- command: shell.exec
664+
type: test
665+
params:
666+
working_dir: "src"
667+
script: |
668+
${PREPARE_SHELL}
669+
set +o xtrace
670+
MONGODB_URI="${MONGODB_URI}" AWS_ACCESS_KEY_ID=${aws_access_key_id} AWS_SECRET_ACCESS_KEY=${aws_secret_access_key} \
671+
.evergreen/run-csfle-aws-from-environment.sh
672+
662673
"publish snapshot":
663674
- command: shell.exec
664675
type: test
@@ -1361,6 +1372,17 @@ tasks:
13611372
TOPOLOGY: "server"
13621373
AUTH: "noauth"
13631374
SSL: "nossl"
1375+
1376+
- name: "test-csfle-aws-from-environment"
1377+
tags: ["csfle-aws-from-environment"]
1378+
commands:
1379+
- func: "bootstrap mongo-orchestration"
1380+
vars:
1381+
TOPOLOGY: "server"
1382+
AUTH: "noauth"
1383+
SSL: "nossl"
1384+
- func: run-csfle-aws-from-environment-test
1385+
13641386
axes:
13651387
- id: version
13661388
display_name: MongoDB Version
@@ -1772,3 +1794,9 @@ buildvariants:
17721794
display_name: "CSFLE KMS TLS"
17731795
tasks:
17741796
- name: ".kms-tls"
1797+
1798+
- matrix_name: "csfle-aws-from-environment-test"
1799+
matrix_spec: { os: "linux", version: [ "5.0" ], topology: ["standalone"] }
1800+
display_name: "CSFLE AWS From Environment"
1801+
tasks:
1802+
- name: ".csfle-aws-from-environment"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/bin/bash
2+
3+
# Don't trace since the URI contains a password that shouldn't show up in the logs
4+
set -o errexit # Exit the script with error if any of the commands fail
5+
6+
# Supported/used environment variables:
7+
# MONGODB_URI Set the suggested connection MONGODB_URI (including credentials and topology info)
8+
# AWS_ACCESS_KEY_ID The AWS access key identifier for client-side encryption
9+
# AWS_SECRET_ACCESS_KEY The AWS secret access key for client-side encryption
10+
11+
############################################
12+
# Main Program #
13+
############################################
14+
RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE:-$0}")"
15+
. "${RELATIVE_DIR_PATH}/javaConfig.bash"
16+
echo "Running CSFLE AWS from environment tests"
17+
18+
./gradlew -version
19+
20+
export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
21+
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
22+
23+
./gradlew --stacktrace --info -Dorg.mongodb.test.uri=${MONGODB_URI} \
24+
--no-build-cache driver-sync:cleanTest driver-sync:test --tests ClientSideEncryptionAwsCredentialFromEnvironmentTest
25+
first=$?
26+
echo $first
27+
28+
./gradlew --stacktrace --info -Dorg.mongodb.test.uri=${MONGODB_URI} \
29+
--no-build-cache driver-reactive-streams:cleanTest driver-reactive-streams:test --tests ClientSideEncryptionAwsCredentialFromEnvironmentTest
30+
second=$?
31+
echo $second
32+
33+
if [ $first -ne 0 ]; then
34+
exit $first
35+
elif [ $second -ne 0 ]; then
36+
exit $second
37+
else
38+
exit 0
39+
fi

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ ext {
4848
nettyTcnativeBoringsslVersion = '2.0.46.Final'
4949
snappyVersion = '1.1.8.4'
5050
zstdVersion = '1.5.0-4'
51-
mongoCryptVersion = '1.3.0'
51+
mongoCryptVersion = '1.4.0-alpha0'
5252
projectReactorVersion = 'Californium-SR23'
5353
junitBomVersion = '5.8.1'
5454
gitVersion = getGitVersion()

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@
1616

1717
package com.mongodb.internal.capi;
1818

19+
import com.mongodb.AwsCredential;
1920
import com.mongodb.Block;
2021
import com.mongodb.ConnectionString;
2122
import com.mongodb.MongoClientException;
2223
import com.mongodb.MongoClientSettings;
2324
import com.mongodb.connection.ClusterSettings;
2425
import com.mongodb.connection.SocketSettings;
2526
import com.mongodb.crypt.capi.MongoCryptOptions;
27+
import com.mongodb.internal.authentication.AwsCredentialHelper;
2628
import org.bson.BsonDocument;
2729
import org.bson.BsonDocumentWrapper;
30+
import org.bson.BsonString;
2831
import org.bson.Document;
2932
import org.bson.codecs.DocumentCodec;
3033

@@ -40,13 +43,36 @@ public static MongoCryptOptions createMongoCryptOptions(final Map<String, Map<St
4043
final Map<String, BsonDocument> namespaceToLocalSchemaDocumentMap) {
4144
MongoCryptOptions.Builder mongoCryptOptionsBuilder = MongoCryptOptions.builder();
4245

46+
BsonDocument bsonKmsProviders = getKmsProvidersAsBsonDocument(kmsProviders);
47+
mongoCryptOptionsBuilder.kmsProviderOptions(bsonKmsProviders);
48+
mongoCryptOptionsBuilder.localSchemaMap(namespaceToLocalSchemaDocumentMap);
49+
mongoCryptOptionsBuilder.needsKmsCredentialsStateEnabled(true);
50+
return mongoCryptOptionsBuilder.build();
51+
}
52+
53+
public static BsonDocument fetchCredentials(final Map<String, Map<String, Object>> kmsProviders) {
54+
BsonDocument kmsProvidersDocument = MongoCryptHelper.getKmsProvidersAsBsonDocument(kmsProviders);
55+
if (kmsProvidersDocument.containsKey("aws") && kmsProvidersDocument.get("aws").asDocument().isEmpty()) {
56+
AwsCredential awsCredential = AwsCredentialHelper.obtainFromEnvironment();
57+
if (awsCredential != null) {
58+
BsonDocument awsCredentialDocument = new BsonDocument();
59+
awsCredentialDocument.put("accessKeyId", new BsonString(awsCredential.getAccessKeyId()));
60+
awsCredentialDocument.put("secretAccessKey", new BsonString(awsCredential.getSecretAccessKey()));
61+
if (awsCredential.getSessionToken() != null) {
62+
awsCredentialDocument.put("sessionToken", new BsonString(awsCredential.getSessionToken()));
63+
}
64+
kmsProvidersDocument.put("aws", awsCredentialDocument);
65+
}
66+
}
67+
return kmsProvidersDocument;
68+
}
69+
70+
private static BsonDocument getKmsProvidersAsBsonDocument(final Map<String, Map<String, Object>> kmsProviders) {
4371
BsonDocument bsonKmsProviders = new BsonDocument();
4472
for (Map.Entry<String, Map<String, Object>> entry : kmsProviders.entrySet()) {
4573
bsonKmsProviders.put(entry.getKey(), new BsonDocumentWrapper<>(new Document(entry.getValue()), new DocumentCodec()));
4674
}
47-
mongoCryptOptionsBuilder.kmsProviderOptions(bsonKmsProviders);
48-
mongoCryptOptionsBuilder.localSchemaMap(namespaceToLocalSchemaDocumentMap);
49-
return mongoCryptOptionsBuilder.build();
75+
return bsonKmsProviders;
5076
}
5177

5278
@SuppressWarnings("unchecked")

driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/Crypt.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.mongodb.crypt.capi.MongoKeyDecryptor;
3030
import com.mongodb.diagnostics.logging.Logger;
3131
import com.mongodb.diagnostics.logging.Loggers;
32+
import com.mongodb.internal.capi.MongoCryptHelper;
3233
import com.mongodb.lang.Nullable;
3334
import com.mongodb.reactivestreams.client.MongoClient;
3435
import org.bson.BsonBinary;
@@ -39,6 +40,7 @@
3940
import reactor.core.publisher.MonoSink;
4041

4142
import java.io.Closeable;
43+
import java.util.Map;
4244
import java.util.function.Supplier;
4345

4446
import static com.mongodb.assertions.Assertions.notNull;
@@ -48,6 +50,7 @@
4850
public class Crypt implements Closeable {
4951
private static final Logger LOGGER = Loggers.getLogger("client");
5052
private final MongoCrypt mongoCrypt;
53+
private final Map<String, Map<String, Object>> kmsProviders;
5154
private final CollectionInfoRetriever collectionInfoRetriever;
5255
private final CommandMarker commandMarker;
5356
private final KeyRetriever keyRetriever;
@@ -60,11 +63,13 @@ public class Crypt implements Closeable {
6063
* Create an instance to use for explicit encryption and decryption, and data key creation.
6164
*
6265
* @param mongoCrypt the mongoCrypt wrapper
66+
* @param kmsProviders the kms providers
6367
* @param keyRetriever the key retriever
6468
* @param keyManagementService the key management service
6569
*/
66-
Crypt(final MongoCrypt mongoCrypt, final KeyRetriever keyRetriever, final KeyManagementService keyManagementService) {
67-
this(mongoCrypt, null, null, keyRetriever, keyManagementService, false, null);
70+
Crypt(final MongoCrypt mongoCrypt, final Map<String, Map<String, Object>> kmsProviders, final KeyRetriever keyRetriever,
71+
final KeyManagementService keyManagementService) {
72+
this(mongoCrypt, kmsProviders, null, null, keyRetriever, keyManagementService, false, null);
6873
}
6974

7075
/**
@@ -77,13 +82,15 @@ public class Crypt implements Closeable {
7782
* @param commandMarker the command marker
7883
*/
7984
Crypt(final MongoCrypt mongoCrypt,
85+
final Map<String, Map<String, Object>> kmsProviders,
8086
@Nullable final CollectionInfoRetriever collectionInfoRetriever,
8187
@Nullable final CommandMarker commandMarker,
8288
final KeyRetriever keyRetriever,
8389
final KeyManagementService keyManagementService,
8490
final boolean bypassAutoEncryption,
8591
@Nullable final MongoClient internalClient) {
8692
this.mongoCrypt = mongoCrypt;
93+
this.kmsProviders = kmsProviders;
8794
this.collectionInfoRetriever = collectionInfoRetriever;
8895
this.commandMarker = commandMarker;
8996
this.keyRetriever = keyRetriever;
@@ -213,6 +220,9 @@ private void executeStateMachineWithSink(final MongoCryptContext cryptContext, @
213220
case NEED_MONGO_MARKINGS:
214221
mark(cryptContext, databaseName, sink);
215222
break;
223+
case NEED_KMS_CREDENTIALS:
224+
fetchCredentials(cryptContext, databaseName, sink);
225+
break;
216226
case NEED_MONGO_KEYS:
217227
fetchKeys(cryptContext, databaseName, sink);
218228
break;
@@ -227,6 +237,16 @@ private void executeStateMachineWithSink(final MongoCryptContext cryptContext, @
227237
}
228238
}
229239

240+
private void fetchCredentials(final MongoCryptContext cryptContext, @Nullable final String databaseName,
241+
final MonoSink<RawBsonDocument> sink) {
242+
try {
243+
cryptContext.provideKmsProviderCredentials(MongoCryptHelper.fetchCredentials(kmsProviders));
244+
executeStateMachineWithSink(cryptContext, databaseName, sink);
245+
} catch (Exception e) {
246+
sink.error(e);
247+
}
248+
}
249+
230250
private void collInfo(final MongoCryptContext cryptContext,
231251
@Nullable final String databaseName,
232252
final MonoSink<RawBsonDocument> sink) {

driver-reactive-streams/src/main/com/mongodb/reactivestreams/client/internal/crypt/Crypts.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static Crypt createCrypt(final MongoClientImpl client, final AutoEncrypti
5252
? internalClient : MongoClients.create(keyVaultMongoClientSettings);
5353
return new Crypt(MongoCrypts.create(createMongoCryptOptions(options.getKmsProviders(),
5454
options.getSchemaMap())),
55+
options.getKmsProviders(),
5556
options.isBypassAutoEncryption() ? null : new CollectionInfoRetriever(collectionInfoRetrieverClient),
5657
new CommandMarker(options.isBypassAutoEncryption(), options.getExtraOptions()),
5758
new KeyRetriever(keyVaultClient, new MongoNamespace(options.getKeyVaultNamespace())),
@@ -63,6 +64,7 @@ public static Crypt createCrypt(final MongoClientImpl client, final AutoEncrypti
6364
public static Crypt create(final MongoClient keyVaultClient, final ClientEncryptionSettings options) {
6465
return new Crypt(MongoCrypts.create(
6566
createMongoCryptOptions(options.getKmsProviders(), null)),
67+
options.getKmsProviders(),
6668
new KeyRetriever(keyVaultClient, new MongoNamespace(options.getKeyVaultNamespace())),
6769
createKeyManagementService(options.getKmsProviderSslContextMap()));
6870
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.reactivestreams.client;
18+
19+
import com.mongodb.ClientEncryptionSettings;
20+
import com.mongodb.MongoClientSettings;
21+
import com.mongodb.client.AbstractClientSideEncryptionAwsCredentialFromEnvironmentTest;
22+
import com.mongodb.client.MongoClient;
23+
import com.mongodb.client.vault.ClientEncryption;
24+
import com.mongodb.lang.NonNull;
25+
import com.mongodb.reactivestreams.client.syncadapter.SyncClientEncryption;
26+
import com.mongodb.reactivestreams.client.syncadapter.SyncMongoClient;
27+
import com.mongodb.reactivestreams.client.vault.ClientEncryptions;
28+
29+
public class ClientSideEncryptionAwsCredentialFromEnvironmentTest extends AbstractClientSideEncryptionAwsCredentialFromEnvironmentTest {
30+
@Override
31+
@NonNull
32+
protected ClientEncryption createClientEncryption(@NonNull final ClientEncryptionSettings settings) {
33+
return new SyncClientEncryption(ClientEncryptions.create(settings));
34+
}
35+
36+
@Override
37+
@NonNull
38+
protected MongoClient createMongoClient(@NonNull final MongoClientSettings settings) {
39+
return new SyncMongoClient(MongoClients.create(settings));
40+
}
41+
}

driver-sync/src/main/com/mongodb/client/internal/Crypt.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.mongodb.crypt.capi.MongoDataKeyOptions;
2929
import com.mongodb.crypt.capi.MongoExplicitEncryptOptions;
3030
import com.mongodb.crypt.capi.MongoKeyDecryptor;
31+
import com.mongodb.internal.capi.MongoCryptHelper;
3132
import com.mongodb.lang.Nullable;
3233
import org.bson.BsonBinary;
3334
import org.bson.BsonDocument;
@@ -38,13 +39,15 @@
3839
import java.io.IOException;
3940
import java.io.InputStream;
4041
import java.nio.ByteBuffer;
42+
import java.util.Map;
4143

4244
import static com.mongodb.assertions.Assertions.notNull;
4345
import static com.mongodb.crypt.capi.MongoCryptContext.State;
4446

4547
class Crypt implements Closeable {
4648

4749
private final MongoCrypt mongoCrypt;
50+
private final Map<String, Map<String, Object>> kmsProviders;
4851
private final CollectionInfoRetriever collectionInfoRetriever;
4952
private final CommandMarker commandMarker;
5053
private final KeyRetriever keyRetriever;
@@ -56,26 +59,31 @@ class Crypt implements Closeable {
5659
* Create an instance to use for explicit encryption and decryption, and data key creation.
5760
*
5861
* @param mongoCrypt the mongoCrypt wrapper
62+
* @param kmsProviders the kms providers
5963
* @param keyRetriever the key retriever
6064
* @param keyManagementService the key management service
6165
*/
62-
Crypt(final MongoCrypt mongoCrypt, final KeyRetriever keyRetriever, final KeyManagementService keyManagementService) {
63-
this(mongoCrypt, null, null, keyRetriever, keyManagementService, false, null);
66+
Crypt(final MongoCrypt mongoCrypt, final Map<String, Map<String, Object>> kmsProviders, final KeyRetriever keyRetriever,
67+
final KeyManagementService keyManagementService) {
68+
this(mongoCrypt, kmsProviders, null, null, keyRetriever, keyManagementService, false, null);
6469
}
6570

6671
/**
6772
* Create an instance to use for auto-encryption and auto-decryption.
68-
*
69-
* @param mongoCrypt the mongoCrypt wrapper
70-
* @param keyRetriever the key retriever
71-
* @param keyManagementService the key management service
73+
* @param mongoCrypt the mongoCrypt wrapper
74+
* @param kmsProviders the KMS provider credentials
7275
* @param collectionInfoRetriever the collection info retriever
7376
* @param commandMarker the command marker
77+
* @param keyRetriever the key retriever
78+
* @param keyManagementService the key management service
7479
*/
75-
Crypt(final MongoCrypt mongoCrypt, @Nullable final CollectionInfoRetriever collectionInfoRetriever,
76-
@Nullable final CommandMarker commandMarker, final KeyRetriever keyRetriever,
77-
final KeyManagementService keyManagementService, final boolean bypassAutoEncryption, @Nullable final MongoClient internalClient) {
80+
Crypt(final MongoCrypt mongoCrypt, final Map<String, Map<String, Object>> kmsProviders,
81+
@Nullable final CollectionInfoRetriever collectionInfoRetriever,
82+
@Nullable final CommandMarker commandMarker, final KeyRetriever keyRetriever,
83+
final KeyManagementService keyManagementService, final boolean bypassAutoEncryption,
84+
@Nullable final MongoClient internalClient) {
7885
this.mongoCrypt = mongoCrypt;
86+
this.kmsProviders = kmsProviders;
7987
this.collectionInfoRetriever = collectionInfoRetriever;
8088
this.commandMarker = commandMarker;
8189
this.keyRetriever = keyRetriever;
@@ -241,6 +249,9 @@ private RawBsonDocument executeStateMachine(final MongoCryptContext cryptContext
241249
case NEED_MONGO_MARKINGS:
242250
mark(cryptContext, databaseName);
243251
break;
252+
case NEED_KMS_CREDENTIALS:
253+
fetchCredentials(cryptContext);
254+
break;
244255
case NEED_MONGO_KEYS:
245256
fetchKeys(cryptContext);
246257
break;
@@ -255,6 +266,10 @@ private RawBsonDocument executeStateMachine(final MongoCryptContext cryptContext
255266
}
256267
}
257268

269+
private void fetchCredentials(final MongoCryptContext cryptContext) {
270+
cryptContext.provideKmsProviderCredentials(MongoCryptHelper.fetchCredentials(kmsProviders));
271+
}
272+
258273
private void collInfo(final MongoCryptContext cryptContext, final String databaseName) {
259274
try {
260275
BsonDocument collectionInfo = collectionInfoRetriever.filter(databaseName, cryptContext.getMongoOperation());

driver-sync/src/main/com/mongodb/client/internal/Crypts.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public static Crypt createCrypt(final MongoClientImpl client, final AutoEncrypti
4646
? internalClient : MongoClients.create(keyVaultMongoClientSettings);
4747
return new Crypt(MongoCrypts.create(createMongoCryptOptions(options.getKmsProviders(),
4848
options.getSchemaMap())),
49+
options.getKmsProviders(),
4950
options.isBypassAutoEncryption() ? null : new CollectionInfoRetriever(collectionInfoRetrieverClient),
5051
new CommandMarker(options.isBypassAutoEncryption(), options.getExtraOptions()),
5152
new KeyRetriever(keyVaultClient, new MongoNamespace(options.getKeyVaultNamespace())),
@@ -57,6 +58,7 @@ public static Crypt createCrypt(final MongoClientImpl client, final AutoEncrypti
5758
static Crypt create(final MongoClient keyVaultClient, final ClientEncryptionSettings options) {
5859
return new Crypt(MongoCrypts.create(
5960
createMongoCryptOptions(options.getKmsProviders(), null)),
61+
options.getKmsProviders(),
6062
createKeyRetriever(keyVaultClient, options.getKeyVaultNamespace()),
6163
createKeyManagementService(options.getKmsProviderSslContextMap()));
6264
}

0 commit comments

Comments
 (0)