Connecting MongoDB to Ruby with Self-Signed Certificates for SSL
Tags: mongodb, ruby, database, ssl
Published: 2019-06-18 16:07:33

---
title: Connecting MongoDB to Ruby with Self-Signed Certificates for SSL
published: true
description: ScaleGrid Tutorial - Connecting a MongoDB server with self-signed certificates for SSL using Ruby MongoDB driver and Object-Document-Mapper (ODM) mongoid.
tags: MongoDB, Ruby, Database, SSL,
canonical_url: https://scalegrid.io/blog/connecting-mongodb-to-ruby-with-self-signed-certificates-for-ssl/
---
<link rel="canonical" href="https://scalegrid.io/blog/connecting-mongodb-to-ruby-with-self-signed-certificates-for-ssl/" />

<a href="https://scalegrid.io/blog/connecting-mongodb-to-ruby-with-self-signed-certificates-for-ssl/"><img class="aligncenter size-full wp-image-3740" style="padding: 15px 0px;" src="https://scalegrid.io/blog/wp-content/uploads/2019/06/Connecting-MongoDB-to-Ruby-with-Self-Signed-Certificates-for-SSL-ScaleGrid-Blog.jpg" alt="Connecting MongoDB to Ruby with Self-Signed Certificates for SSL" width="100%" height="auto" /></a>
<p style="text-align: justify;">Given the popularity of our post on connecting <a title="MongoDB SSL with Self-Signed Certificates in Node.js" href="https://scalegrid.io/blog/mongodb-ssl-with-self-signed-certificates-in-node-js/" target="_blank">MongoDB SSL with Self-Signed Certificates in Node.js</a>, we decided to write a tutorial on <a title="Connecting MongoDB to Ruby with Self-Signed Certificates for SSL" href="https://scalegrid.io/blog/connecting-mongodb-to-ruby-with-self-signed-certificates-for-ssl/">connecting MongoDB with Ruby</a>. In this blog, we'll show you how to connect to a MongoDB server configured with self-signed certificates for SSL using both the <a href="https://docs.mongodb.com/ruby-driver/current/" target="_blank" rel="nofollow">Ruby MongoDB driver</a> and the popular<a href="https://docs.mongodb.com/mongoid/current/" target="_blank" rel="nofollow"> Object-Document-Mapper (ODM) mongoid</a>.</p>
<p style="text-align: justify;">ScaleGrid currently uses self-signed certificates for SSL when creating nodes for a new cluster. Additionally, we also provide you with the option of purchasing your own SSL certificates and configuring them on the <a title="MongoDB hosting" href="https://scalegrid.io/mongodb.html" target="_blank">MongoDB server</a>, and you can email <a href="mailto:support@scalegrid.io" target="_top">support@scalegrid.io </a> to learn more about this offer.</p>

<h2 style="padding-top: 10px;">Connecting to a Replica Set Using Ruby MongoDB Driver</h2>
<p style="text-align: justify;">We will use the latest stable Ruby MongoDB driver version 2.8 for this example. The 2.5.x versions of the driver have a <a href="https://scalegrid.io/blog/mongodb-ruby-driver-2-5-x-case-sensitivity-issues-with-hostnames-on-replica-sets/" target="_blank">known bug</a> that inhibit them from working with ScaleGrid deployments. The Ruby version used in both the examples below is 2.6.3.</p>
<p style="text-align: justify;">The connection options available for the driver are documented <a href="https://docs.mongodb.com/ruby-driver/master/tutorials/ruby-driver-create-client/#ruby-options" target="_blank" rel="nofollow">here</a>, and the options we will need are:</p>

<ul>
	<li><tt>:ssl</tt></li>
	<li><tt>:ssl_verify</tt></li>
	<li><tt>:ssl_ca_cert</tt>.</li>
</ul>
<p style="text-align: justify;">First, find and copy your <a href="https://help.scalegrid.io/docs/mongodb-connecting-to-your-application" target="_blank">MongoDB connection string</a> from the cluster details page on the ScaleGrid console:</p>
<a href="https://scalegrid.io/blog/wp-content/uploads/2018/12/ConnString.png"><img class="aligncenter size-full wp-image-3740" style="padding: 15px 0px;" src="https://scalegrid.io/blog/wp-content/uploads/2018/12/ConnString.png" alt="MongoDB Connection String for Ruby Applications" width="100%" height="auto" /></a>
<p style="text-align: justify;">The CA certificate file is also available for download from the cluster details page. Download and store the cert file at a location that is available to the application:
<a href="https://scalegrid.io/blog/wp-content/uploads/2018/12/SSLCert.jpg"><img class="aligncenter size-full wp-image-3734" style="padding: 15px 0px;" src="https://scalegrid.io/blog/wp-content/uploads/2018/12/SSLCert.jpg" alt="MongoDB SSL Certificate and Command Line Syntax for Ruby Applications" width="100%" height="auto" /></a>Here's a snippet showing how to connect to a MongoDB replica set from Ruby:</p>

<pre>require 'mongo'

Mongo::Logger.logger.level = ::Logger::DEBUG

MONGODB_CA_CERT = "/path/to/ca_cert.pem"
MONGODB_CONN_URL = "mongodb://testuser:&lt;pwd&gt;@SG-example-17026.servers.mongodirector.com:27017,SG-example-17027.servers.mongodirector.com:27017,SG-example-17028.servers.mongodirector.com:27017/test?replicaSet=RS-example-0&amp;ssl=true"

options = { ssl:true, ssl_verify: true, :ssl_ca_cert =&gt; MONGODB_CA_CERT }

client = Mongo::Client.new(MONGODB_CONN_URL, options)

db = client.database
collections = db.collection_names
puts "db #{db.name} has collections #{collections}"
client.close
</pre>
<p style="text-align: justify;">To keep the example simple, we have specified the connection string and the cert file path directly in the code snippet - you would generally either put them in a <a href="https://docs.mongodb.com/mongoid/current/tutorials/mongoid-installation/#configuration" target="_blank" rel="nofollow">yaml </a>file or specify them as environment variables. Also, the example sets the log level to <code>DEBUG </code> so that any connectivity issues can be debugged. It should be changed to a less verbose level once connectivity issues have been sorted out.</p>

<h2 style="padding-top: 10px;">Connecting Using Mongoid</h2>
<p style="text-align: justify;">The <a href="https://docs.mongodb.com/mongoid/current/tutorials/mongoid-installation/" target="_blank" rel="nofollow">mongoid version </a>we will use in our example is the latest stable version - 7.0.2. We will use a yaml file to provide configuration to mongoid, and the details of such a config file are documented <a href="https://docs.mongodb.com/mongoid/current/tutorials/mongoid-installation/#anatomy-of-a-mongoid-config" target="_blank" rel="nofollow">here</a>. The SSL-specific configuration options we will need to connect to our replica set are:</p>

<ul>
	<li><tt>ssl</tt></li>
	<li><tt>ssl_verify</tt></li>
	<li><tt>ssl_ca_cert</tt></li>
</ul>
<p style="text-align: justify;">Our yml file:</p>

<pre>development:
  # Configure available database clients. (required)
  clients:
    # Define the default client. (required)
    default:
      # A uri may be defined for a client:
      # uri: 'mongodb://user:password@myhost1.mydomain.com:27017/my_db'
      # Please see driver documentation for details. Alternatively, you can define the following:
      #
      # Define the name of the default database that Mongoid can connect to.
      # (required).
      database: test
      # Provide the hosts the default client can connect to. Must be an array
      # of host:port pairs. (required)
      <strong>hosts:
        - SG-example-17026.servers.mongodirector.com:27017
        - SG-example-17027.servers.mongodirector.com:27017
        - SG-example-17028.servers.mongodirector.com:47100</strong>
      options:
        # The name of the user for authentication.
        <strong>user: 'testuser'</strong>

        # The password of the user for authentication.
        <strong>password: 'pwd'</strong>

        # The user's database roles.
        roles:
          - 'readWrite'

        # Change the default authentication mechanism. Valid options are: :scram,
        # :mongodb_cr, :mongodb_x509, and :plain. (default on 3.0 is :scram, default
        # on 2.4 and 2.6 is :plain)
        auth_mech: :scram

        # The database or source to authenticate the user against. (default: admin)
        <strong>auth_source: test</strong>

        # Force the driver to connect in a specific way instead of auto-
        # discovering. Can be one of: :direct, :replica_set, :sharded. Set to :direct
        # when connecting to hidden members of a replica set.
        connect: :replica_set
        ...
        ...
        # The name of the replica set to connect to. Servers provided as seeds that do
        # not belong to this replica set will be ignored.
        <strong>replica_set: RS-example-0</strong>

        # Whether to connect to the servers via ssl. (default: false)
        <strong>ssl: true</strong>

        # Whether or not to do peer certification validation. (default: true)
        <strong>ssl_verify: true</strong>

        # The file containing a set of concatenated certification authority certifications
        # used to validate certs passed from the other end of the connection.
        <strong>ssl_ca_cert: /path/to/ca_cert.pem</strong>

  # Configure Mongoid specific options. (optional)
  options:
    # Set the Mongoid and Ruby driver log levels. (default: :info)
    log_level: :debug
</pre>
<p style="text-align: justify;">Connection example:</p>

<pre>gem 'mongoid', '7.0.2'
require 'mongoid'

Mongoid.load!("/path/to/mongoid.yml", :development)

# Not using any of the ODM features - just fetch the underlying mongo client and attempt to connect

client = Mongoid::Clients.default
db = client.database
collections = db.collection_names
puts "db #{db.name} has collections #{collections}"
Mongoid::Clients.disconnect

</pre>
<p style="text-align: justify;">Again, in production Ruby on Rails applications, the <a href="https://docs.mongodb.com/mongoid/current/tutorials/mongoid-installation/#rails-applications" target="_blank" rel="nofollow">yaml file path </a>would be picked up from the environment variables.</p>

<h2 style="padding-top: 10px;">Testing Failover Behavior</h2>
<p style="text-align: justify;">Like other MongoDB drivers, the Ruby MongoDB driver is also designed to internally recognize changes in topology due to events like failover. However, it is good to test and validate the behavior of the driver during failovers to avoid surprises in production.</p>
<p style="text-align: justify;">Like my previous post on <a title="PyMongo Tutorial: Testing MongoDB Failover in Your Python App" href="https://scalegrid.io/blog/pymongo-tutorial-testing-mongodb-failover-in-your-python-app/" target="_blank">MongoDB PyMongo</a>, we can write a perpetual writer test program to observe the failover behavior of the driver.</p>
<p style="text-align: justify;">The easiest way to induce failover is to run the <a style="color: #2da964;" href="https://docs.mongodb.com/manual/reference/method/rs.stepDown/" target="_blank" rel="nofollow">rs.stepDown()</a> command:</p>

<pre>RS-example-0:PRIMARY&gt; rs.stepDown()
2019-04-18T19:44:42.257+0530 E QUERY [thread1] Error: error doing query: failed: network error while attempting to run command 'replSetStepDown' on host 'SG-example-1.servers.mongodirector.com:27017' :
DB.prototype.runCommand@src/mongo/shell/db.js:168:1
DB.prototype.adminCommand@src/mongo/shell/db.js:185:1
rs.stepDown@src/mongo/shell/utils.js:1305:12
@(shell):1:1
2019-04-18T19:44:42.261+0530 I NETWORK [thread1] trying reconnect to SG-example-1.servers.mongodirector.com:27017 (X.X.X.X) failed
2019-04-18T19:44:43.267+0530 I NETWORK [thread1] reconnect SG-example-1.servers.mongodirector.com:27017 (X.X.X.X) ok
RS-example-0:SECONDARY&gt;
</pre>
<p style="text-align: justify;">Here are the relevant parts of our test code:</p>

<pre>require 'mongo'
...

logger = Logger.new(STDOUT)
logger.level = Logger::INFO

MONGODB_CA_CERT = "/path/to/ca_cert.pem"
MONGODB_CONN_URL = "mongodb://testuser:&lt;pwd&gt;@SG-example-17026.servers.mongodirector.com:27017,SG-example-17027.servers.mongodirector.com:27017,SG-example-17028.servers.mongodirector.com:27017/test?replicaSet=RS-example-0&amp;ssl=true"
options = { ssl:true, ssl_verify: true, :ssl_ca_cert =&gt; MONGODB_CA_CERT }

begin
    logger.info("Attempting to connect...")
    client = Mongo::Client.new(MONGODB_CONN_URL, options)
    i = 0
    loop do
        db = client.database
        collection = db[:test]
        begin
            doc = {"idx": i, "date": DateTime.now, "text": SecureRandom.base64(3) }
            result = collection.insert_one(doc)
            logger.info("Record inserted - id: #{result.inserted_id}")
            i += 1
            sleep(3)
        rescue Mongo::Error =&gt; e
            logger.error("Mong Error seen: #{e.message}")
            logger.error(e.backtrace)
            logger.info("Retrying...")
        end
    end
    logger.info("Done")
rescue =&gt; err
    logger.error("Exception seen: #{err.message}")
    logger.error(err.backtrace)
ensure
    client.close unless client.nil?
end
</pre>
<p style="text-align: justify;">This continuously writes entries like these to the test collection on the test database:</p>

<pre>RS-test-0:PRIMARY&gt; db.test.find()
{ "_id" : ObjectId("5cf50ff1896cd172a4f7c6ee"), "idx" : 0, "date" : ISODate("2019-06-03T12:17:53.008Z"), "text" : "HTvd" }
{ "_id" : ObjectId("5cf50ff6896cd172a4f7c6ef"), "idx" : 1, "date" : ISODate("2019-06-03T12:17:58.697Z"), "text" : "/e5Z" }
{ "_id" : ObjectId("5cf50ff9896cd172a4f7c6f0"), "idx" : 2, "date" : ISODate("2019-06-03T12:18:01.940Z"), "text" : "quuw" }
{ "_id" : ObjectId("5cf50ffd896cd172a4f7c6f1"), "idx" : 3, "date" : ISODate("2019-06-03T12:18:05.194Z"), "text" : "gTyY" }
{ "_id" : ObjectId("5cf51000896cd172a4f7c6f2"), "idx" : 4, "date" : ISODate("2019-06-03T12:18:08.442Z"), "text" : "VDXX" }
{ "_id" : ObjectId("5cf51003896cd172a4f7c6f3"), "idx" : 5, "date" : ISODate("2019-06-03T12:18:11.691Z"), "text" : "UY87" }
...
</pre>
<p style="text-align: justify;">Let's see the behavior during a failover:</p>

<pre>I, [2019-06-03T17:53:25.079829 #9464]  INFO -- : Attempting to connect...
I, [2019-06-03T17:53:30.577099 #9464]  INFO -- : Record inserted - id: 5cf5113f896cd124f8f31062
I, [2019-06-03T17:53:33.816528 #9464]  INFO -- : Record inserted - id: 5cf51145896cd124f8f31063
I, [2019-06-03T17:53:37.047043 #9464]  INFO -- : Record inserted - id: 5cf51148896cd124f8f31064
I, [2019-06-03T17:53:40.281537 #9464]  INFO -- : Record inserted - id: 5cf5114c896cd124f8f31065
I, [2019-06-03T17:53:43.520010 #9464]  INFO -- : Record inserted - id: 5cf5114f896cd124f8f31066
I, [2019-06-03T17:53:46.747080 #9464]  INFO -- : Record inserted - id: 5cf51152896cd124f8f31067
I, [2019-06-03T17:53:49.978077 #9464]  INFO -- : Record inserted - id: 5cf51155896cd124f8f31068 &lt;&lt;&lt; Failover initiated here
E, [2019-06-03T17:53:52.980434 #9464] ERROR -- : Mong Error seen: EOFError: end of file reached (for x.x.x.x:27017 (sg-example-17026.servers.mongodirector.com:27017, TLS))
E, [2019-06-03T17:53:52.980533 #9464] ERROR -- : ["C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/mongo-2.8.0/lib/mongo/socket.rb:300:in `rescue in handle_errors'", "C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/mongo-2.8.0/lib/mongo/socket.rb:294:in `handle_errors'", "C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/mongo-2.8.0/lib/mongo/socket.rb:126:in `read'", "C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/mongo-2.8.0/lib/mongo/protocol/message.rb:139:in `deserialize'",...
...
I, [2019-06-03T17:53:52.980688 #9464]  INFO -- : Retrying...
W, [2019-06-03T17:53:52.981575 #9464]  WARN -- : Retrying ismaster on sg-example-17026.servers.mongodirector.com:27017 due to: Mongo::Error::SocketError EOFError: end of file reached (for x.x.x.x:27017 (sg-example-17026.servers.mongodirector.com:27017, TLS))
I, [2019-06-03T17:54:06.293100 #9464]  INFO -- : Record inserted - id: 5cf51163896cd124f8f3106a
I, [2019-06-03T17:54:09.547716 #9464]  INFO -- : Record inserted - id: 5cf51169896cd124f8f3106b
I, [2019-06-03T17:54:12.806636 #9464]  INFO -- : Record inserted - id: 5cf5116c896cd124f8f3106c
</pre>
<p style="text-align: justify;">It is evident that if correct errors are caught and read/writes retried, the driver will automatically detect the topology change and reconnect to the new master. For writes, the option <tt>:<a href="http://api.mongodb.com/ruby/2.8.0/Mongo/Client.html" target="_blank" rel="nofollow">retry_writes</a></tt> ensure that the driver will retry once on its own before notifying the application of an error.</p>
<p style="text-align: justify;">There are also multiple driver timeouts that can be tweaked based on the exact behavior and latency you see on your setup. These are documented <a href="https://docs.mongodb.com/ruby-driver/current/tutorials/ruby-driver-create-client/#details-on-timeout-options" target="_blank" rel="nofollow">here</a>.</p>

<h2 style="padding-top: 10px;">Troubleshooting</h2>
<p style="text-align: justify;">If you're having trouble connecting to your SSL-enabled MongoDB deployment, here are a few tips for debugging:</p>

<ul>
	<li style="text-align: justify; padding-bottom: 15px;">First, verify that you can actually connect to the MongoDB server from the server where your application is running. The simplest way to do this is to <a href="https://docs.mongodb.com/manual/installation/#mongodb-community-edition" rel="nofollow">install</a> <a href="https://docs.mongodb.com/manual/introduction/" target="_blank" rel="nofollow">mongo shell </a>on the client machine. On <a href="https://docs.mongodb.com/manual/administration/install-on-linux/" target="_blank" rel="nofollow">Linux</a>, you wouldn't need to install the entire MongoDB server - you can choose to just install the shell separately. Once the shell is available, try to use the 'Command Line Syntax' we provide to attempt to connect to the server.</li>
	<li style="text-align: justify; padding-bottom: 15px;">If you are unable to connect via the mongo shell, it means that the client machine is unable to reach the port 27017 of the MongoDB servers. Look at your Security Group, VPC, and ScaleGrid firewall settings to ensure that there is connectivity between the client and server machines.</li>
	<li style="text-align: justify; padding-bottom: 15px;">If network connectivity is correct, the next thing to check is that you are using versions of <a href="https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#reference-compatibility-language-ruby" target="_blank" rel="nofollow">Ruby</a>, <a href="https://docs.mongodb.com/mongoid/current/tutorials/mongoid-installation/#compatibility" target="_blank" rel="nofollow">mongoid</a>, and <a href="https://docs.mongodb.com/ecosystem/drivers/driver-compatibility-reference/#ruby-driver-compatibility" target="_blank" rel="nofollow">mongo</a> gem that are compatible with the version of your MongoDB server.</li>
	<li style="text-align: justify; padding-bottom: 15px;">If you have confirmed that the driver versions are correct, try running a sample Ruby script, similar to the example we provided above, on the <a href="https://en.wikipedia.org/wiki/Interactive_Ruby_Shell" target="_blank">IRB</a>. A step-by-step execution might point out where the issue is.</li>
	<li style="text-align: justify; padding-bottom: 15px;">If the test script runs fine, but you are still unable to connect with mongoid, attempt to run a simple test script, like the example we provided above.</li>
	<li style="text-align: justify; padding-bottom: 15px;">If you are still having trouble connecting to your instance, please write to us at <a href="mailto:support@scalegrid.io" target="_blank">support@scalegrid.io</a> with detailed results of the above troubleshooting steps and with the exact versions of Ruby, mongoid and mongo driver you are using. The <a href="https://bundler.io/v1.3/rationale.html#checking-your-code-into-version-control" target="_blank">Gemfile.lock</a> will provide you with the exact versions.</li>
</ul>
<p style="text-align: justify; padding-bottom:15px;">If you are new to ScaleGrid and want to give this tutorial a try, sign up for a <a title="Free MongoDB hosting trial" href="https://console.scalegrid.io/" target="_blank">free 30-day trial</a> to explore the platform and test out connecting MongoDB to your Ruby application.</p>