Skip to content

Instantly share code, notes, and snippets.

@EliasRanz
Last active June 12, 2024 19:48
Show Gist options
  • Save EliasRanz/758a1a884cf2eb3fea69309f9531c524 to your computer and use it in GitHub Desktop.
Save EliasRanz/758a1a884cf2eb3fea69309f9531c524 to your computer and use it in GitHub Desktop.
Mongo Configuration for Amazon DocumentDB utilizing Spring-Boot and spring-data-mongodb
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@Configuration
@Log4j2
@EnableMongoRepositories(basePackages = "<REPOSITORIES_PACKAGE>")
public class MongoConfig extends AbstractMongoConfiguration {
@Value("${DB_HOST}")
private String dbUri;
@Value("${DB_PORT}")
private String port;
@Value("${DB_NAME}")
private String dbName;
@Value("${APP_DIR}")
private String directory;
@Value("${DB_USER}")
private String dbUser;
@Value("${DB_PWD}")
private char[] dbPassword;
private static final String RDS_COMBINED_CA_BUNDLE = "rds-combined-ca-bundle.pem";
@Override
protected String getDatabaseName() {
return db;
}
@Override
public MongoClient mongoClient() {
MongoClient mongoClient = new MongoClient(new ServerAddress(dbUri, Integer.parseInt(port)), mongoCredentials(), mongoClientOptions());
return mongoClient;
}
@Bean
public MongoClientOptions mongoClientOptions() {
MongoClientOptions.Builder mongoClientOptions = MongoClientOptions.builder().sslInvalidHostNameAllowed(true).sslEnabled(true);
try {
String fileName = directory + RDS_COMBINED_CA_BUNDLE;
InputStream is = new FileInputStream(fileName);
// You could get a resource as a stream instead.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
mongoClientOptions.sslContext(sslContext);
} catch (Exception e) {
log.error(e);
}
return mongoClientOptions.build();
}
private MongoCredential mongoCredentials() {
return MongoCredential.createCredential(dbUser, dbName, dbPassword);
}
}
@EliasRanz
Copy link
Author

EliasRanz commented Feb 18, 2019

I've been banging my head against getting a valid SSLContext and workflow with AWS going in my Spring-Boot app so I figured I'd share my configuration class.

Useful Documentation from Amazon/MongoDB

Setup

Minimum Requirements

  • EC2 instance, this will serve as your connection point to the Amazon VPC for SSH Tunneling. For the rest of these steps I'll be referencing their Ubuntu documentation.
  • Amazon DocumentDB Cluster, this will be the remote instance of DocumentDB.

AWS Configuration

Security Group

By Default Amazon says to use their default security group when creating your EC2 instance, but this still caused an error where it said Connection Refused on Port 22. This is because it will set the Inbound Rule to use a source of <GROUP_ID> where the ID is associated with the Security Group it configured. To get around this I had to add a SSH rule, which allowed everyone to connect (I'm sure there is a better way to handle that where you restrict it more based on clients rather than completely open.
I only created a single rule and then Amazon auto generated the second SSH rule.
image

EC2 Configuration

I opted to just use the EC2 free tier configuration options, so you can accept many of the defaults (I opted to use the Ubuntu 18.04 instance). The Instance Type is t2.micro since that's what Amazon said was free tier eligible. I also had to setup a Private Key (.PEM) file, but you can re-use one if you've created one before. Once the EC2 box is setup you'll want to do a few things on the server itself.


SSH:
Replace <PATH_TO_PRIVATE_KEY_FILE> with the path to the file (either relative or absolute).
Replace <PUBLC_DNS> with the value you got from your EC2 instance.
The -i flag on the ssh command will make it so that it uses the hardcoded path to the EC2 key that you downloaded, you could alternatively add it to your SSH Configuration.

SSH Config

Host <PUBLIC_DNS>
 	AddKeysToAgent yes
 	IdentityFile <PATH_TO_PRIVATE_KEY_FILE>

Then when you SSH you can simply just do

ssh ubuntu@<PUBLIC_DNS>

CLI

ssh -i "<PATH_TO_PRIVATE_KEY_FILE>" ubuntu@<PUBLIC_DNS>

Once you're connected to the server you'll need to run a few commands to get Mongo Configured.

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD
$ echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list
$ sudo apt-get update
$ sudo apt-get install -y mongodb-org-shell
$ wget https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem

# To validate the connection you should run the following command. You should be able to connect while SSH'd into the EC2 instance.
$ mongo --ssl --host <DOCUMENTDB_CLUSTER_HOST> --sslCAFile rds-combined-ca-bundle.pem --username <DB_USER> --password "<DB_PASS>"
> exit
$ exit

You should be all set on the EC2 side of things from here. Proceed to connection from your local machine.

Connecting locally

Environment Variables

DB_HOST: This should be your local ssh forwarded db connection IE localhost.
DB_PORT: This is the port that is your local version binded to your SSH tunnel. IE 27017 by default
DB_NAME: This would be the DB in mongo that you're connecting to. IE the db you get to when using use on the Mongo CLI.
DB_USER: Username associated with your MongoDB cluster in AWS.
APP_DIR: This is where you have your PEM file saved.

Terminal stuff

Now that you have a DocumentDB instance running through AWS you will want to create an SSH Tunnel between your local machine and your EC2 instance. The following command will do this for you. Keep in mind that you'll want to have this running in a separate terminal tab, because it'll keep the connection as long as the command is being executed.

$ export APP_DIR="<PATH_TO_PRIVATE_KEY_FILE>"
$ ssh -i ${APP_DIR}/<PRIVATE_KEY_FILE_NAME>.pem -L ${DB_HOST}:<DOCUMENTDB_CLUSTER_HOST>:27017 ubuntu@<EC2_PUBLIC_DNS> -N

If you did the SSH Configuration approach you can omit the -i <PATH_TO_PRIVATE_KEY_FILE>. This will bind a connection between your DocumentDB Cluster and your local machine running through <DB_PORT>. Then in a new tab, you'll want to test the connection via the following shell command.

$ export DB_USER="<DB_USER>"
$ export DB_PWD="<DB_PASS>"
$ export DB_PORT="<DB_PORT>"
$ mongo --sslAllowInvalidHostnames --ssl --sslCAFile ${APP_DIR}/rds-combined-ca-bundle.pem --username ${DB_USER} --password ${DB_PWD} --host "localhost" --port ${DB_PORT}

From here you should be able to connect successfully and should be all set!

@rtehok
Copy link

rtehok commented Nov 27, 2019

Thanks mate for your instructions! This is quite helpful!
Also, for me, following the developer's guide from aws worked for me (instead of reading the .pem file, use the truststore)

@ajays0110
Copy link

What is the path for directory in your configuration, because when I am receiving "unable to find valid certification path to requested target" even though I hard coded the file path
@bean
public MongoClientOptions mongoClientOptions() {
MongoClientOptions.Builder mongoClientOptions = MongoClientOptions.builder().sslInvalidHostNameAllowed(true).sslEnabled(true);
try {
String fileName = "/src/main/resources/new-rds.pem";

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate caCert = (X509Certificate) cf.generateCertificate(Files.newInputStream(Paths.get(fileName)));

        TrustManagerFactory tmf = TrustManagerFactory
            .getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(null); // You don't need the KeyStore instance to come from a file.
        ks.setCertificateEntry("caCert", caCert);

        tmf.init(ks);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        mongoClientOptions.sslContext(sslContext);
    } catch (Exception e) {
    }
    return mongoClientOptions.build();
}

@rtehok
Copy link

rtehok commented Nov 28, 2019

For me, I followed the java section of this page with this part:

keytool -importcert -trustcacerts -file rds-combined-ca-bundle.pem -alias rds -keystore rds-ca-certs -storepass <keystorePassword>

and in the code

        String keystore = "rds-ca-certs";
        String keystorePassword = "<keystorePassword>";

        System.setProperty("javax.net.ssl.trustStore", keystore);
        System.setProperty("javax.net.ssl.trustStorePassword", keystorePassword);

Also I downloaded the rds-ca-2019-root.pem and it is working.

@ssi-hu-miklos-kosarkar
Copy link

Hey, have you tried accessing any HTTPS rest endpoint from the same app? I noticed if I have a https call before or after connecting to the documnetdb, all the following connections will fail with SunCertPathBuilderException.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment