Saturday 31 December 2016

Using Mongo SSL with GO

Using Mongo with SSL with GO I am building a personal web site using GO language and as a back-end database I decided to use MongoDB. As a web framework in GO I decided to use echo framework from LabStack, and to access MongoDB I used mgo drivers from Labix.
Once everything was working fine (retrieving and saving data), I wanted to make sure that my web application can only access a specific database, so I needed to create a specific user with only read/write access to the database.
To create a user in MongoDB, we use mongo shell. In the shell we use the following command :
db.CreateUser({
    user: "mongoUser",
    pwd: "userPassword",
    roles: [
        { role: "readWrite", db: "myDatabase" }
    ]}) 
Then we need to add the following to the MongoDB configuration file to enable the security:
security:
    authorization: enabled
Then the URL for accessing the database in GO would become:
session,err := mgo.Dial("mongodb://mongoUser:userPassword@localhost:27017/myDatabase")
The next step was to secure the connection between my web application and MongoDB by using SSL. Mongo official documentation suggests that for production you should use a valid certificate generated and signed by a valid certificate authority. But since my web application is for personal use only, I decided to use a self-signed certificate using OpenSSL (if you have a linux virtual machine OpenSSL comes pre-installed, or windows version can be obtained from sourceforge)
To generate a self-signed certificate use the following command:
openssl req -newkey rsa:2048 -new -x509 -days 365 -nodes -out mongodb-cert.crt -keyout mongodb-cert.key
After we generate the certificate, we need to concatenate the certificate and private key into a .pem file. So using powershell CLI we use the following command :
cat mongodb-cert.key, mongodb-cert.crt > mongodb.pem
or in Linux:
cat mongodb-cert.key mongodb-cert.crt > mongodb.pem
The next step is to update the MongoDB configuration file (YAML format) and add the following:
net:
   ssl:
      mode: requireSSL
      PEMKeyFile: /etc/ssl/mongodb.pem
The value of PEMKeyFile is the full path to the mongodb.pem file (the one that we created by concatenating the certificate and private key).
Now to connect to MongoDB using the shell we need to start the shell with the following arguments:
mongo --ssl --sslPEMKeyFile /etc/ssl/mongodb.pem --sslAllowInvalidCertificates
Now, after we have configured MongoDB to use SSL, we need to change our GO code to use SSL also to connect to MongoDB. To do that we need to use crypto library.
If we open the mongodb-cert.crt file with any text editor, we will see that this file is a string base64 encoded. We are going to need to use this file to connect to MongoDB. We should read the file from the disk, but for the purpose of this blog, I am going to hard code the the contents of the certificate file to GO code.
So to get a valid MongoDB session in our GO code we use the following code:
package db

import (
 "crypto/tls"
 "crypto/x509"
 "net"
 "gopkg.in/mgo.v2"
)  


const rootPEM = `
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIJAIWbTDoXSnjGMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
Q29tcGFueSBMdGQwHhcNMTYxMjMxMDAxMTE1WhcNMTcxMjMxMDAxMTE1WjBCMQsw
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
vo86T254hx88jjNhQzjaLvkbnq7EL/TB02zeuoH81d4kt4YSptOXUxN2kVZLzAKB
05viULTDBiG+B69doZfWA2nXMo7CiGJrVbqprDT3796XDmwe5e2PdpZnvVwVl9QT
1D+u/QcDntNa49wxlnls6vyBKumGJQzIVatV9h+NOlRQohxrDjFVO75njJlOXH5G
w7k1yjpHzMBTqRu0UoFQtWRr9EE3MN39ID6/4V8nvlskX8E/jqMsJYw92lNgnsNF
EzPSJyNe8NKm7//4bcJL73qo+m6XF1Qg3SmNaBUzmDBnwHdPy31Dy+ojRkY3ad7d
2DizNKovmKEPFPtwL5PfiQIDAQABo1AwTjAdBgNVHQ4EFgQUg24bTv/PrV0n4cqu
vErEzWaezfswHwYDVR0jBBgwFoAUg24bTv/PrV0n4cquvErEzWaezfswDAYDVR0T
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEALU9OrXuSkJV5Cio33bJVxXmBLoKn
HO1P4vki4Un5hXCKgWH6jk8JEZQLcObUZHpQiIDb2CZM8RnkTWW/kYXPqb1S/w7/
47QXsYFrN/ua0yhHRlHw9feaMVaVwzGrULeloVphoJz8GgJz5HRpyUUlbWwAfHnp
Wm1834BhzFzUGyCWbM9IC9vYKKXJsSb4PxuVFrlME42IPUFu6EPZJb8SKkbE0Lz8
dqZRtALUcq9ZhjQoWGg1bk+HNvSLABURMA+EqUNAIF6d7eeNNdk8NI1cb1mKmTOG
lo+N/pelbegCGzlLvJ79GI27DMm+uQfQtIm/lQ0A9dxMxi3SHDbD0SxARg==
-----END CERTIFICATE-----`


func Open() *mgo.Session {
 var err error

 roots := x509.NewCertPool()
 ok := roots.AppendCertsFromPEM([]byte(rootPEM))
 if !ok {
  panic("failed to parse root certificate")
 }

 tlsConfig := &tls.Config{
  RootCAs:            roots,
  InsecureSkipVerify: true,
 }

 dialInfo, err := mgo.ParseURL("mongodb://mongoUser:userPassword@localhost:27017/myDatabase")
 if err != nil {
  log.Println(err)
  panic(err)
 }

 log.Println("dialInfo : ", dialInfo)

 dialInfo.DialServer = func(addr *mgo.ServerAddr) (net.Conn, error) {
  conn, err := tls.Dial("tcp", addr.String(), tlsConfig)
  if err != nil {
   log.Println(err)
  }
  return conn, err
 }

 //Here is the session you are looking for. Up to you from here ;)
 session, err = mgo.DialWithInfo(dialInfo)
 if err != nil {
  log.Println(err)
  panic(err)
 }

 return session
}

Now we should have a valid MongoDB session and we should be able to access all our collections and documents from our GO application.

EDIT:

I tested SSL Security with MongoDB Community Edtion 3.4.1