Skip to content

Commit 1ef1b5e

Browse files
authored
Support the Azure VM-assigned managed identity for automatic KMS credentials (#1035)
JAVA-4706
1 parent 8e91765 commit 1ef1b5e

File tree

11 files changed

+293
-129
lines changed

11 files changed

+293
-129
lines changed

.evergreen/.evg.yml

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ functions:
237237
AWS_TEMP_SESSION_TOKEN=$CSFLE_AWS_TEMP_SESSION_TOKEN \
238238
AZURE_TENANT_ID=${azure_tenant_id} AZURE_CLIENT_ID=${azure_client_id} AZURE_CLIENT_SECRET=${azure_client_secret} \
239239
GCP_EMAIL=${gcp_email} GCP_PRIVATE_KEY=${gcp_private_key} \
240+
AZUREKMS_KEY_VAULT_ENDPOINT=${testazurekms_keyvaultendpoint} \
241+
AZUREKMS_KEY_NAME=${testazurekms_keyname} \
240242
REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \
241243
CRYPT_SHARED_LIB_PATH="${PROJECT_DIRECTORY}/crypt_shared/lib/mongo_crypt_v1.so" \
242244
.evergreen/run-tests.sh
@@ -1445,21 +1447,38 @@ tasks:
14451447
export GCPKMS_PROJECT=${GCPKMS_PROJECT}
14461448
export GCPKMS_ZONE=${GCPKMS_ZONE}
14471449
export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME}
1448-
GCPKMS_CMD="MONGODB_URI='mongodb://localhost:27017' SUCCESS=true ./.evergreen/run-mongodb-fle-gcp-auto.sh" $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/run-command.sh
1450+
GCPKMS_CMD="MONGODB_URI=mongodb://localhost:27017 PROVIDER=gcp ./.evergreen/run-fle-on-demand-credential-test.sh" $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/run-command.sh
14491451
1450-
- name: "testgcpkms-fail-task"
1451-
# testgcpkms-fail-task runs in a non-GCE environment.
1452-
# It is expected to fail to obtain GCE credentials.
1452+
- name: testazurekms-task
14531453
commands:
1454+
- command: shell.exec
1455+
type: setup
1456+
params:
1457+
working_dir: src
1458+
shell: "bash"
1459+
script: |
1460+
${PREPARE_SHELL}
1461+
echo "Copying files ... begin"
1462+
export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup}
1463+
export AZUREKMS_VMNAME=${AZUREKMS_VMNAME}
1464+
export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey
1465+
tar czf /tmp/mongo-csharp-driver.tgz .
1466+
AZUREKMS_SRC=/tmp/mongo-csharp-driver.tgz AZUREKMS_DST="~/" $DRIVERS_TOOLS/.evergreen/csfle/azurekms/copy-file.sh
1467+
echo "Copying files ... end"
1468+
echo "Untarring file ... begin"
1469+
AZUREKMS_CMD="tar xf mongo-csharp-driver.tgz" $DRIVERS_TOOLS/.evergreen/csfle/azurekms/run-command.sh
1470+
echo "Untarring file ... end"
14541471
- command: shell.exec
14551472
type: test
14561473
params:
14571474
working_dir: "src"
14581475
shell: "bash"
14591476
script: |
14601477
${PREPARE_SHELL}
1461-
MONGODB_URI='mongodb://localhost:27017' SUCCESS=false ./.evergreen/run-mongodb-fle-gcp-auto.sh
1462-
1478+
export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup}
1479+
export AZUREKMS_VMNAME=${AZUREKMS_VMNAME}
1480+
export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey
1481+
AZUREKMS_CMD="MONGODB_URI=mongodb://localhost:27017 PROVIDER=azure AZUREKMS_KEY_VAULT_ENDPOINT=${testazurekms_keyvaultendpoint} AZUREKMS_KEY_NAME=${testazurekms_keyname} ./.evergreen/run-fle-on-demand-credential-test.sh" $DRIVERS_TOOLS/.evergreen/csfle/azurekms/run-command.sh
14631482
axes:
14641483
- id: version
14651484
display_name: MongoDB Version
@@ -1694,6 +1713,47 @@ task_groups:
16941713
$DRIVERS_TOOLS/.evergreen/csfle/gcpkms/delete-instance.sh
16951714
tasks:
16961715
- testgcpkms-task
1716+
- name: testazurekms_task_group
1717+
setup_group_can_fail_task: true
1718+
setup_group_timeout_secs: 1800 # 30 minutes
1719+
setup_group:
1720+
- func: fetch source
1721+
- func: prepare resources
1722+
- func: fix absolute paths
1723+
- func: make files executable
1724+
- command: shell.exec
1725+
params:
1726+
shell: "bash"
1727+
script: |
1728+
${PREPARE_SHELL}
1729+
echo '${testazurekms_publickey}' > /tmp/testazurekms_publickey
1730+
echo '${testazurekms_privatekey}' > /tmp/testazurekms_privatekey
1731+
# Set 600 permissions on private key file. Otherwise ssh / scp may error with permissions "are too open".
1732+
chmod 600 /tmp/testazurekms_privatekey
1733+
export AZUREKMS_CLIENTID=${testazurekms_clientid}
1734+
export AZUREKMS_TENANTID=${testazurekms_tenantid}
1735+
export AZUREKMS_SECRET=${testazurekms_secret}
1736+
export AZUREKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS
1737+
export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup}
1738+
export AZUREKMS_PUBLICKEYPATH=/tmp/testazurekms_publickey
1739+
export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey
1740+
export AZUREKMS_SCOPE=${testazurekms_scope}
1741+
export AZUREKMS_VMNAME_PREFIX=JAVADRIVER
1742+
$DRIVERS_TOOLS/.evergreen/csfle/azurekms/create-and-setup-vm.sh
1743+
- command: expansions.update
1744+
params:
1745+
file: testazurekms-expansions.yml
1746+
teardown_group:
1747+
- command: shell.exec
1748+
params:
1749+
shell: "bash"
1750+
script: |
1751+
${PREPARE_SHELL}
1752+
export AZUREKMS_VMNAME=${AZUREKMS_VMNAME}
1753+
export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup}
1754+
$DRIVERS_TOOLS/.evergreen/csfle/azurekms/delete-vm.sh
1755+
tasks:
1756+
- testazurekms-task
16971757

16981758
buildvariants:
16991759

@@ -1932,4 +1992,11 @@ buildvariants:
19321992
tasks:
19331993
- name: testgcpkms_task_group
19341994
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README
1935-
- testgcpkms-fail-task
1995+
1996+
- name: testazurekms-variant
1997+
display_name: "Azure KMS"
1998+
run_on:
1999+
- debian11-small
2000+
tasks:
2001+
- name: testazurekms_task_group
2002+
batchtime: 20160 # Use a batchtime of 14 days as suggested by the CSFLE test README
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
3+
set -o xtrace
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 URI, including an optional username/password to use to connect to the server
8+
# PROVIDER Which KMS provider to test (either "gcp" or "azure")
9+
# AZUREKMS_KEY_VAULT_ENDPOINT The Azure key vault endpoint for Azure integration tests
10+
# AZUREKMS_KEY_NAME The Azure key name endpoint for Azure integration tests
11+
12+
############################################
13+
# Main Program #
14+
############################################
15+
16+
echo "Running ${PROVIDER}} Credential Acquisition Test"
17+
18+
if ! which java ; then
19+
echo "Installing java..."
20+
sudo apt install openjdk-17-jdk -y
21+
fi
22+
23+
./gradlew -Dorg.mongodb.test.uri="${MONGODB_URI}" \
24+
-Dorg.mongodb.test.fle.on.demand.credential.test.success.enabled="true" \
25+
-Dorg.mongodb.test.fle.on.demand.credential.test.azure.keyVaultEndpoint="${AZUREKMS_KEY_VAULT_ENDPOINT}" \
26+
-Dorg.mongodb.test.fle.on.demand.credential.test.azure.keyName="${AZUREKMS_KEY_NAME}" \
27+
-Dorg.mongodb.test.fle.on.demand.credential.provider="${PROVIDER}" \
28+
--stacktrace --debug --info driver-sync:test --tests ClientSideEncryptionOnDemandCredentialsTest
29+
first=$?
30+
echo $first
31+
32+
./gradlew -Dorg.mongodb.test.uri="${MONGODB_URI}" \
33+
-Dorg.mongodb.test.fle.on.demand.credential.test.success.enabled="true" \
34+
-Dorg.mongodb.test.fle.on.demand.credential.test.azure.keyVaultEndpoint="${AZUREKMS_KEY_VAULT_ENDPOINT}" \
35+
-Dorg.mongodb.test.fle.on.demand.credential.test.azure.keyName="${AZUREKMS_KEY_NAME}" \
36+
-Dorg.mongodb.test.fle.on.demand.credential.provider="${PROVIDER}" \
37+
--stacktrace --debug --info driver-reactive-streams:test --tests ClientSideEncryptionOnDemandCredentialsTest
38+
second=$?
39+
echo $second
40+
41+
if [ $first -ne 0 ]; then
42+
exit $first
43+
elif [ $second -ne 0 ]; then
44+
exit $second
45+
else
46+
exit 0
47+
fi

.evergreen/run-mongodb-fle-gcp-auto.sh

Lines changed: 0 additions & 36 deletions
This file was deleted.

.evergreen/run-tests.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ set -o errexit # Exit the script with error if any of the commands fail
2424
# AZURE_CLIENT_SECRET The Azure client secret for client-side encryption
2525
# GCP_EMAIL The GCP email for client-side encryption
2626
# GCP_PRIVATE_KEY The GCP private key for client-side encryption
27+
# AZUREKMS_KEY_VAULT_ENDPOINT The Azure key vault endpoint for integration tests
28+
# AZUREKMS_KEY_NAME The Azure key name endpoint for integration tests
2729

2830
AUTH=${AUTH:-noauth}
2931
SSL=${SSL:-nossl}
@@ -139,6 +141,9 @@ if [ "$SLOW_TESTS_ONLY" == "true" ]; then
139141
--stacktrace --info testSlowOnly
140142
else
141143
./gradlew -PjavaVersion=${JAVA_VERSION} -Dorg.mongodb.test.uri=${MONGODB_URI} \
144+
-Dorg.mongodb.test.fle.on.demand.credential.test.failure.enabled="true" \
145+
-Dorg.mongodb.test.fle.on.demand.credential.test.azure.keyVaultEndpoint="${AZUREKMS_KEY_VAULT_ENDPOINT}" \
146+
-Dorg.mongodb.test.fle.on.demand.credential.test.azure.keyName="${AZUREKMS_KEY_NAME}" \
142147
-Dorg.mongodb.test.awsAccessKeyId=${AWS_ACCESS_KEY_ID} -Dorg.mongodb.test.awsSecretAccessKey=${AWS_SECRET_ACCESS_KEY} \
143148
-Dorg.mongodb.test.tmpAwsAccessKeyId=${AWS_TEMP_ACCESS_KEY_ID} -Dorg.mongodb.test.tmpAwsSecretAccessKey=${AWS_TEMP_SECRET_ACCESS_KEY} -Dorg.mongodb.test.tmpAwsSessionToken=${AWS_TEMP_SESSION_TOKEN} \
144149
-Dorg.mongodb.test.azureTenantId=${AZURE_TENANT_ID} -Dorg.mongodb.test.azureClientId=${AZURE_CLIENT_ID} -Dorg.mongodb.test.azureClientSecret=${AZURE_CLIENT_SECRET} \
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.authentication;
18+
19+
import com.mongodb.MongoClientException;
20+
import org.bson.BsonDocument;
21+
import org.bson.json.JsonParseException;
22+
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
26+
import static com.mongodb.internal.authentication.HttpHelper.getHttpContents;
27+
28+
/**
29+
* Utility class for working with Azure authentication.
30+
*
31+
* <p>This class should not be considered a part of the public API.</p>
32+
*/
33+
public final class AzureCredentialHelper {
34+
public static BsonDocument obtainFromEnvironment() {
35+
String endpoint = "http://" + "169.254.169.254:80"
36+
+ "/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net";
37+
38+
Map<String, String> headers = new HashMap<>();
39+
headers.put("Metadata", "true");
40+
headers.put("Accept", "application/json");
41+
42+
String response = getHttpContents("GET", endpoint, headers);
43+
try {
44+
BsonDocument responseDocument = BsonDocument.parse(response);
45+
if (responseDocument.containsKey("access_token")) {
46+
return new BsonDocument("accessToken", responseDocument.get("access_token"));
47+
} else {
48+
throw new MongoClientException("The access_token is missing from Azure IMDS metadata response.");
49+
}
50+
} catch (JsonParseException e) {
51+
throw new MongoClientException("Exception parsing JSON from Azure IMDS metadata response.", e);
52+
}
53+
}
54+
55+
private AzureCredentialHelper() {
56+
}
57+
}

driver-core/src/main/com/mongodb/internal/authentication/HttpHelper.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.mongodb.internal.authentication;
1818

19-
import com.mongodb.MongoInternalException;
19+
import com.mongodb.MongoClientException;
2020
import com.mongodb.lang.NonNull;
2121

2222
import java.io.BufferedReader;
@@ -41,9 +41,10 @@ public static String getHttpContents(final String method, final String endpoint,
4141
HttpURLConnection conn = null;
4242
try {
4343
conn = (HttpURLConnection) new URL(endpoint).openConnection();
44-
conn.setRequestMethod(method);
44+
conn.setConnectTimeout(10000);
4545
conn.setReadTimeout(10000);
46-
if (headers != null) {
46+
conn.setRequestMethod(method);
47+
if (headers != null) {
4748
for (Map.Entry<String, String> kvp : headers.entrySet()) {
4849
conn.setRequestProperty(kvp.getKey(), kvp.getValue());
4950
}
@@ -61,7 +62,7 @@ public static String getHttpContents(final String method, final String endpoint,
6162
}
6263
}
6364
} catch (IOException e) {
64-
throw new MongoInternalException("Unexpected IOException", e);
65+
throw new MongoClientException("Unexpected IOException from endpoint " + endpoint + ".", e);
6566
} finally {
6667
if (conn != null) {
6768
conn.disconnect();

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.mongodb.client.model.vault.RewrapManyDataKeyOptions;
2727
import com.mongodb.crypt.capi.MongoCryptOptions;
2828
import com.mongodb.internal.authentication.AwsCredentialHelper;
29+
import com.mongodb.internal.authentication.AzureCredentialHelper;
2930
import com.mongodb.internal.authentication.GcpCredentialHelper;
3031
import com.mongodb.lang.Nullable;
3132
import org.bson.BsonDocument;
@@ -125,6 +126,10 @@ public static BsonDocument fetchCredentials(final Map<String, Map<String, Object
125126
if (kmsProvidersDocument.containsKey("gcp") && kmsProvidersDocument.get("gcp").asDocument().isEmpty()) {
126127
kmsProvidersDocument.put("gcp", GcpCredentialHelper.obtainFromEnvironment());
127128
}
129+
if (kmsProvidersDocument.containsKey("azure") && kmsProvidersDocument.get("azure").asDocument().isEmpty()) {
130+
kmsProvidersDocument.put("azure", AzureCredentialHelper.obtainFromEnvironment());
131+
}
132+
128133
return kmsProvidersDocument;
129134
}
130135

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
package com.mongodb.reactivestreams.client;
1818

1919
import com.mongodb.ClientEncryptionSettings;
20-
import com.mongodb.client.AbstractClientSideEncryptionOnDemandGcpCredentialsTest;
20+
import com.mongodb.client.AbstractClientSideEncryptionOnDemandCredentialsTest;
2121
import com.mongodb.client.vault.ClientEncryption;
2222
import com.mongodb.reactivestreams.client.syncadapter.SyncClientEncryption;
2323
import com.mongodb.reactivestreams.client.vault.ClientEncryptions;
2424

25-
public class ClientSideEncryptionOnDemandGcpCredentialsTest extends AbstractClientSideEncryptionOnDemandGcpCredentialsTest {
25+
public class ClientSideEncryptionOnDemandCredentialsTest extends AbstractClientSideEncryptionOnDemandCredentialsTest {
2626

2727
@Override
2828
public ClientEncryption getClientEncryption(final ClientEncryptionSettings settings) {

0 commit comments

Comments
 (0)