Self-signed SSL connection using PyMongo

Marshall Farrier picture Marshall Farrier · Mar 4, 2016 · Viewed 9.7k times · Source

I'm trying to create a secure SSL connection to MongoDB using PyMongo. The goal is to use this configuration for a Mongo instance running on EC2 to which I can connect with a Python client. For testing, I'm just trying to get the configuration working locally first. My as yet failing attempt can be found here.

Short version of what I think is the problem: My client side certificate authority file ca.pem isn't correct. The way I have it, this file is actually identical to the one I'm using server side. Both were created using x509 with openssl, and I suspect that my client side file needs some kind of different content, but I'm not sure exactly how to generate that content or if this suspicion is even correct.

Here are the details of what I've done for creating the necessary certificates and keys (on Mac El Capitan):

First, generate a certificate authority:

$ mkdir ~/ssl
$ cd ~/ssl
$ openssl req -out ca.pem -new -x509 -days 3650    
# enter info

Generate server .pem file:

$ openssl genrsa -out server.key 2048
$ openssl req -key server.key -new -out server.req
# enter info
$ openssl x509 -req -in server.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out server.crt -days 3650
$ cat server.key server.crt > server.pem

Now do the same for the client:

$ openssl genrsa -out client.key 2048
$ openssl req -key client.key -new -out client.req
$ openssl x509 -req -in client.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out client.crt -days 3650
$ cat client.key client.crt > client.pem

Then I do the following configurations in etc/mongod.conf:

net:
  ...
  ssl:
    mode: requireSSL
    PEMKeyFile: ~/ssl/server.pem
    CAFile: ~/ssl/ca.pem

Now starting mongo (Mac) with

$ mongod --config /etc/mongod.conf

works as expected. The process runs and seems to be accepting the configurations.

Then on the Python side (also running on localhost for debugging), I do the following in calling mongo:

import ssl
from pymongo import MongoClient

client = MongoClient(
        '127.0.0.1',
        27017,
        ssl=True,
        ssl_certfile='~/ssl/client.pem',
        ssl_cert_reqs=ssl.CERT_REQUIRED,
        ssl_ca_certs='~/ssl/ca.pem'
        )
# try a simple insert

When I run this, the Python code blocks after creating the MongoClient, and I see in the mongo logs:

2016-03-03T22:11:30.331-0800 E NETWORK  [conn21] SSL: error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca

On the basis of Wan's comment below, I redid the whole thing, making sure that the information in the 'Distinguished Name' was consistent with those specifications. The old version definitely used the same Common Name for both server and client. The connection still isn't succeeding, but the messages are somewhat different:

2016-03-11T12:29:40.380-0800 I NETWORK  [initandlisten] connection accepted from 127.0.0.1:57363 #3 (1 connection now open)
2016-03-11T12:29:40.386-0800 I NETWORK  [conn3] end connection 127.0.0.1:57363 (0 connections now open)

These 2 messages just repeat until I stop the Python process, which blocks when attempting to open the connection. The end connection part is now in the place where the alert unknown ca line was previously in the mongo log.

Additional info for debugging:

$ openssl verify -CAfile ca.pem client.pem
client.pem: OK
$ openssl verify -CAfile ca.pem server.pem
server.pem: OK
$ openssl x509 -noout -subject -in server.pem
subject= /C=US/ST=Washington/L=Seattle/O=codeMelon/OU=Engineering/CN=server.com/emailAddress=<my-email-address>@gmail.com
$ openssl x509 -noout -subject -in client.pem
subject= /C=US/ST=Washington/L=Seattle/O=codeMelon/OU=Engineering/CN=client.com/emailAddress=<my-email-address>@gmail.com

The bottom line after getting the subject line properly formed is that mongo is accepting the connection, then ending it, and the Python process is blocking without ever making the intended insert.

Any help is much appreciated!

Answer

Wan Bachtiar picture Wan Bachtiar · Mar 13, 2016

There are other ways of generating server/client pem with a Certificate Authority i.e. without involving file.srl, but this answer is to follow up on the question.

Worth mentioning that most MongoDB v3.0+ distributions now include support for SSL, please be sure to choose a package that supports SSL. The example below is tested with MongoDB v3.2 on Ubuntu Linux 14.04 with PyMongo v3.2.1. Where a single machine generated the CA, server and client pem files for demonstration purposes.

Let's generate ca.pem and privkey.pem. The subject structure is /C=<Country Name>/ST=<State>/L=<Locality Name>/O=<Organisation Name>/emailAddress=<email>/CN=<Common Name>.

mkdir ~/ssl
cd ~/ssl
openssl req -out ca.pem -new -x509 -days 3650 -subj "/C=AU/ST=NSW/O=Organisation/CN=root/[email protected]"

Generate server .pem file:

hostname  # note down the value
echo "00" > file.srl # two random digits number
openssl genrsa -out server.key 2048
openssl req -key server.key -new -out server.req -subj  "/C=AU/ST=NSW/O=Organisation/CN=server1/CN=<hostname value>/[email protected]"
openssl x509 -req -in server.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out server.crt -days 3650
cat server.key server.crt > server.pem
openssl verify -CAfile ca.pem server.pem

Although you can use IP address as CN value as well, it is not recommended. See RFC-6125.

Now let's generate client.pem file:

openssl genrsa -out client.key 2048
openssl req -key client.key -new -out client.req -subj "/C=AU/ST=NSW/O=Organisation/CN=client1/[email protected]"
openssl x509 -req -in client.req -CA ca.pem -CAkey privkey.pem -CAserial file.srl -out client.crt -days 3650
cat client.key client.crt > client.pem
openssl verify -CAfile ca.pem client.pem

After generating the .pem files, now you can run mongod. for example:

mongod --sslMode requireSSL --sslPEMKeyFile ~/server.pem --sslCAFile ~/ca.pem

You can test the connection using the mongo shell, for example:

mongo --ssl --sslPEMKeyFile ~/client.pem --sslCAFile ~/ca.pem --host <server hostname>

Once you can get connected successfully, you can try with PyMongo. For example:

import ssl 
from pymongo import MongoClient
client = MongoClient(
    '<server hostname>',
    27017,
    ssl=True,
    ssl_certfile='~/client.pem',
    ssl_cert_reqs=ssl.CERT_REQUIRED,
    ssl_ca_certs='~/ca.pem'
 )

Alternatively, you can also use mongod flag --sslAllowInvalidHostnames to specify localhost, etc.

For production use, your MongoDB deployment should use valid certificates generated and signed by a single certificate authority. If you use a self-signed certificate, although the communications channel will be encrypted, there will be no validation of server identity. Using a certificate signed by a trusted certificate authority will permit MongoDB drivers to verify the server’s identity. In general, avoid using self-signed certificates unless the network is trusted.

Other related links that you may find useful: