Connecting to MongoDB with TLS using Monger in Clojure

Kappagantula Ram Narayan
4 min readAug 4, 2024

--

MongoDB

Hello! As we want to connect to MongoDB from our servers with TLS a good starting point to see how to do this would be the official Monger documentation. After going through the docs, we notice that our solution is not trivial. Don’t be alarmed, we have a few helpful hints to guide us. The clue is to look into the source code of the monger.core/connect function.

Scrolling down we find a function called ^MongoClientOptions$Builder. This function returns a MongoClientOptions object that we can pass to the connect function as an argument.

We find a lot of options that we can use, out of all of them the ones that we need are ssl-context, ssl-enabled, and ssl-invalid-host-name-allowed.

These options are properties of MongoClientOptions.Builder. So, if we want to know how to work with these options we need to look into the documentation of MongoClientOptions.Builder. So yeah let’s go there!

We find the documentation on how to use the ssl-context option. It’s another object(SSLContext) that we need to generate. We can use the getInstance method to get the SSLContext object and use the init method to initialize it.

The init method takes in a TrustManager array and a KeyManager array objects as input. To pass those objects, we’ll have to generate a Client Certificate and a CA(Certificate Authority) Certificate and use them to create a Client Keystore and a CA TrustStore.

We can then initialize the TrustManager and the KeyManager with the TrustManagerFactory and the KeyManagerFactory objects that we generate from the CA TrustStore and the Client KeyStore.

You can run these commands in your terminal. Make sure you have openssl and keytool installed in your system.

Steps to generate a CA Certificate

  1. Create a Private Key
openssl genrsa -out ca.key 2048

2. Create a Certificate Signing Request(CSR)

openssl req -new -key ca.key -out ca.csr

3. Create the CA(Certificate Authority) Certificate

openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt

Steps to generate a Client Certificate

  1. Create a Private Key
openssl genrsa -out client.key 2048

2. Create a Certificate Signing Request(CSR)

openssl req -new -key client.key -out client.csr

3. Create the Client Certificate using the CA(Certificate Authority)

openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365

Command to generate the CA(Certificate Authority) TrustStore

keytool -import -file ca.crt -alias ca -keystore catruststore

Command to generate the Client KeyStore

openssl pkcs12 -export -in client.crt -inkey client.key -out client-keystore.p12

Now that we’ve generated the necessary files let’s look at how to use them to create the SSLContext object.

(defn create-ssl-context
[ca-truststore-stream client-keystore-stream ca-truststore-password client-keystore-password]
(let [ca-keystore (KeyStore/getInstance "PKCS12")
client-keystore (KeyStore/getInstance "PKCS12")
trust-manager-factory (TrustManagerFactory/getInstance "X509")
key-manager-factory (KeyManagerFactory/getInstance "PKIX")
ssl-context (SSLContext/getInstance "TLS")]
(.load ca-keystore
ca-truststore-stream
(.toCharArray (str ca-truststore-password)))
(.load client-keystore
client-keystore-stream
(.toCharArray (str client-keystore-password)))
(.init trust-manager-factory
ca-keystore)
(.init key-manager-factory
client-keystore
(.toCharArray (str client-keystore-password)))
(.init ssl-context
(.getKeyManagers key-manager-factory)
(.getTrustManagers trust-manager-factory)
nil)
ssl-context))
  • We first generate two KeyStores, a TrustManagerFactory, a KeyManagerFactory along with the SSLContext object.
  • We load the KeyStores with both the keystore streams(Client and CA).
  • Then we initialize the TrustManagerFactory with the CA KeyStore, the KeyManagerFactory with the Client KeyStore.
  • Finally, we use the KeyManagerFactory object and the TrustManagerFactory object to initialize the SSLContext object.

We have the SSLContext object with us now, let’s move on and establish the connection to MongoDB.

(defn connect-mongo-secure
[client-keystore-password ca-truststore-password client-keystore-path ca-truststore-path]
(let [client-keystore-stream (slurp (io/file client-keystore-path))
ca-truststore-stream (slurp (io/file ca-truststore-path))
ssl-context (create-ssl-context ca-trust-store-stream client-keystore-stream ca-truststore-password client-keystore-password)
opts {:ssl-enabled true
:ssl-context ssl-context
:ssl-invalid-host-name-allowed true
;other options can be added here}
^MongoClientOptions conn-opts (mg/mongo-options opts)
^ServerAddress sa (mg/server-address "<replace-with-actual-server-address>" 27017)
connection (mg/connect sa conn-opts)]
connection))
  • We create the Client KeyStore stream and the CA TrustStore stream by reading the files from the paths and passing them and the respective passwords to the create-ssl-context function which will return a SSLContext object.
  • After that, we pass the object as the value for the :ssl-context option along with the other options that Monger offers us to connect to MongoDB.
  • Finally, we create the MongoClientOptions object and the ServerAddress object and pass them to the connect function and get our secure connection object that we can use to perform operations on our MongoDB instance!

Here is the entire code in a gist for your reference.

References

--

--