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
Provision server for MongoDB
MongoDB certificate subject requirements
Create certificate authority (CA)
Create key and certificate signing requests (CSR)
Sign the certificate signing requests with CA
Create the privacy enhanced mail (PEM) file for mongod
Create the replica set
Create user on x509 authentication database
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
download mongodb 4.2.3 community edition archive
install libcurl and openssl which are dependencies for installing from mongodb community edition archive
unpack and link binaries to default path
create firewall rules for internode communication
#!/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
A single Certificate Authority (CA) must issue the certificates for both the client and the server.
The Organization (O), the Organizational Unit (OU), and the Domain Components (DC) must match those from the certificates for the other cluster members
Client certificate subjects must differ in at least Organization (O), Organizational Unit (OU), or Domain Component (DC) from the cluster members subjects
Either the Common Name (CN) or one of the Subject Alternative Name (SAN) entries must match the hostname of the server, used by the other members of the cluster. Starting in MongoDB 4.2, when performing comparison of SAN, MongoDB supports comparison of DNS names or IP addresses. In previous versions, MongoDB only supports comparisons of DNS names.
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
/CN=SomeUser/OU=MONGO_CLIENTS/O=BUSTEDWARE/L=PAINESVILLE/ST=OH/C=US
#!/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
/CN=SERVERFQDN/OU=MONGO/O=BUSTEDWARE/L=PAINESVILLE/ST=OH/C=US
#!/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'