Securing MongoDB using Let's Encrypt certificate

I had to setup few instances of MongoDB in DigitalOcean but I also wanted to make them secure using TLS/SSL. One way would have been to pay for a wildcard certificate and deal with all the hassle associated with setting up TLS/SSL using that certificate.

Gladly for all of us we have Let's Encrypt, which I believe is one of the best things that happened to make web secure. In this tutorial we are going to look into how to setup TLS/SSL for MongoDB using Let's Encrypt certificate along with auto renewing the certificate.

Making MongoDB publicly accessible

I assume you already have a MongoDB instance setup. We need to make sure that you can access that instance publicly.  If you haven't already made it accessible publicly then lets do that.

Secure your DB

First thing first, making your DB public means that it will be susceptible to attacks which means you need to make sure it is secured.

Make sure you have auth enabled for your MongoDB so that only authorized users can access your instance and with limited privileges e.g. read-only, read-write etc. You can follow the steps in MongoDB documentation here to setup authentication for your DB. Make sure to follow the principle of giving users the least privilege they need to be able to function. For example a service which needs to only read data from a particular DB should have a read-only role only for that DB.

Along with that do take a look at the security checklist provided by MongoDB to make sure all your bases are covered.

Make it publicly accessible

Now configure your MongoDB to bind to all IPs. If you have a configuration file then make sure it has the following settings

net:
  port: 27017
  bindIp: 0.0.0.0

Restart your mongo instance so that it picks up the new configuration. Though the port here is 27017 but generally it's better to change it so that someone can not easily guess what your port is.

Now you need to allow that port from your firewall. In case of Ubuntu we can use Uncomplicated Firewall

sudo ufw allow OpenSSH
sudo ufw allow 27017
sudo ufw enable

This will allow the SSH port to your VM so that you can still SSH into it after the firewall is turned on and then we enable 27017 port and at the end we enable the firewall. You can run ufw status to make sure that the firewall is indeed enabled.

Now you need to point some domain/subdomain to your VM public address/IP. You can use A record in your DNS configuration to point your domain to VM IP.

Setup Nginx

To setup certificate using Let's Encrypt it needs to verify that your domain is actually owned by you. Simply put it does that by placing a file in your local machine and then trying to access it from your provided domain to see if it can access the file. To access the file it needs to be served using HTTP server and in this case we use Nginx.

sudo apt-get update
sudo apt install nginx
sudo ufw allow 'Nginx Full'

The above will install nginx and also allow the ports using the firewall that we enabled. Now we need to specify the domain in the nginx configuration.

First make a backup of /etc/nginx/sites-available/default and then edit it.  In configuration where it specifies server_name _ make sure to replace it with your domain e.g. server_name foo.example.com. You can then run sudo nginx -t to confirm if the configuration file is valid.

At the end restart the nginx so it picks up the new configuration file

sudo systemctl reload nginx

Setup certbot

Certbot is the tool that automatically sets up the TLS/SSL certificate for the HTTP server.

sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install software-properties-common
sudo apt-get install python-certbot-nginx 

This will install the certboot tool. You can take a look at certbot website to see the steps to install it for your specific machine.

Create TLS/SSL certificate for MongoDB

We need to run certbot to let it configure the certificate and then from that certificate we need to create the .pem file containing the public and private certificate chain which MongoDB uses.

Run the following and it will start a step by step wizard for setting up TLS/SSL for your domain. Go through the steps in the wizard.

sudo certbot --nginx -d foo.example.com

Now you should have a certificate installed and your HTTP web server will be serving pages using HTTPS. You can verify that by visiting your domain.

I assume you have a mongodb user and your mongod instance is being run using that user. Lets create the mongo.pem file which will then be used by MongoDB.

sudo cat /etc/letsencrypt/archive/foo.example.com/{fullchain1.pem,privkey1.pem} | sudo tee /etc/ssl/mongo.pem
sudo chown mongodb:mongodb /etc/ssl/mongo.pem
sudo chmod 600 /etc/ssl/mongo.pem

So here we are concatenating the content of two files fullchain1.pem and privkey1.pem into a single file mongo.pem.

Edit your mongo configuration file to have the following

net:
  port: 27017
  bindIp: 0.0.0.0
  ssl:
    mode: requireSSL
    PEMKeyFile: /etc/ssl/mongo.pem

The requireSSL requires all nodes in your mongo cluster to use SSL. Though that is preferred but if you don't want that then you can change it to some other value as mentioned in MongoDB documentation.

If you restart your mongod it should now be using the TLS/SSL.

You would think you are done but we have a little more to go so bare with me. Let's Encrypt only creates a certificate for a specific period and it will be invalid after that hence we have to renew the certificate after a while. We don't want to go through the process again every month or so hence we will use a simple script to renew it for us.

Setup certificate renew script

Lets create a script /path/to/renew-mongo-cert.sh and make sure it has the following contents

Make sure to set the DOMAIN to your domain.

The script is basically doing what we did earlier. It runs certbot renew to renew the certificate which creates new files which it concatenates into mongo.pem and restarts the mongod.

Be aware the script will restart the MongoDB so if you don't want it to automatically restart then remove it but you would have to make sure it gets restarted so it picks up the new cert.

Make the script executable chmod +x /path/to/renew-mongo-cert.sh. We will leverage cron to setup a recurring task of executing the script. Run crontab -e and add the following

0 0 1 * * /path/to/renew-mongo-cert.sh

This will setup a cron job to run the script first day of the month at midnight.

Lastly run sudo certbot renew --dry-run to make sure that the certbot renewal works properly. This will not actually renew it but just dry run the process.

You now have a TLS/SSL setup for your MongoDB with auto renewal of certificate.

Extra recommendation for production DBs

Now you have a valid TLS/SSL certificate, but we also have a dependency on Let's Encrypt servers to renew the certificate. If you are using this in a critical production database then make sure to modify the renew script and cron job to cater for failure if the Let's Encrypt servers ever go down so that you are not stranded with an expired certificate.

References