RethinkDB and SSL- Think secure

With the arrival of RethinkDB 2.1 on the Compose platform, we've been able to activate SSL support alongside the new high availability features. Now, while high availability runs in the background, the SSL support can dramatically improve the security and day-to-day usability of RethinkDB on Compose so, let's begin with a question...

Didn't you have a secure connection option before?

Yes, we did. We offered and still offer SSH tunnelling connections to RethinkDB database clusters on Compose. What SSH tunnelling offers is the ability to plug into your RethinkDB deployment's local virtual private network so you can connect "directly" to the systems in that deployment. It's great for servers where you can define the structural aspects of how your application and database server connects.

So who's SSL for?

SSL is for anyone who's use case is one of ad-hoc connections being made to the database where they want a secured connection. Whether it's a browser based application or a server on the move, the ability to connect to the database securely using SSL is a big plus. It also allows you to quickly get to the powerful admin front-end of RethinkDB and we'll start our tour there.

Do you just do SSL?

When we say SSL, we use it in the generic sense that has become common, to refer to the family secure socket connections that can be created. Generally, the drivers configure a TLS 1.2 connection, rather than other older versions and variants.

Administration over SSL

Lets start by how to administer the database over SSL. First of all, you need to create a user. This isn't a user on the RethinkDB database but a user of the Compose Haproxy which handles the incoming connections from the internet. That's a standard part of our arsenal of access portals for controlling connections into database deployments; it also supports IP whitelisting if you want to restrict the addresses from which those connections come from.

Log into your Compose dashboard, go to go to Users and select Add User. Enter a username and password and wait a moment while the user is created.

The next stop is to get the Admin URL for your deployment. You'll find that on the Overview tab under Connection Info. Take that URL, pop it in your browser address bar and hit return. You'll be prompted for a username and password, which you just created so enter them and you will be connected to the RethinkDB administration front end.

Connecting with JavaScript, Ruby and Python

All three of the official drivers for RethinkDB have a very similar way of setting up a connection to the database. Each needs a host and port to connect to, a database to access, an authentication key and a timeout setting passed to the appropriate connect method. These are passed to the driver as a number of parameters to the connect method, or as a map of keys/values in JavaScript.

First, we need that connection information. If you go to your Compose Overview for your RethinkDB and look for the Connection Info you'll find something that looks like this:

Let's go through the things we need to make a connection and where to find them. First up, the host. You'll find that in the connection string URL, after the @ and up to the : like so...

The port, of course comes after the ':'...

Now, the database to access depends entirely upon your configuration. It defaults to "test" but can even be left blank as long as your code specifies which database it's working with.

We move onto the authentication key now. Previously with Compose you haven't had to specify that because you're already within an SSH tunnelled network. But now, coming in from the outside, you have to supply this secret to confirm that your application should be trusted. You'll find it's obscured by default at the top of the Connection Info panel. Clicking on Show will, more often than not, prompt you for your account password to re-authenticate you. It'll then show you the Authentication Key (and offer you the chance to change it). Copy that down somewhere safe and we can move on. We'll leave the timeout to its default. For the purposes of this example and realism, let's say that the authentication key is "QBXORIDHnnjkvUyhexl1nKcnAxbIqPBcrHeqkWglXc"

There's just one additional thing we need to make an SSL connection. Newly added is the ssl key, which is itself a map of key/values for the SSL connection to use. There's only one option currently implemented, ca_certs, which takes a CA Certificate to validate the server connection against and ensure you are talking to the server you think you are talking to. You can get that certificate, also known here as the SSL Public Key, from a panel below the Connection Info. It'll be displayed as selectable text which you can copy, in its entirety including the BEGIN and END lines, into a local file. We've copied ours into a file called cacert.

Python

So, here's the minimal code for connecting. Note that in these examples we'll confirm we are connected by running the db_list operation which returns an array of names of available databases.

import rethinkdb as r

conn = r.connect(host='aws-eu-west-1-portal.1.dblayer.com',  
                 port=10605,
                 auth_key='QBXORIDHnnjkvUyhexl1nKcnAxbIqPBcrHeqkWglXc',
                 ssl={'ca_certs': './cacert'})

dbs=r.db_list().run(conn)

print(dbs)  

The ssl parameter's value is a hash with just ca_certs as the only key. It's value is a file path to our SSL Public Key file.

Ruby

The Ruby version is remarkably similar in structure to the Python version:

require 'openssl'  
require 'rethinkdb'  
include RethinkDB::Shortcuts

conn = r.connect(:host => 'aws-eu-west-1-portal.1.dblayer.com',  
                 :port => 10605,
                 :auth_key => 'QBXORIDHnnjkvUyhexl1nKcnAxbIqPBcrHeqkWglXc',
                 :ssl => { :ca_certs => './cacert' }
                 )

dbs=r.db_list().run(conn)  
print(dbs)  

JavaScript/Node.js

The version for connecting with JavaScript has a separate hoop to jump through in that, rather than taking the path to a filename which the library can read for the certificate, it takes a JavaScript Buffer which needs to be loaded with the certificate files contents. Combined with the asynchronous nature of Node, this means we get to run fs.readFile() to get the certificate into a Buffer in the functions callback and it's there we call the r.connect() with a hash of parameters which you'll recognise from the previous examples. One change, in Node, the SSL option ca_cert becomes just ca.

var r = require('rethinkdb');

var fs = require('fs');  
fs.readFile('./cacert', function(err, caCert) {  
  r.connect({
    host: 'aws-eu-west-1-portal.1.dblayer.com',
    port: 10605,
    authKey: 'QBXORIDHnnjkvUyhexl1nKcnAxbIqPBcrHeqkWglXc',
    ssl: {
      ca: caCert
    }
  }, function(error, conn) {
    r.dbList().run(conn, function(err, results) {
      console.log(results);
    })
  })
});

The rest of the code, in the callback to the r.connect() runs the dbList command and prints a list of databases.

Connecting with SSL and Go

As well as the official drivers, some of the unofficial drivers for RethinkDB have acquired the ability to communicate over SSL. Foremost among them is Dan Cannon's GoRethink driver. We'll step through some code that does the same as the previous examples, in Go to show the differences. First of all, the obligatory imports and then the start of the actual code:

package main

import (  
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io/ioutil"

    r "github.com/dancannon/gorethink"
)

func main() {  
    roots := x509.NewCertPool()
    cert, err := ioutil.ReadFile("./cacert")
    roots.AppendCertsFromPEM(cert)

Now, if you don't do crypto, this will look a bit odd. Basically its creating a pool of certificates to check using Go's x509 package. Then it's reading our certificate into a variable, cert, using the handy ioutil package. And finally it is adding that certificate to the pool we created. GoRethink uses the exisiting Go language crypto facilities, hence the x509 call.

    session, err := r.Connect(r.ConnectOpts{
        Address:  "aws-eu-west-1-portal.1.dblayer.com:10605",
        Database: "test",
        AuthKey:  "QBXORIDHnnjkvUyhexl1nKcnAxbIqPBcrHeqkWglXc",
        TLSConfig: &tls.Config{
            RootCAs: roots,
        },
    })

This is the core of the connect. First thing to notice is the Address combines the host name and port number in one parameter. The other thing to notice is the "TLSConfig", which does the work of the "ssl" parameter from the other examples. It points to a Go tls.Config structure and we're populating that with just the root certificate that we read in.

    if err != nil {
        fmt.Println(err)
        return
    }
    result, err := r.DBList().Run(session)

    var row interface{}

    for result.Next(&row) {
        fmt.Println(row)
    }
}

Finally, we check the error result – well Go insists we do as an error not using err – and then we have run DBList() and extract the strings from the result. And there we have it; connecting to RethinkDB with SSL and Go.

Wrapping up

Other drivers are getting SSL support and will likely implement it in a style in keeping with whatever the language's idiom is. If a driver doesn't explicitly support SSL to RethinkDB, get in touch with its authors and ask them to implement it.