MongoDB SSL/TLS with X509 Authentication


sourced from - https://www.bustedware.com/blog/mongodb-ssl-tls-x509-authentication#signing-certificate-signing-requests

For this tutorial I am setting up a 3 node mongodb replica set with TLS and enabling X509 client authentication.

I created 3 ec2 instances running a custom AMI I built for this demo. Heres a blog post that goes into more detail how that AMI was built. TLDR; its a fresh installation of CentOS-7-x86_64-Minimal-1908.iso with a preloaded ssh public key in roots authorized keys


In this blog 


Create ec2 instances

See here for a complete specification of the ec2 run-instances command 


#!/bin/bash

aws ec2 run-instances --image-id ami-00446be8a2dcad866 --count 1 --instance-type t2.micro --key-name id_rsa_vlog --security-group-ids sg-2f055c55 --subnet-id subnet-1ba30952 --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=mongo-rs0-00}]'

aws ec2 run-instances --image-id ami-00446be8a2dcad866 --count 1 --instance-type t2.micro --key-name id_rsa_vlog --security-group-ids sg-2f055c55 --subnet-id subnet-1ba30952 --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=mongo-rs0-01}]'

aws ec2 run-instances --image-id ami-00446be8a2dcad866 --count 1 --instance-type t2.micro --key-name id_rsa_vlog --security-group-ids sg-2f055c55 --subnet-id subnet-1ba30952 --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=mongo-rs0-02}]'


Provision server for MongoDB

#!/bin/bash

yum install wget libcurl openssl telnet -y

firewall-cmd --permanent --add-port=27017/tcp

firewall-cmd --reload

wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.2.3.tgz

tar -xvf mongodb-linux-x86_64-rhel70-4.2.3.tgz

mv mongodb-linux-x86_64-rhel70-4.2.3 /opt

ln -sf /etc/alternatives/mongo /bin/mongo

ln -sf /etc/alternatives/mongod /bin/mongod

ln -sf /opt/mongodb-linux-x86_64-rhel70-4.2.3/bin/mongo /etc/alternatives/mongo

ln -sf /opt/mongodb-linux-x86_64-rhel70-4.2.3/bin/mongod /etc/alternatives/mongod

mkdir /data


MongoDB certificate subject requirements


Create certificate authority (CA)

#!/bin/bash

openssl req -passout pass:password -new -x509 -days 3650 -extensions v3_ca -keyout ca_private.pem -out ca.pem -subj "/CN=CA/OU=MONGO/O=BUSTEDWARE/L=PAINESVILLE/ST=OH/C=US"

Create key and certificate signing requests (CSR)

Note that the OU is different between the client and members of the replica set


Clients of replica set

#!/bin/bash

openssl req -newkey rsa:4096 -nodes -out client.csr -keyout client.key -subj '/CN=JacobTBills/OU=MONGO_CLIENTS/O=BUSTEDWARE/L=PAINESVILLE/ST=OH/C=US'

Members of replica set

#!/bin/bash

openssl req -newkey rsa:4096 -nodes -out node1.csr -keyout node1.key -subj '/CN=ip-172-31-7-201.ec2.internal/OU=MONGO/O=BUSTEDWARE/L=PAINESVILLE/ST=OH/C=US'

openssl req -newkey rsa:4096 -nodes -out node2.csr -keyout node2.key -subj '/CN=ip-172-31-5-30.ec2.internal/OU=MONGO/O=BUSTEDWARE/L=PAINESVILLE/ST=OH/C=US'

openssl req -newkey rsa:4096 -nodes -out node3.csr -keyout node3.key -subj '/CN=ip-172-31-4-24.ec2.internal/OU=MONGO/O=BUSTEDWARE/L=PAINESVILLE/ST=OH/C=US'


Sign the certificate signing requests with CA

Client

You cannot use a single client certificate to authenticate more than one MongoDB user. 

#!/bin/bash

openssl x509 -passin pass:password -sha256 -req -days 365 -in client.csr -CA ca.pem -CAkey ca_private.pem -CAcreateserial -out client-signed.crt

Server

For the mongod server instances I will specify the subject alternative name to be included in the signed certificate extensions. 

#!/bin/bash

# sign node 1 csr

openssl x509 -passin pass:password -sha256 -req -days 365 -in node1.csr -CA ca.pem -CAkey ca_private.pem -CAcreateserial -out node1-signed.crt -extensions v3_req -extfile <(

cat << EOF

[ v3_req ]

subjectAltName = @alt_names


[ alt_names ]

DNS.1 = 127.0.0.1

DNS.2 = localhost

DNS.3 = ip-172-31-7-201.ec2.internal

EOF

)


# sign node 2 csr

openssl x509 -passin pass:password -sha256 -req -days 365 -in node2.csr -CA ca.pem -CAkey ca_private.pem -CAcreateserial -out node2-signed.crt -extensions v3_req -extfile <(

cat << EOF

[ v3_req ]

subjectAltName = @alt_names


[ alt_names ]

DNS.1 = 127.0.0.1

DNS.2 = localhost

DNS.3 = ip-172-31-5-30.ec2.internal

EOF

)


# sign node 3 csr

openssl x509 -passin pass:password -sha256 -req -days 365 -in node3.csr -CA ca.pem -CAkey ca_private.pem -CAcreateserial -out node3-signed.crt -extensions v3_req -extfile <(

cat << EOF

[ v3_req ]

subjectAltName = @alt_names


[ alt_names ]

DNS.1 = 127.0.0.1

DNS.2 = localhost

DNS.3 = ip-172-31-4-24.ec2.internal

EOF

)


Create the privacy enhanced mail (PEM) file for mongod

The PEM file is a combination of the signed certificate and private key. I create the PEM file for the client and each member of the replica set, then transfer each file to its respective server we have running in AWS for this demo 

#!/bin/bash

cat client-signed.crt client.key > client.pem

cat node1-signed.crt node1.key > node1.pem

cat node2-signed.crt node2.key > node2.pem

cat node3-signed.crt node3.key > node3.pem


scp node1.pem ca.pem client.pem root@X.X.X.X:/root

scp node2.pem ca.pem root@Y.Y.Y.Y:/root

scp node3.pem ca.pem root@Z.Z.Z.Z:/root

Note that I also copied the CA certificate (ca.pem) to each server. This is a requirement for hostname verification when starting the mongod instance. I also copied the client.pem file to node1 so that I can connect and run administrative commands after creating the user in the x509 authentication database.


Create the replica set

Make sure to bind to both local interface and IPv4 interfaces with mongod. You will need to use the localhost exception to create the first user. After creating the first user you may restart mongod and only specify the interfaces you need


Node1, 2, 3

#!/bin/bash

mongod --bind_ip 127.0.0.1,172.31.7.201 --replSet rs0 --dbpath /data --logpath /data/mongod.log --port 27017 --fork --wiredTigerCacheSizeGB 1 --sslMode requireSSL --sslPEMKeyFile /root/node1.pem --sslCAFile /root/ca.pem --clusterAuthMode x509

#!/bin/bash

mongod --bind_ip 127.0.0.1,172.31.5.30 --replSet rs0 --dbpath /data --logpath /data/mongod.log --port 27017 --fork --wiredTigerCacheSizeGB 1 --sslMode requireSSL --sslPEMKeyFile /root/node2.pem --sslCAFile /root/ca.pem --clusterAuthMode x509

#!/bin/bash

mongod --bind_ip 127.0.0.1,172.31.4.24 --replSet rs0 --dbpath /data --logpath /data/mongod.log --port 27017 --fork --wiredTigerCacheSizeGB 1 --sslMode requireSSL --sslPEMKeyFile /root/node3.pem --sslCAFile /root/ca.pem --clusterAuthMode x509

Connect with the following command 

#!/bin/bash

mongo localhost --ssl --sslPEMKeyFile client.pem --sslCAFile ca.pem

And run initiate to create the replica set 

> rs.initiate(

   {

      _id: "rs0",

      version: 1,

      members: [

         { _id: 0, host : "ip-172-31-7-201.ec2.internal:27017" },

         { _id: 1, host : "ip-172-31-5-30.ec2.internal:27017" },

         { _id: 2, host : "ip-172-31-4-24.ec2.internal:27017" }

      ]

   }

)

Create user on x509 authentication database

> db.getSiblingDB("$external").runCommand({createUser:"C=US,ST=OH,L=PAINESVILLE,O=BUSTEDWARE,OU=MONGO_CLIENTS,CN=JacobTBills",roles:[{role:"root",db:"admin"}]})

Connect using x509 authentication 

#!/bin/bash

mongo ip-172-31-7-201.ec2.internal --ssl --sslPEMKeyFile client.pem --sslCAFile ca.pem --username "C=US,ST=OH,L=PAINESVILLE,O=BUSTEDWARE,OU=MONGO_CLIENTS,CN=JacobTBills" --authenticationMechanism "MONGODB-X509" --authenticationDatabase '$external'