May 20, 2019

Secure MongoDB with Docker

Secure MongoDB with Docker

This time we are going to deploy a MongoDB instance within Docker and secure it with user authentication.

When deploying MongoDB, it does not enable authentication by default. This might be desirable for local development, but when it comes time to deploy your application in projection that last thing you want is your database wide open for anyone to access. Over the last year thousands of systems have been comprised due to not enabling authentication on the database.

Whether you are installing Mongo on your server, or deploying it in Docker, the following guide is similar for both.

The Objective

After following these steps, we will have a Docker container running MongoDB with user authentication for our application. Additionally we are going to lock down remote access to the database so it only accepts connections from our application.

Prerequisites

Before we begin, make sure you have Docker installed. You will also want to have a backend server to test against. For this example, we are going to use the Loopback 4 API we build in the last post. You can find that here.

Enabled Authentication

Mongo releases an official Docker image, which we are going to be using. Although other people have created images with authentication already enabled, it is always better when you use an official image if it is available. You can find the Mongo image here.

The Mongo image accepts environment variables, which allows us to set the admin username and password.

We do this, by providing the following parameters:

- MONGO_INITDB_ROOT_USERNAME=admin
- MONGO_INITDB_ROOT_PASSWORD=password

When you create your docker container, it will enable authentication and set the admin account to the above. Yay! :)

Create Application User

Next, we are going to create a database user for our application. The docker image does not allow us to set this in environment variables, but it does provide a way of executing a script after the container starts. We will use this to setup our app user.

Create a folder called docker_scripts in your project folder, and inside that create a script call db_setup.sh

#!/usr/bin/env bash

echo 'Creating application user and db'

mongo ${MONGO_INITDB_DATABASE} \
        --host localhost \
        --port ${MONGO_PORT} \
        -u ${MONGO_INITDB_ROOT_USERNAME} \
        -p ${MONGO_INITDB_ROOT_PASSWORD} \
        --authenticationDatabase admin \
        --eval "db.createUser({user: '${DATABASE_USERNAME}', pwd: '${DATABASE_PASSWORD}', roles:[{role:'dbOwner', db: '${MONGO_INITDB_DATABASE}'}]});"

This script is going to be executed inside our docker container. You'll notice that we are using the same environment variables as above, but also some new ones we haven't set yet.

Environment Variables

Speaking of environment variables, lets set those now. Create a .env file in your project folder and add it to your .gitignore and .dockerignore file. We don't want this file syncing with our project.

.env

MONGO_HOST=db
MONGO_PORT=27017
MONGO_INITDB_DATABASE=todo-list
MONGO_INITDB_ROOT_USERNAME=admin
MONGO_INITDB_ROOT_PASSWORD=admin_password
DATABASE_USER=app_user
DATABASE_PASSWORD=app_password

Putting it all together

Lets use the information we have learned and put it all together. Create a docker_compose.yml file in your project folder, and put in the following

version: '3'

services:
  nodejs:
    build:
      context: .
      dockerfile: Dockerfile
    image: nodejs
    container_name: nodejs
    restart: unless-stopped
    env_file: .env
    environment:
      - MONGO_USERNAME=$DATABASE_USERNAME
      - MONGO_PASSWORD=$DATABASE_PASSWORD
      - MONGO_HOST=db
      - MONGO_PORT=$DATABASE_PORT
      - MONGO_DB=$DATABASE
    ports:
      - '3000:3000'
    networks:
      - todo-network
    links:
      - mongo
  mongo:
    image: mongo
    container_name: db
    restart: unless-stopped
    env_file: .env
    environment:
      - MONGO_INITDB_ROOT_USERNAME=$DATABASE_ROOT_USERNAME
      - MONGO_INITDB_ROOT_PASSWORD=$DATABASE_ROOT_PASSWORD
      - MONGO_INITDB_DATABASE=$DATABASE
      - DATABASE_USERNAME=$DATABASE_USERNAME
      - DATABASE_PASSWORD=$DATABASE_PASSWORD
      - MONGO_PORT=$DATABASE_PORT
    volumes:
      - /home/s0n1c/tmp/db:/data/db
      - ./docker_scripts/:/docker-entrypoint-initdb.d
    # ports:
    # - 27017:27017
    networks:
      - todo-network
networks:
  todo-network:
    driver: bridge

The first part of the file sets up the our docker image for our loopback api server. The second part is where we are setting up our mongo image. Notice we are importing our .env file and using the vars declared in that file in the environment section. The other important part is the second volume. We are mapping our local docker_scripts folder to the containers /docker-entrypoint-initdb.d directory. This is where our db application user is created.

Both images we are creating here use a bridged network we are creating called todo-network. This allows both images to communicate between themselves freely without allowing any remote connects to the mongodb. If you do want to enable remote access to your database, uncomment the ports section.

Before this will work, you will have to update your application to use the database user/password

Conclusion

That's it for this post. I hope you found it useful and can now deploy mongodb containers with user authentication for your applications.

I have added this as part of the Loopback 4 Todo List API, which is posted in Github, here