DEV Community

Cover image for Tutorial — Configuring a Local MongoDB-Docker ReplicaSet with 3Nodes and TLS.
Arthur Germano
Arthur Germano

Posted on • Edited on

Tutorial — Configuring a Local MongoDB-Docker ReplicaSet with 3Nodes and TLS.

And yes… All easy with shell scripts (Linux) and ready to run! ;)!

Hi there.. This is my very first article! I am excited about it.

The development is a difficult area, specially because we have to know everything, literally. Since new technologies up to new business rules. In my belief who works with code, programming, configuring servers, databases, front-ends, back-ends are the today’s magicians of a RPG game.

Anyway, let’s start talking about the important stuff. I intend to explain how I did configure a MongoDB ReplicaSet with 3 nodes in Docker Containers. In the next lines I will deliver a shell script to create ALL the certificates, CA, Server and Client to connect, as the docker compose file. Plus you can grab and customize it at your will and risk.

If you didn’t understand anything from the last phrase above.. well you need to start studying because your path is long my friend.

First things first, an image to help us visualize the configuration settings of what we will be accomplishing.
MongoDockerStructure

created with https://app.diagrams.net/

The docker-compose.yml file

So we have to create the following docker-compose file, all the explanation is in its comments section by section:

# docker-compose yml file 
# docker version must be compatible with your running docker version # for more info see https://docs.docker.com/compose/compose-file/compose-versioning/
# Don't forget to use only spaces to indent ok!
version: "3.9"
services:
  # The container mongo_node_1 configurations #############
  mongo_node_1:
    container_name: mongo_node_1
    hostname: mongo_node_1
    # the image from the docker-hub repository
    image: "mongo"
    # mongo uses port 27017 to change see more info https://hub.docker.com/_/mongo
    expose:
      - "27017"
    # here comes the interesting part, with volumes we can easily insert all the configuration that we need!
    volumes:
      # This volume will hold the database information, what you save, your collections, documents, etc. Since we have 3 nodes we need three places which will hold information this is the first one
      - /path/to/your/OS/folder/data/node_1:/data/db
      # This volume will hold the Server Certificate file which will be signed by the CA and will make work TLS - The shell script create all the folders inside ssl just need to run the script!
      - /path/to/your/OS/folder/ssl/node_1:/data/ssl
      # This volume will hold this node cluster configuration! Each node has it's own configuration and differences, but to simplify things I just created ONE configuration to dominate them ALL =D! The file is the cluster.conf 
      - /path/to/your/OS/folder/config:/data/config
    # always restart right.. keep it alive!
    restart: always
    # entrypoint will execute a command inside the container as soon as it starts. So basically we are saying run mongo database with this file configuration settings. Note that the cluster.conf is inside the volume folder set in the volume section. Since it is mapped inside the container we don't need to copy any files (nice and slender!)
    entrypoint: ["/usr/bin/mongod", "--config", "/data/config/cluster.conf"]
    # setting the inside network 
    networks:
      # the name of the below configured network we have to belong to it
      my_network:
        # we have to specify a FIXED IP - this IP will be use after
        ipv4_address: 170.17.5.5

# This works like MAGIC - this allows us to connect inside the network "my_network" FROM THE HOST - because we are creating a closed network we would have to configure a DNS server only to reach it. So very complicated. This option make it easy for us! Check your ip address with ifconfig in Linux and REPLACE "(YOUR MACHINE/SERVER IP)"
    extra_hosts:
      - "localhost:(YOUR MACHINE/SERVER IP)"
    # mongo images use this variables inside the container and they are very helpful. Set the username and password of the Database. We are going to have to configure it by hand anyway but it's nice to have it there
    environment:
      MONGO_INITDB_ROOT_USERNAME: mdb_admin
      MONGO_INITDB_ROOT_PASSWORD: mdb_pass
  # The container mongo_node_2 configurations #############
  mongo_node_2:
    container_name: mongo_node_2
    hostname: mongo_node_2
    # the image from the docker-hub repository
    image: "mongo"
    # mongo uses port 27017 to change see more info https://hub.docker.com/_/mongo
    expose:
      - "27017"
    # here comes the interesting part, with volumes we can easily insert all the configuration that we need!
    volumes:
      # This volume will hold the database information, what you save, your collections, documents, etc. Since we have 3 nodes we need three places which will hold information this is the second one
      - /path/to/your/OS/folder/data/node_2:/data/db
      # This volume will hold the Server Certificate file which will be signed by the CA and will make work TLS - The shell script create all the folders inside ssl just need to run the script!
      - /path/to/your/OS/folder/ssl/node_2:/data/ssl
      # This volume will hold this node cluster configuration! Each node has it's own configuration and differences, but to simplify things I just created ONE configuration to dominate them ALL =D! The file in the cluster.conf 
      - /path/to/your/OS/folder/config:/data/config
    # always restart right.. keep it alive!
    restart: always
    # entrypoint will execute a command inside the container as soon as it starts. So basically we are saying run mongo database with this file configuration settings. Note that the cluster.conf is inside the volume folder set in the volume section. Since it is mapped inside the container we don't need to copy any files (nice and slender!)
    entrypoint: ["/usr/bin/mongod", "--config", "/data/config/cluster.conf"]
    # setting the inside network 
    networks:
    # the name of the below configured network we have to belong to it
      my_network:
        # we have to specify a FIXED IP - this IP will be use after
        ipv4_address: 170.17.5.6
# This works like MAGIC - this allows us to connect inside the network "my_network" FROM THE HOST - because we are creating a closed network we would have to configure a DNS server only to reach it. So very complicated. This option make it easy for us! Check your ip address with ifconfig in Linux and REPLACE "(YOUR MACHINE/SERVER IP)"
    extra_hosts:
      - "localhost:(YOUR MACHINE/SERVER IP)"
    # mongo images use this variables inside the container and they are very helpful. Set the username and password of the Database. We are going to have to configure it by hand anyway but it's nice to have it there
    environment:
      MONGO_INITDB_ROOT_USERNAME: mdb_admin
      MONGO_INITDB_ROOT_PASSWORD: mdb_pass
  # The container mongo_node_arbiter configurations #############
  mongo_node_arbiter:
    container_name: mongo_node_arbiter
    hostname: mongo_node_arbiter
    # the image from the docker-hub repository
    image: "mongo"
    # mongo uses port 27017 to change see more info https://hub.docker.com/_/mongo
    expose:
      - "27017"
    # here comes the interesting part, with volumes we can easily insert all the configuration that we need!
    volumes:
      # This volume will hold the database information, what you save, your collections, documents, etc. Since we have 3 nodes we need three places which will hold information this is the third one and the arbiter only chooses which node is primary or secondary
      - /path/to/your/OS/folder/data/node_arbiter:/data/db
      # This volume will hold the Server Certificate file which will be signed by the CA and will make work TLS - The shell script create all the folders inside ssl just need to run the script!
      - /path/to/your/OS/folder/ssl/node_arbiter:/data/ssl
      # This volume will hold this node cluster configuration! Each node has it's own configuration and differences, but to simplify things I just created ONE configuration to dominate them ALL =D! The file in the cluster.conf 
      - /path/to/your/OS/folder/config:/data/config
    # always restart right.. keep it alive!
    restart: always
    # entrypoint will execute a command inside the container as soon as it starts. So basically we are saying run mongo database with this file configuration settings. Note that the cluster.conf is inside the volume folder set in the volume section. Since it is mapped inside the container we don't need to copy any files (nice and slender!)
    entrypoint: ["/usr/bin/mongod", "--config", "/data/config/cluster.conf"]
    # setting the inside network 
    networks:
      # the name of the below configured network we have to belong to it
      my_network:
        # we have to specify a FIXED IP - this IP will be use after
        ipv4_address: 170.17.5.7
# This works like MAGIC - this allows us to connect inside the network "my_network" FROM THE HOST - because we are creating a closed network we would have to configure a DNS server only to reach it. So very complicated. This option make it easy for us! Check your ip address with ifconfig in Linux and REPLACE "(YOUR MACHINE/SERVER IP)"
    extra_hosts:
      - "localhost:(YOUR MACHINE/SERVER IP)"
    # mongo images use this variables inside the container and they are very helpful. Set the username and password of the Database. We are going to have to configure it by hand anyway but it's nice to have it there
    environment:
      MONGO_INITDB_ROOT_USERNAME: mdb_admin
      MONGO_INITDB_ROOT_PASSWORD: mdb_pass
# Finally we create our network. And WE HAVE to create it. Docker only allows us to set IP address for custom networks so here we go
networks:
  # this is your custom network NAME inside this file 
  my_network:
    # this name is the docker network name outside. We can check it with docker network inspect my_network_default  
    name: my_network_default
    # here we configure the driver bridge is default, and set the network specifics
    ipam: 
      driver: default
      config:
        - subnet: 170.17.0.0/16
          ip_range: 170.17.0.0/16
          gateway: 170.17.5.254
# THE END
Enter fullscreen mode Exit fullscreen mode

Wow.. this is a huge, easy to get lost docker-compose.yml file. I will not be able to explain everything, but if you copy that, create a file named docker-compose.yml inside a folder, paste it, and then open with your linux terminal that folder and run the following command:

# what this command does is.. it starts docker-compose.yml
# removes any possible previous set containers created
# forces to recreate all container inside the docker-compose.yml
# and for last... renew the volumes - DOES NOT recreate it,  just renew it's inside volumes
docker-compose up --remove-orphans --force-recreate --renew-anon-volumes
Enter fullscreen mode Exit fullscreen mode

Of course it will throw an error:
FirstRunError

the minimum we can do is try!

But the first part it okay! If you had any doubt about what each section of the file means, please read the comments inside the file. And an important note we will always run the docker-compose-yml with that command.


The Cluster.conf file

So.. let’s move on to the next part. We have to configure each node with its own configuration. But, if you read the comments, I have created only one file config to make our lives easier.
To do that create a folder named “config” in the same folder where the docker-compose lies. Create a file name cluster.conf with the following content:

storage:
  dbPath: /data/db
replication:
  replSetName: my_mdb_rs
  enableMajorityReadConcern: true
net:
  port: 27017
  bindIpAll: true
  tls:
    mode: requireTLS
    CAFile: /data/ssl/mdb_root_CA.crt
    certificateKeyFile: /data/ssl/mdb_nodes_keycert.pem
    certificateKeyFilePassword: mdb_my_custom_passphrase_security
    clusterFile: /data/ssl/mdb_nodes_keycert.pem
    clusterPassword: mdb_my_custom_passphrase_security
    allowInvalidCertificates: true
    allowInvalidHostnames: true
security:
  authorization: enabled
  clusterAuthMode: x509
Enter fullscreen mode Exit fullscreen mode

Let’s review each section:

  • storage > dbPath: is where you set the volume data inside the docker-compose.yml file
  • replication > replSetName: this is the Mongo Replica Set name we will use it in several places
  • replication > enableMajorityReadConcern: it is not a must have config, but it is nice to enable it
  • net > port: the node configuration port, must be the same of the docker-compose.yml file. We didn’t specify one directly because 27017 is the default port of mongo DB
  • net > bindIpAll: the node IP. But since we are using the same file for all nodes and each node has it own IP we need to bind any IP that tries to connect.
  • net > tls > mode: Here we can have some options and we are choosing the most restrict one.
  • net > tls > CAFile: The certificate authority file. The location where the file lies inside the container. For that each node has it’s volume and we have configured each volume inside the docker-compose.yml.
  • net > tls > certificateKeyFile: This node server certificate file. The location where the file lies inside the container. For that each node has it’s volume and we have configured each volume inside the docker-compose.yml.
  • net > tls > certificateKeyFilePassword: This node password server certificate file. I have create the same password for each file and this is not a recommended thing to do.
  • net > tls > clusterFile: Since we create a cluster and each node has it’s own certificate we can connect to the cluster with them. The location where the file lies inside the container. For that each node has it’s volume and we have configured each volume inside the docker-compose.yml.
  • net > tls > clusterFilePassword: Cluster password certificate file. I have create the same password for each file and this is not a recommended thing to do.
  • net > tls > allowInvalidCertificates: Allows us to connect with self-signed certificates. (It isn’t a safe production measure, so use with caution)
  • net > tls > allowInvalidHostnames: We don’t have valid host names so we by pass using this option. (It isn’t a safe production measure, so use with caution)
  • security > authorization: Enable authorization on mongoDB
  • security > clusterAuthMode: There are some options here, the cool and easiest one is with keyfile. But we are using a little bit more secure. ### Important Note

I wrote above and I will repeat it here, the “CAFile”, “certificateKeyFile” and “clusterFile” are mapped in the docker-compose.yml file. So we have created a folder and inside of it the docker-compose-yml and a folder named “config” folder with our “cluster.conf” file inside this last one right ? So our volume section of each node inside the docker-compose-yml file must point to this very created “config” folder. Put the absolute path to be sure!

- /path/to/your/OS/folder/config:/data/config
Enter fullscreen mode Exit fullscreen mode

The Certificate Mess — Made Easy Peasy

Part One — Shell Script

Who doesn’t struggle with certificates ?

They are difficult to understand, in a way, so to make things easy.. very very easy. I have created an shell script to create all this things for us. Yes .. you and I.
What this script does, and which I have make my best efforts to create for the past week in my spare time, is to create the Certificate Authority Files, all the three nodes server certificate files, and the client certificate files to connect from your application like an NodeJS application or MongoCompass.

Without it we would have to execute several commands in the linux terminal and make sure that all of them are correct and with the right information in the right spot. It is A LOT to process.

Enough talk.. let’s see this shell script. For that, create a folder where the docker-compose-yml lies named “ssl”. After this step create a file inside this ssl folder named generateSSLCerts.sh. Remember to give execute permission to this file!

And just to get ahead create another folder named “confs” inside ssl folder as well.

On MAC there some issues with LibreSSL lib, and when running the script make sure that the command line is in the script folder, the script does not work if the path is relative.

#!/bin/sh
TXT_LOG=" ----- "
CONFS_FILES_DIR="./confs/"
# the paths must match in docker-compose.yml file
# must match the file name of cluster.conf
MDB_NODE1_DIR="node_1"
MDB_NODE1_KEY="mdb_node_1.key"
MDB_NODE1_CSR="mdb_node_1.csr"
MDB_NODE1_CRT="mdb_node_1.crt"
MDB_NODE1_CNF="${CONFS_FILES_DIR}mdb_node1_CN.cnf"
# the paths must match in docker-compose.yml file
# must match the file name of cluster.conf
MDB_NODE2_DIR="node_2"
MDB_NODE2_KEY="mdb_node_2.key"
MDB_NODE2_CSR="mdb_node_2.csr"
MDB_NODE2_CRT="mdb_node_2.crt"
MDB_NODE2_CNF="${CONFS_FILES_DIR}mdb_node2_CN.cnf"
# the paths must match in docker-compose.yml file
# must match the file name of cluster.conf
MDB_NODE_ARB_DIR="node_arbiter"
MDB_NODE_ARB_KEY="mdb_node_arbiter.key"
MDB_NODE_ARB_CSR="mdb_node_arbiter.csr"
MDB_NODE_ARB_CRT="mdb_node_arbiter.crt"
MDB_NODE_ARB_CNF="${CONFS_FILES_DIR}mdb_node_arbiter_CN.cnf"
# must match the file name of cluster.conf
MDB_CA_DIR="CA"
MDB_CA_KEY="mdb_root_CA.key"
MDB_CA_CRT="mdb_root_CA.crt"
MDB_CA_SRL="mdb_root_CA.srl"
MDB_CA_CNF="${CONFS_FILES_DIR}mdb_root_CA.cnf"
MDB_PASS_PHRASE_CA="mdb_my_custom_passphrase_security"
# must match the file name of cluster.conf
MDB_FINAL_KEYCERT_PEM="mdb_nodes_keycert.pem"
MDB_CLIENT_DIR="serverclient"
MDB_CLIENT_KEY="mdb_client.key"
MDB_CLIENT_CSR="mdb_client.csr"
MDB_CLIENT_CRT="mdb_client.crt"
MDB_CLIENT_PEM="mdb_client.pem"
MDB_CLIENT_CN_CNF="${CONFS_FILES_DIR}mdb_client_CN.cnf"
# param order
# DIR         ----- $1 - $X_DIR
# FILE        ----- $2 - $FILEVAR_NAME
move_files() {
mkdir $1 2> /dev/null
printf "Moving $2 to ./$1/$2 $TXT_LOG \n"
mv ./$2 ./$1/$2
}
# param order
# DIR         ----- $1 - $X_DIR
# FILE        ----- $2 - $FILEVAR_NAME
copy_files() {
mkdir $1 2> /dev/null
printf "Copying $2 to ./$1/$2 $TXT_LOG \n"
cp ./$2 ./$1/$2
}
# param order
# DIR         ----- $1 - $MDB_NODEX_DIR
# NODE .key file  ----- $2 - $MDB_NODEX_KEY
# NODE .csr file  ----- $3 - $MDB_NODEX_CSR
# NODE .cnf file  ----- $4 - $MDB_NODEX_CNF
# MDB CA .crt file  ----- $5 - $MDB_CA_CRT
# MDB CA .key file  ----- $6 - $MDB_CA_KEY
# NODE .crt file  ----- $7 - $MDB_NODEX_CRT
gen_node_keycerts(){
printf "\nSTARTING $1 Certificates $TXT_LOG \n"
mkdir $1 2> /dev/null
printf "Generating $1 - KEY and CSR files $TXT_LOG\n"
openssl genrsa -des3 -out $2 -passout pass:"$MDB_PASS_PHRASE_CA" 4096
printf "\nGenerating $1 - CRT file $TXT_LOG\n"
openssl req -new -config $4 -key $2 -passin pass:"$MDB_PASS_PHRASE_CA" -out $3 -config $4
openssl x509 -req -days 365 -in $3 -CA $5 -CAkey $6 -CAcreateserial -passin pass:"$MDB_PASS_PHRASE_CA" -out $7
printf "\nGenerating $1 - PEM file $TXT_LOG\n"
cat $2 $7 > $MDB_FINAL_KEYCERT_PEM
move_files $1 $2
move_files $1 $3
move_files $1 $7
move_files $1 $MDB_FINAL_KEYCERT_PEM
copy_files $1 $5
printf "\nFINISHED $1 $TXT_LOG\n"
}
openssl rand -writerand .rnd
printf "STARTING SCRIPT $TXT_LOG\n\n"
openssl genrsa -des3 -out $MDB_CA_KEY -passout pass:"$MDB_PASS_PHRASE_CA" 4096
printf "Root CA key OK $TXT_LOG \n"
openssl req -x509 -new -key $MDB_CA_KEY -sha256 -passin pass:"$MDB_PASS_PHRASE_CA" -days 720 -out $MDB_CA_CRT -config $MDB_CA_CNF
printf "Root CA crt OK $TXT_LOG \n"
printf "FINISHED CA CERTIFICATE $TXT_LOG \n"
gen_node_keycerts $MDB_NODE1_DIR $MDB_NODE1_KEY $MDB_NODE1_CSR $MDB_NODE1_CNF $MDB_CA_CRT $MDB_CA_KEY $MDB_NODE1_CRT
gen_node_keycerts $MDB_NODE2_DIR $MDB_NODE2_KEY $MDB_NODE2_CSR $MDB_NODE2_CNF $MDB_CA_CRT $MDB_CA_KEY $MDB_NODE2_CRT
gen_node_keycerts $MDB_NODE_ARB_DIR $MDB_NODE_ARB_KEY $MDB_NODE_ARB_CSR $MDB_NODE_ARB_CNF $MDB_CA_CRT $MDB_CA_KEY $MDB_NODE_ARB_CRT
printf "Generating client access certificates key and cert $TXT_LOG \n"
openssl req -new -out $MDB_CLIENT_CSR -keyout $MDB_CLIENT_KEY -passout pass:"$MDB_PASS_PHRASE_CA" -config $MDB_CLIENT_CN_CNF
printf "Signing client access certificates $TXT_LOG \n"
openssl x509 -req -in $MDB_CLIENT_CSR -CA $MDB_CA_CRT -CAkey $MDB_CA_KEY -passin pass:"$MDB_PASS_PHRASE_CA" -out $MDB_CLIENT_CRT
printf "Generating client PEM file $TXT_LOG \n"
cat $MDB_CLIENT_KEY $MDB_CLIENT_CRT > $MDB_CLIENT_PEM
move_files $MDB_CLIENT_DIR $MDB_CLIENT_CSR
move_files $MDB_CLIENT_DIR $MDB_CLIENT_KEY
move_files $MDB_CLIENT_DIR $MDB_CLIENT_CRT
move_files $MDB_CLIENT_DIR $MDB_CLIENT_PEM
copy_files $MDB_CLIENT_DIR $MDB_CA_CRT
move_files $MDB_CA_DIR $MDB_CA_KEY
move_files $MDB_CA_DIR $MDB_CA_CRT
move_files $MDB_CA_DIR $MDB_CA_SRL
printf "FINISHED $1 $TXT_LOG\n"
Enter fullscreen mode Exit fullscreen mode

This is a pretty long script file. I will not explain what it does step-by-step. What you have to know is the following things:

MDB_PASS_PHRASE_CA=”mdb_my_custom_passphrase_security” — This variable holds the password configured inside the cluster.conf file. We use the same password for everything and it is not a recommended thing to do! If you want you can set your own custom password, but keep in mind that you have to change it in the cluster.conf file and when connecting with the clients.

The Folder Structure created reflects what we have declared inside of docker-compose.yml file. So the variables in the shell can be customized, but you have to remember to changed it in all other places too, which means all the volumes must point to the correct folder. For example:

# Note that we set each node volume like to ../../node_X:/data/ssl where "X" is the node name.
# the "node_X" part - the script will create it and it is declared in the _DIR variables of the script.
# the /data/ssl/ part - lies inside the container and we are mapping all the content inside ../../../node_X to /data/ssl. For example node_1
- /path/to/your/OS/folder/ssl/node_1:/data/ssl/
Enter fullscreen mode Exit fullscreen mode

Although we have our script ready to run, if you do so, it will run with errors! (Not recommended!)
That happens because we are not ready yet. To spare us to execute any kind of interaction with the commands of the shell script with have a couple of configuration files. =S


Part two — Configuration Files

The configuration files are all the names, options and choices that we have to make when generating certificates. Since we are creating self-signing certificates we can have it done this way. So it is not for production!

Only to make things easier “for me”, I will only show the two files. But for the script actually are five files, which four of them have the same content. Again, it is not recommended for production!

The first one is the Certificate Authority Configuration File. I will not comment all it’s items, mostly because I don’t know them well and I found it somewhere in the internet!

So to start this section create a file named “mdb_root_CA.cnf” inside of the previously create folder named “confs” with the following content:

RANDFILE        = ./.rnd
####################################################################
[ ca ]
# The default ca section
default_ca    = CA_default
[ CA_default ]
# How long to certify for
default_days     = 1000
# How long before next CRL
default_crl_days = 30
# Use public key default MD
default_md       = sha256
# Keep passed DN ordering
preserve         = no
# The extensions to add to the cert
x509_extensions = ca_extensions
# Don't concat the email in the DN
email_in_dn     = no
# Required to copy SANs from CSR to cert
copy_extensions = copy
# Set to 'no' to allow creation of 
# several certificates with same subject.
unique_subject = no
####################################################################
[ req ]
default_bits       = 4096
distinguished_name = ca_distinguished_name
x509_extensions    = ca_extensions
string_mask        = utf8only
prompt = no
####################################################################
[ ca_distinguished_name ]
countryName         = BR
stateOrProvinceName         = Parana
localityName                = YourCity
organizationName            = Your Company Name
organizationalUnitName         = Company Dev Departament
commonName         = yourcompanycn.com
emailAddress = yourcompanyemail@email.com
####################################################################
[ ca_extensions ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints       = critical, CA:true
keyUsage               = keyCertSign, cRLSign, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
####################################################################
[ signing_policy ]
countryName            = optional
stateOrProvinceName    = optional
localityName           = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied
emailAddress           = optional
####################################################################
[ signing_req ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints       = CA:FALSE
keyUsage               = keyCertSign, cRLSign, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
Enter fullscreen mode Exit fullscreen mode

The second content will be used for all the other four files. There are three server certificate configuration files and one client certificate connection configuration file.

To do that create four files named as it follows:

  • “mdb_node1_CN.cnf”;
  • “mdb_node2_CN.cnf”;
  • “mdb_node_arbiter_CN.cnf”;
  • “mdb_client_CN.cnf”;

All of them with the same following content:

RANDFILE        = ./.rnd
####################################################################

[ req ]
default_bits       = 2048
distinguished_name = server_distinguished_name
req_extensions     = server_req_extensions
string_mask        = utf8only
prompt = no

####################################################################
[ server_distinguished_name ]
countryName         = BR
stateOrProvinceName         = Parana
localityName                = YourCity
organizationName            = Your Company Name
commonName         = mdb_node_cn
emailAddress = yourcompanyemail@email.com
####################################################################

[ server_req_extensions ]
subjectKeyIdentifier = hash
basicConstraints     = CA:FALSE
keyUsage             = digitalSignature, keyEncipherment
subjectAltName       = @alternate_names
nsComment            = "OpenSSL Generated Certificate"
extendedKeyUsage = clientAuth, serverAuth
####################################################################

[ alternate_names ]
IP.1 = 170.17.5.5
IP.2 = 170.17.5.6
IP.3 = 170.17.5.7
IP.4 = 170.17.5.254
IP.5 = 127.0.0.1
IP.6 = ::1
DNS.1 = localhost
Enter fullscreen mode Exit fullscreen mode

Just to emphasize, I will repeat that I don’t know very well all items inside this configuration file. I found them on the internet across many stack overflow questions, MongoDB pages and etc. I will try to reference most of them at the final of this tutorial.

Now we are good to go. So go ahead and run the generateSSLCerts.sh script in the Linux terminal. It will print step-by-step of what is going on. No errors should occur.

If everything goes well we must go from this: FolderBeforeRunScript

ssl folder content before run the script

To the following: FolderAfterRunScript

ssl folder content after you run the script

Each folder contains all the files needed to the containers operate with TLS. And that is it for this section. And we are almost finished. Just to show you, go back one folder and this is what you should see: RootFolder

root folder where we a creating this amazing secure thing — yes it is missing the data folder


As you can see, there is one more folder structure that we have to create. It is the data folder which will hold the nodes database contents. Let’s go ahead and create a folder named “data” and inside of it you have to create three more folders named as it follows:
  • “db_node_1”;
  • “db_node_2”;
  • “db_node_arbiter”;

With that the folder structure are complete and we have all the necessary. Important thing to note we are creating exactly as we declared inside docker-compose.yml.

# for example - node_1 
- /path/to/your/OS/folder/data/node_1:/data/db
Enter fullscreen mode Exit fullscreen mode

Now let’s run docker-compose.yml and see what we got so far.


Configuring MongoDB ReplicaSet Cluster

After you run the command you should get lots of log lines in the terminal. And that is normal. It stops more or less like the next image:
LogDockerComposer

Matrix log style! “You get used to it, I don’t even see the code, All I see is blond, brunette, redhead…”

We are now running and that is a very nice thing. And yet we do not have our cluster. We have to configure it. Let’s get over with it.

Open another linux terminal shell and run the following command:

# it executes a shell inside then container. You can choose the container in which to run. All three containers are the same for now.
docker exec -it mongo_node_1 /bin/bash
Enter fullscreen mode Exit fullscreen mode

After we run the previous command we will enter inside the container. Now we have to login inside mongoDB node_1. How do we do that? Any clues? Took a lot for me too. But I finally could craft the final command as it follows:

# this command helps us to log as root inside mongodatabase
# we have to log as root to create the replicaset and the user
# IMPORTANT: Note that we are inside the container and they are reflecting our node mapped volume
# IMPORTANT2: remember the password set in the SCRIPT and cluster.conf. Yes we are using here to connect and decrypt the files
# IMPORTANT3: the option --tlsAllowInvalidHostnames is necessary because we are using self-signed certificates!
mongo --tls --tlsCertificateKeyFile /data/ssl/mdb_nodes_keycert.pem --tlsCAFile /data/ssl/mdb_root_CA.crt --tlsCertificateKeyFilePassword mdb_my_custom_passphrase_security --tlsAllowInvalidHostnames
Enter fullscreen mode Exit fullscreen mode

Now we are logged inside the database. Let’s configure it. The first step is to set the ReplicaSet as it follows:

rs.initiate({
  "_id": "my_mdb_rs", 
  "version": 1, 
  "writeConcernMajorityJournalDefault": true, 
  "members": [
    { 
      "_id": 0, 
      "host": "170.17.5.5:27017", 
    }, 
    { 
      "_id": 1, 
      "host": "170.17.5.6:27017", 
    }, 
    { 
      "_id": 2, 
      "host": "170.17.5.7:27017", 
      arbiterOnly: true 
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode

Important parts are the following:

The _id of the configuration is the ReplicaSet name, configured in the cluster file and when setting the mongo string connection.
The last node we are setting is the arbiter. It has the “arbiterOnly” flag to true
You will see something like this:

SetReplicaOnMongoDB

The vote happened, maybe you got the primary — not important

Now we have to set the user as it follows:

use admin;
db.createUser({
  user: "mdb_admin",
  pwd: "mdb_pass",
  roles: [
    {role: "root", db: "admin"},
    { role: "userAdminAnyDatabase", db: "admin" }, 
    { role: "dbAdminAnyDatabase", db: "admin" }, 
    { role: "readWriteAnyDatabase", db:"admin" }, 
    { role: "clusterAdmin",  db: "admin" }
  ]
});
Enter fullscreen mode Exit fullscreen mode

This will create the admin user that can connect properly outside the replicaset! It should be as demonstrated by the following image:
MongoReplicaSetConfigured

Success! We have now a configured mongo docker replica set with 3 nodes!

Now we are done! All set!

To test it you can exit mongo terminal, but continue inside the container. Then run the following command:

# Remember we set the user and pass environment into docker-compose.yml file. Well we can use them now!
mongo --tls --tlsCertificateKeyFile /data/ssl/mdb_nodes_keycert.pem --tlsCAFile /data/ssl/mdb_root_CA.crt --tlsCertificateKeyFilePassword mdb_my_custom_passphrase_security -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --sslAllowInvalidHostnames
Enter fullscreen mode Exit fullscreen mode

It should connect successfully! \o/. But wait… there is more! And a leave this section with the big question….

How can we connect now with MongoCompass or NodeJS ?


Connecting with MongoCompass on our ReplicaSet

Well, we started this journey a hundred years ago. You grow old and now.. look at you.. all smiley and wise!

Now we finish our journey with the cherry on the top!

To connect on mongocompass — first install it. It shouldn’t be difficult and I will not show it here. However I will go ahead and just open it and then show the following images that show how to configure it.

After you open MongoCompass click in the text:
“Fill in connection fields individually”

MongoCompassScreen1

Fill the fields as the image shows

For the next image, do remember the ssl folder ? So inside of it there is a folder named serverclient. There lies all the files that you need to select to connect and some more. You need to select the right ones so let’s see them:

  • Certificate Authority: mdb_root_CA.crt
  • Client Certificate: mdb_client.pem
  • Client Private key: mdb_client.key
  • Client Key Password: mdb_my_custom_passphrase_security

Also, fill the “Replica Set Name” correctly.
MongoCompassScreen2

What could go wrong ? After what we just went through this should be easy!

If you did everything correctly.. well you should be connected =). Let’s see the next screen:
MongoCompassConnected

Yes! We did it! =)

Connecting with Mongoose in a NodeJS Application

The last section, but not least, is to exemplify a connection with mongoose. The same again.. I will not show how to install and configure your NodeJS application with Mongoose, however I going to post the string connection that you can use. I struggled with it a little bit, but in the end I found the way.

See the next image:
NodeMongooseConfig

NodeJS configuration for Mongoose

Conclusion

Yes. We did it! I hope that this tutorial helped you because for me it was very difficult. I had to learn all by myself, researching in the web. All the knowledge is there, the computer community is amazing everyone helping everyone. So I am trying to do the same. I did it by myself because others did first and helped leading the way!

The code for this project is hosted on github!
Github Repository

I want to say that I am sorry for my english. It isn’t my first language… so if I misspoke or I did something wrong let me know and feel free to correct me. =)

Here there is some links which helped me find the path!
Docker - ReplicaSet
Mongo - ReplicaSet
Tutorial Mongo with ReplicaSet SSL
Root CA File Configuration
Mongo Cluster with SSL and TLS
Configure MongoDB with TLS and SSL
Self-Signed Request With CA

Top comments (0)