[JAVA-4292] AWS credential refreshing Created: 08/Sep/21  Updated: 28/Oct/23  Resolved: 04/Oct/21

Status: Closed
Project: Java Driver
Component/s: Security
Affects Version/s: None
Fix Version/s: 4.4.0

Type: Epic Priority: Major - P3
Reporter: Jeffrey Yemin Assignee: Unassigned
Resolution: Fixed Votes: 0
Labels: rp-TW
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
related to JAVA-4718 Depend on AWS SDK for fetching creden... Closed
related to DRIVERS-1746 Add native support for AWS IAM Roles ... Closed
is related to JAVA-4118 Add support for EKS when using AWS Ia... Closed
Case:
Documentation Changes: Needed
Documentation Changes Summary: This is going to need some work

 Description   

The Java driver supports authenticating with AWS credentials, according to this specification of driver behavior. However, there are some use case where the application requires the ability to refresh the session token, since it is temporary.  Currently, the only way to refresh a session token is to create a new MongoClient with a new MongoCredential.

Additionally, there are some use cases for obtaining session tokens that the driver does not support, e.g. the Elastic Kubernetes Service (EKS).

The driver could enable these use cases by allowing the application to register with the MongoClient a callback that the driver invokes before every authentication attempt (essentially, every time a connection is opened).  This callback will return an object containing all the information needed to authenticate: the access key id, the secret access key, and the session token.  It is up to the application providing the callback to ensure that these values are valid and have not yet expired.

The proposed API includes a simple value class called AwsCredential that includes the three aforementioned pieces of information – the access key id, the secret access key, and the session token – none of which can be null.  The callback itself must be added as a MongoCredential mechanism property with the name "AWS_CREDENTIAL_PROVIDER_KEY" whose value is of type Supplier<AwsCredential>. If this mechanism property is included in the MongoCredential, the driver will use it to obtain the AWS credentials in preference to all other mechanisms defined in Obtaining Credentials section of the specification.

 

 

 



 Comments   
Comment by Jeffrey Yemin [ 22/Jun/23 ]

With the introduction of JAVA-4718, most applications should not need a callback anymore.

Comment by Jeffrey Yemin [ 22/Jun/23 ]

mark.baker-munton@mongodb.com also note that the driver now does this refresh in EKS automatically.

Comment by Jeffrey Yemin [ 09/Sep/21 ]

Usage example:

import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
 
import java.time.Instant;
import java.util.function.Supplier;
 
import static org.bson.assertions.Assertions.notNull;
 
public class AssumeRoleExample {
 
    public static void main(String[] args) {
 
        final String USAGE = "\n" +
                "Usage:\n" +
                "    AssumeRoleExample <region> <roleArn> <roleSessionName> \n\n" +
                "Where:\n" +
                "    region - the Amazon region (for example, us-east-1) \n" +
                "    roleArn - the Amazon Resource Name (ARN) of the role to assume (for example, rn:aws:iam::000008047983:role/s3role). \n" +
                "    roleSessionName - an identifier for the assumed role session (for example, mysession). \n";
 
        if (args.length != 3) {
            System.out.println(USAGE);
            System.exit(1);
        }
 
        String regionString = args[0];
        String roleArn = args[1];
        String roleSessionName = args[2];
 
        Region region = Region.of(regionString);
        StsClient stsClient = StsClient.builder()
                .region(region)
                .build();
 
        MongoCredential credential = MongoCredential.createAwsCredential(null, null)
                .withMechanismProperty(MongoCredential.AWS_CREDENTIAL_PROVIDER_KEY, 
                      new MongoAwsCredentialSupplier(
                        new CredentialsSupplier(stsClient, roleArn, roleSessionName)));
 
        MongoClient client = MongoClients.create(MongoClientSettings.builder()
                .credential(credential)
                .build());
 
        // use client
        
        client.close();
        stsClient.close();
    }
 
 
   private static class MongoAwsCredentialSupplier implements Supplier<AwsCredential> {
        private final Supplier<Credentials> wrappedSupplier;
        private Credentials credentials;
 
        public MongoAwsCredentialSupplier(Supplier<Credentials> wrappedSupplier) {
            this.wrappedSupplier = wrappedSupplier;
            credentials = wrappedSupplier.get();
        }
 
        @Override
        public AwsCredential get() {
            synchronized (this) {
                // alternatively, could start a thread that keeps the credentials up to date, in order to avoid blocking 
                if (credentials.expiration().isBefore(Instant.now().minusSeconds(60))) {
                    credentials = wrappedSupplier.get();
                }
            }
            return new AwsCredential(
                    credentials.accessKeyId(),
                    credentials.secretAccessKey(),
                    credentials.sessionToken());
        }
    }
 
    private static class CredentialsSupplier implements Supplier<Credentials> {
        private final StsClient stsClient;
        private final String roleArn;
        private final String roleSessionName;
 
        public CredentialsSupplier(StsClient stsClient, String roleArn, String roleSessionName) {
            this.stsClient = stsClient;
            this.roleArn = roleArn;
            this.roleSessionName = roleSessionName;
        }
 
        @Override
        public Credentials get() {
            AssumeRoleRequest roleRequest = AssumeRoleRequest.builder()
                    .roleArn(roleArn)
                    .roleSessionName(roleSessionName)
                    .build();
 
            AssumeRoleResponse roleResponse = stsClient.assumeRole(roleRequest);
            return roleResponse.credentials();
        }
    }
}

requiring these additions to pom.xml:

 <dependencyManagement>
  <dependencies>
   <dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>bom</artifactId>
    <version>2.16.60</version>
    <type>pom</type>
    <scope>import</scope>
   </dependency>
  </dependencies>
 </dependencyManagement>
 <dependencies>
  <dependency>
   <groupId>software.amazon.awssdk</groupId>
   <artifactId>sts</artifactId>
  </dependency>
 </dependencies>

Generated at Thu Feb 08 09:01:42 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.