Skip to content

Commit 7b1904d

Browse files
authored
Use AWS SDK, if available, for AWS credential fetching (#1017)
* Add optional dependencies for both AWS SDK v2 and v1 * For credential fetching, use AWS SDK v2 if available. Otherwise use AWS SDK v1. Otherwise, use the existing driver implementations JAVA-4718
1 parent b1f4e3c commit 7b1904d

File tree

10 files changed

+301
-47
lines changed

10 files changed

+301
-47
lines changed

.evergreen/.evg.yml

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,9 @@ functions:
371371
PASS=$(urlencode ${iam_auth_ecs_secret_access_key})
372372
MONGODB_URI="mongodb://$USER:$PASS@localhost"
373373
EOF
374-
JAVA_VERSION=${JAVA_VERSION} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} .evergreen/run-mongodb-aws-test.sh
374+
JAVA_VERSION=${JAVA_VERSION} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \
375+
AWS_CREDENTIAL_PROVIDER=${AWS_CREDENTIAL_PROVIDER} \
376+
.evergreen/run-mongodb-aws-test.sh
375377
376378
"run aws auth test with assume role credentials":
377379
- command: shell.exec
@@ -400,7 +402,9 @@ functions:
400402
SESSION_TOKEN=$(urlencode $SESSION_TOKEN)
401403
MONGODB_URI="mongodb://$USER:$PASS@localhost"
402404
EOF
403-
JAVA_VERSION=${JAVA_VERSION} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} DRIVERS_TOOLS=${DRIVERS_TOOLS} .evergreen/run-mongodb-aws-test.sh
405+
JAVA_VERSION=${JAVA_VERSION} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} DRIVERS_TOOLS=${DRIVERS_TOOLS} \
406+
AWS_CREDENTIAL_PROVIDER=${AWS_CREDENTIAL_PROVIDER} \
407+
.evergreen/run-mongodb-aws-test.sh
404408
405409
"run aws auth test with aws EC2 credentials":
406410
- command: shell.exec
@@ -420,7 +424,7 @@ functions:
420424
${PREPARE_SHELL}
421425
# Write an empty prepare_mongodb_aws so no auth environment variables are set.
422426
echo "" > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh"
423-
JAVA_VERSION=${JAVA_VERSION} .evergreen/run-mongodb-aws-test.sh
427+
JAVA_VERSION=${JAVA_VERSION} AWS_CREDENTIAL_PROVIDER=${AWS_CREDENTIAL_PROVIDER} .evergreen/run-mongodb-aws-test.sh
424428
425429
"run aws auth test with aws credentials as environment variables":
426430
- command: shell.exec
@@ -449,7 +453,7 @@ functions:
449453
working_dir: "src"
450454
script: |
451455
${PREPARE_SHELL}
452-
JAVA_VERSION=${JAVA_VERSION} .evergreen/run-mongodb-aws-test.sh
456+
JAVA_VERSION=${JAVA_VERSION} AWS_CREDENTIAL_PROVIDER=${AWS_CREDENTIAL_PROVIDER} .evergreen/run-mongodb-aws-test.sh
453457
454458
"run aws auth test with aws credentials and session token as environment variables":
455459
- command: shell.exec
@@ -479,7 +483,7 @@ functions:
479483
working_dir: "src"
480484
script: |
481485
${PREPARE_SHELL}
482-
JAVA_VERSION=${JAVA_VERSION} .evergreen/run-mongodb-aws-test.sh
486+
JAVA_VERSION=${JAVA_VERSION} AWS_CREDENTIAL_PROVIDER=${AWS_CREDENTIAL_PROVIDER} .evergreen/run-mongodb-aws-test.sh
483487
484488
"run aws ECS auth test":
485489
- command: shell.exec
@@ -1637,6 +1641,22 @@ axes:
16371641
variables:
16381642
LOGIN_CONTEXT_NAME: "com.sun.security.jgss.initiate"
16391643

1644+
- id: aws-credential-provider
1645+
display_name: AWS Credential Provider
1646+
values:
1647+
- id: aws_sdk_v2
1648+
display_name: AWS SDK V2
1649+
variables:
1650+
AWS_CREDENTIAL_PROVIDER: "awsSdkV2"
1651+
- id: aws_sdk_v1
1652+
display_name: AWS SDK V1
1653+
variables:
1654+
AWS_CREDENTIAL_PROVIDER: "awsSdkV1"
1655+
- id: built_in
1656+
display_name: Built-In
1657+
variables:
1658+
AWS_CREDENTIAL_PROVIDER: "builtIn"
1659+
16401660
task_groups:
16411661
- name: testgcpkms_task_group
16421662
setup_group_can_fail_task: true
@@ -1813,15 +1833,22 @@ buildvariants:
18131833
- name: "plain-auth-test"
18141834

18151835
- matrix_name: "aws-auth-test"
1816-
matrix_spec: { ssl: "nossl", jdk: ["jdk8", "jdk17"], version: ["4.4", "5.0", "6.0", "latest"], os: "ubuntu" }
1817-
display_name: "MONGODB-AWS Auth test ${version} ${jdk}"
1836+
matrix_spec: { ssl: "nossl", jdk: ["jdk8", "jdk17"], version: ["4.4", "5.0", "6.0", "latest"], os: "ubuntu",
1837+
aws-credential-provider: "*" }
1838+
display_name: "MONGODB-AWS Auth test ${version} ${jdk} ${aws-credential-provider}"
18181839
run_on: ubuntu1804-test
18191840
tasks:
18201841
- name: "aws-auth-test-with-regular-aws-credentials"
18211842
- name: "aws-auth-test-with-assume-role-credentials"
18221843
- name: "aws-auth-test-with-aws-credentials-as-environment-variables"
18231844
- name: "aws-auth-test-with-aws-credentials-and-session-token-as-environment-variables"
18241845
- name: "aws-auth-test-with-aws-EC2-credentials"
1846+
1847+
- matrix_name: "aws-ecs-auth-test"
1848+
matrix_spec: { ssl: "nossl", jdk: ["jdk8", "jdk17"], version: ["4.4", "5.0", "6.0", "latest"], os: "ubuntu" }
1849+
display_name: "MONGODB-AWS ECS Auth test ${version} ${jdk}"
1850+
run_on: ubuntu1804-test
1851+
tasks:
18251852
- name: "aws-ECS-auth-test"
18261853

18271854
- matrix_name: "accept-api-version-2-test"

.evergreen/run-mongodb-aws-ecs-test.sh

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,30 @@ RELATIVE_DIR_PATH="$(dirname "${BASH_SOURCE:-$0}")"
4444
./gradlew -version
4545

4646
echo "Running tests..."
47-
./gradlew -Dorg.mongodb.test.uri=${MONGODB_URI} --stacktrace --debug --info driver-core:test --tests AwsAuthenticationSpecification
47+
./gradlew -Dorg.mongodb.test.uri=${MONGODB_URI} -Dorg.mongodb.test.aws.credential.provider=awsSdkV2 --stacktrace --debug --info \
48+
driver-core:test --tests AwsAuthenticationSpecification
49+
first=$?
50+
echo $first
51+
52+
./gradlew -Dorg.mongodb.test.uri=${MONGODB_URI} -Dorg.mongodb.test.aws.credential.provider=awsSdkV1 --stacktrace --debug --info \
53+
driver-core:test --tests AwsAuthenticationSpecification
54+
second=$?
55+
echo $second
56+
57+
./gradlew -Dorg.mongodb.test.uri=${MONGODB_URI} -Dorg.mongodb.test.aws.credential.provider=builtIn --stacktrace --debug --info \
58+
driver-core:test --tests AwsAuthenticationSpecification
59+
third=$?
60+
echo $third
61+
62+
if [ $first -ne 0 ]; then
63+
exit $first
64+
elif [ $second -ne 0 ]; then
65+
exit $second
66+
elif [ $third -ne 0 ]; then
67+
exit $third
68+
else
69+
exit 0
70+
fi
71+
72+
4873
cd -

.evergreen/run-mongodb-aws-test.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ set -o xtrace
44
set -o errexit # Exit the script with error if any of the commands fail
55

66
# Supported/used environment variables:
7-
# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java
8-
# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9", "jdk11"
9-
7+
# JDK Set the version of java to be used. Java versions can be set from the java toolchain /opt/java
8+
# "jdk5", "jdk6", "jdk7", "jdk8", "jdk9", "jdk11"
9+
# AWS_CREDENTIAL_PROVIDER "builtIn", 'awsSdkV1', 'awsSdkV2'
1010
############################################
1111
# Main Program #
1212
############################################
@@ -37,5 +37,6 @@ echo "Running tests with Java ${JAVA_VERSION}"
3737

3838
# As this script may be executed multiple times in a single task, with different values for MONGODB_URI, it's necessary
3939
# to run cleanTest to ensure that the test actually executes each run
40-
./gradlew -PjavaVersion=${JAVA_VERSION} -Dorg.mongodb.test.uri=${MONGODB_URI} --stacktrace --debug --info --no-build-cache \
41-
driver-core:cleanTest driver-core:test --tests AwsAuthenticationSpecification
40+
./gradlew -PjavaVersion="${JAVA_VERSION}" -Dorg.mongodb.test.uri="${MONGODB_URI}" \
41+
-Dorg.mongodb.test.aws.credential.provider="${AWS_CREDENTIAL_PROVIDER}" \
42+
--stacktrace --debug --info --no-build-cache driver-core:cleanTest driver-core:test --tests AwsAuthenticationSpecification

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ ext {
4747
nettyVersion = '4.1.79.Final'
4848
snappyVersion = '1.1.8.4'
4949
zstdVersion = '1.5.2-3'
50+
awsSdkV2Version = '2.17.293'
51+
awsSdkV1Version = '1.12.291'
5052
mongoCryptVersion = '1.6.0-alpha0'
5153
projectReactorVersion = '2020.0.22'
5254
junitBomVersion = '5.8.1'

driver-core/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ dependencies {
4747
api "io.netty:netty-buffer", optional
4848
api "io.netty:netty-transport", optional
4949
api "io.netty:netty-handler", optional
50+
51+
// Optionally depend on both AWS SDK v2 and v1. The driver will use v2 is present, v1 if present, or built-in functionality if
52+
// neither are present
53+
implementation "software.amazon.awssdk:auth:$awsSdkV2Version", optional
54+
implementation "com.amazonaws:aws-java-sdk-core:$awsSdkV1Version", optional
55+
5056
implementation "org.xerial.snappy:snappy-java:$snappyVersion", optional
5157
implementation "com.github.luben:zstd-jni:$zstdVersion", optional
5258
implementation "org.mongodb:mongodb-crypt:$mongoCryptVersion", optional

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

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,61 +17,82 @@
1717
package com.mongodb.internal.authentication;
1818

1919
import com.mongodb.AwsCredential;
20-
import org.bson.BsonDocument;
20+
import com.mongodb.diagnostics.logging.Logger;
21+
import com.mongodb.diagnostics.logging.Loggers;
22+
import com.mongodb.internal.VisibleForTesting;
2123

22-
import java.util.HashMap;
23-
import java.util.Map;
24+
import java.util.function.Supplier;
2425

25-
import static com.mongodb.internal.authentication.HttpHelper.getHttpContents;
26+
import static com.mongodb.internal.VisibleForTesting.AccessModifier.PRIVATE;
2627

2728
/**
2829
* Utility class for working with AWS authentication.
2930
*
3031
* <p>This class should not be considered a part of the public API.</p>
3132
*/
3233
public final class AwsCredentialHelper {
34+
public static final Logger LOGGER = Loggers.getLogger("authenticator");
3335

34-
public static AwsCredential obtainFromEnvironment() {
35-
if (System.getenv("AWS_ACCESS_KEY_ID") != null) {
36-
return obtainFromEnvironmentVariables();
36+
private static volatile Supplier<AwsCredential> awsCredentialSupplier;
37+
38+
static {
39+
if (isClassAvailable("software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider")) {
40+
awsCredentialSupplier = new AwsSdkV2CredentialSupplier();
41+
LOGGER.info("Using DefaultCredentialsProvider from AWS SDK v2 to retrieve AWS credentials. This is the recommended "
42+
+ "configuration");
43+
} else if (isClassAvailable("com.amazonaws.auth.DefaultAWSCredentialsProviderChain")) {
44+
awsCredentialSupplier = new AwsSdkV1CredentialSupplier();
45+
LOGGER.info("Using DefaultAWSCredentialsProviderChain from AWS SDK v1 to retrieve AWS credentials. Consider adding a "
46+
+ "dependency to AWS SDK v2's software.amazon.awssdk:auth artifact to get access to additional AWS authentication "
47+
+ "functionality.");
3748
} else {
38-
return obtainFromEc2OrEcsResponse();
49+
awsCredentialSupplier = new BuiltInAwsCredentialSupplier();
50+
LOGGER.info("Using built-in driver implementation to retrieve AWS credentials. Consider adding a dependency to AWS SDK "
51+
+ "v2's software.amazon.awssdk:auth artifact to get access to additional AWS authentication functionality.");
3952
}
4053
}
4154

42-
private static AwsCredential obtainFromEnvironmentVariables() {
43-
return new AwsCredential(
44-
System.getenv("AWS_ACCESS_KEY_ID"),
45-
System.getenv("AWS_SECRET_ACCESS_KEY"),
46-
System.getenv("AWS_SESSION_TOKEN"));
55+
private static boolean isClassAvailable(final String className) {
56+
try {
57+
Class.forName(className);
58+
return true;
59+
} catch (ClassNotFoundException e) {
60+
return false;
61+
}
4762
}
4863

49-
private static AwsCredential obtainFromEc2OrEcsResponse() {
50-
String path = System.getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI");
51-
BsonDocument ec2OrEcsResponse = path == null ? BsonDocument.parse(getEc2Response()) : BsonDocument.parse(getEcsResponse(path));
52-
53-
return new AwsCredential(
54-
ec2OrEcsResponse.getString("AccessKeyId").getValue(),
55-
ec2OrEcsResponse.getString("SecretAccessKey").getValue(),
56-
ec2OrEcsResponse.getString("Token").getValue());
64+
/**
65+
* This method is visible to allow tests to require the built-in provider rather than rely on the fixed checks for classes on the
66+
* classpath. It allows us to easily write tests of the built-in implementation without resorting to runtime classpath shenanigans.
67+
*/
68+
@VisibleForTesting(otherwise = PRIVATE)
69+
public static void requireBuiltInProvider() {
70+
LOGGER.info("Using built-in driver implementation to retrieve AWS credentials");
71+
awsCredentialSupplier = new BuiltInAwsCredentialSupplier();
5772
}
5873

59-
private static String getEcsResponse(final String path) {
60-
return getHttpContents("GET", "http://169.254.170.2" + path, null);
74+
/**
75+
* This method is visible to allow tests to require the AWS SDK v1 provider rather than rely on the fixed checks for classes on the
76+
* classpath. It allows us to easily write tests of the AWS SDK v1 implementation without resorting to runtime classpath shenanigans.
77+
*/
78+
@VisibleForTesting(otherwise = PRIVATE)
79+
public static void requireAwsSdkV1Provider() {
80+
LOGGER.info("Using AWS SDK v1 to retrieve AWS credentials");
81+
awsCredentialSupplier = new AwsSdkV1CredentialSupplier();
6182
}
6283

63-
private static String getEc2Response() {
64-
final String endpoint = "http://169.254.169.254";
65-
final String path = "/latest/meta-data/iam/security-credentials/";
66-
67-
Map<String, String> header = new HashMap<>();
68-
header.put("X-aws-ec2-metadata-token-ttl-seconds", "30");
69-
String token = getHttpContents("PUT", endpoint + "/latest/api/token", header);
84+
/**
85+
* This method is visible to allow tests to require the AWS SDK v2 provider rather than rely on the fixed checks for classes on the
86+
* classpath. It allows us to easily write tests of the AWS SDK v2 implementation without resorting to runtime classpath shenanigans.
87+
*/
88+
@VisibleForTesting(otherwise = PRIVATE)
89+
public static void requireAwsSdkV2Provider() {
90+
LOGGER.info("Using AWS SDK v2 to retrieve AWS credentials");
91+
awsCredentialSupplier = new AwsSdkV2CredentialSupplier();
92+
}
7093

71-
header.clear();
72-
header.put("X-aws-ec2-metadata-token", token);
73-
String role = getHttpContents("GET", endpoint + path, header);
74-
return getHttpContents("GET", endpoint + path + role, header);
94+
public static AwsCredential obtainFromEnvironment() {
95+
return awsCredentialSupplier.get();
7596
}
7697

7798
private AwsCredentialHelper() {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.amazonaws.auth.AWSCredentials;
20+
import com.amazonaws.auth.AWSCredentialsProvider;
21+
import com.amazonaws.auth.AWSSessionCredentials;
22+
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
23+
import com.mongodb.AwsCredential;
24+
25+
import java.util.function.Supplier;
26+
27+
public final class AwsSdkV1CredentialSupplier implements Supplier<AwsCredential> {
28+
29+
private final AWSCredentialsProvider provider = DefaultAWSCredentialsProviderChain.getInstance();
30+
31+
@Override
32+
public AwsCredential get() {
33+
AWSCredentials credentials = provider.getCredentials();
34+
if (credentials instanceof AWSSessionCredentials) {
35+
AWSSessionCredentials sessionCredentials = (AWSSessionCredentials) credentials;
36+
return new AwsCredential(sessionCredentials.getAWSAccessKeyId(), sessionCredentials.getAWSSecretKey(),
37+
sessionCredentials.getSessionToken());
38+
} else {
39+
return new AwsCredential(credentials.getAWSAccessKeyId(), credentials.getAWSSecretKey(), null);
40+
}
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.AwsCredential;
20+
import software.amazon.awssdk.auth.credentials.AwsCredentials;
21+
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
22+
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
23+
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
24+
25+
import java.util.function.Supplier;
26+
27+
public final class AwsSdkV2CredentialSupplier implements Supplier<AwsCredential> {
28+
29+
private final AwsCredentialsProvider provider = DefaultCredentialsProvider.create();
30+
31+
@Override
32+
public AwsCredential get() {
33+
AwsCredentials credentials = provider.resolveCredentials();
34+
if (credentials instanceof AwsSessionCredentials) {
35+
AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) credentials;
36+
return new AwsCredential(sessionCredentials.accessKeyId(), sessionCredentials.secretAccessKey(),
37+
sessionCredentials.sessionToken());
38+
} else {
39+
return new AwsCredential(credentials.accessKeyId(), credentials.secretAccessKey(), null);
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)