TJ Tang

1 result

Secure MongoDB with X.509 Authentication

Disclaimer - the following article is intended for a test environment. Overview In this tutorial, I will be describing the detailed process of setting up X.509 based authentication, both for cluster inter-member authentication as well as for client authentication, using a local CA (Certificate Authority). X.509 is one of the multiple authentication mechanisms supported by MongoDB. An X.509 certificate is a digital certificate that uses the widely accepted international X.509 public key infrastructure (PKI) standard to verify that a public key, presented by a client or another member of the cluster, belongs to that said client or member. One of the main benefits compared to conventional password based authentication is it’s more secure in a sense that each machine would need a dedicated key to join the cluster. So stealing an existing key from another machine isn't going to be very helpful for those with an evil agenda. In MongoDB, we need to understand the distinction between member authentication and client authentication. MongoDB is a distributed database and deployments almost always consist of multiple mongod or mongos processes running on multiple machines. Member authentication refers to the fact that these machines need to verify each other to ensure a node attempting to replicate data is indeed part of the current cluster. On the other hand, client authentication refers to those MongoDB clients, including mongo shell, export/import tools and MongoDB drivers. Below is a deployment of standard 3 node replica set and a client. To enable X.509 certificate based authentication, essentially you will need the following set of certificates: 3x server certificates, one for each MongoDB member 1x client certificate for one client 1x certificate for Root CA and 1x certificate for Signing CA All the server certificates and client certificates must be signed by same CA. The bulk part of the work for setting up X.509 authentication would be to create these certificates. The creation of Root CA and Signing CA and the relevant parts of the signing process is a one time effort, while the creation of the server/client certificates are as needed. For simplicity, I am going to use one machine and I will run multiple mongods on different ports for this exercise. I also put the steps into an executable script if you want to jump to the end. You can download and run the script, and you will have a test setup just like the diagram above in no time. This way you can quickly get things working, boost your confidence, and then come back to study the details. Preparation One linux box, I tested on RedHat 7 and Ubuntu 14.04 but it should work with other flavors as well. Download MongoDB Enterprise 3.2 Install MongoDB following instructions documented here Download the demo shell script and save to a clean directory Running the script The script will setup a test replica set on your machine and configure X.509 authentication mechanism for that replica set. Before you run the script, double check: OpenSSL is installed (typically comes with most Linux distributions) Make sure mongod/mongo are in the executable path Make sure no mongod is already running on 27017 port, or change the port numbers in the shell script To run the script, go to the directory that contains the script and execute the following: # chmod +x # ./ If everything goes smoothly, you should have a 3 nodes replica set running with X.509 as member auth. Then in the current directory, you may connect to the primary node with a newly generated client certificate client1.pem: mongo --ssl --sslPEMKeyFile client1.pem --sslCAFile root-ca.pem --sslAllowInvalidHostnames Note above step just allows you to connect to the MongoDB shell. You will not have any permission at this point. To do anything meaningful, you need to authenticate yourself using following command: $mongo ... > db.getSiblingDB("$external").auth( { mechanism: "MONGODB-X509", user: "CN=client1,OU=MyClients,O=MongoDB China,L=Shenzhen,ST=GD,C=CN" } ); > db.test.find() If you are able to execute the last find statement, congratulations, the X.509 authentication is working! Now it's time to perform an anatomy of the script to understand what are the key steps involved in setting up the X.509 authentication mechanism. Main Parts of the Script Initialization Create local CA and signing keys Generate and sign server certificates for member authentication Generate and sign client certificates for client authentication Start MongoDB cluster in non-auth mode Setup replica set and initial users Restart MongoDB replica set in X.509 mode using server certificates 0. Variable initialization First initialize some variables. Feel free to modify the values as appropriate. dn_prefix="/C=CN/ST=GD/L=Shenzhen/O=MongoDB China" ou_member="MyServers" ou_client="MyClients" mongodb_server_hosts=( "server1" "server2" "server3" ) mongodb_client_hosts=( "client1" "client2" ) mongodb_port=27017 Here dn_prefix will be used to construct the full DN name for each of the certificate. ou_member is used to have a different OU than the client certificates. Client certificates uses ou_client in its OU name. mongodb_server_hosts should list the hostname (FQDN) for all the MongoDB servers while mongodb_client_hosts should list the hostnames for all of the client machines. For a clean start, let’s kill the running mongods and clean up the working directory (note: don’t use -9 to kill mongod per the manual ): kill $(ps -ef | grep mongod | grep set509 | awk '{print $2}') mkdir -p db 1. Create local root CA A root CA (Certificate Authority) is at the top of the certificate chain. This is the ultimate source of the trust. Ideally a third party CA should be used. However in the case of an isolated network (very typical in large enterprise environment), or for testing purpose, we need to use local CA to test the functionality. echo "##### STEP 1: Generate root CA " openssl genrsa -out root-ca.key 2048 # !!! In production you will want to password protect the keys # openssl genrsa -aes256 -out root-ca.key 2048 openssl req -new -x509 -days 3650 -key root-ca.key -out root-ca.crt -subj "$dn_prefix/CN=ROOTCA" mkdir -p RootCA/ca.db.certs echo "01" >> RootCA/ca.db.serial touch RootCA/ca.db.index echo $RANDOM >> RootCA/ca.db.rand mv root-ca* RootCA/ Above we first created a key pair root-ca.key with AES256 encryption and 2048 bits strength. Then using openssl req command to generate a self-signed certificate with a validity of 3650 days. One thing to call out here is the argument -x509 which tells openssl to self sign the certificate instead of generating a signing request (as what we will do below). The output is a crt file, a certificate file that contains the public key of the root CA. 2. Create CA config A CA config file is used to provide some default settings during the certificate signing process, such as the directories to store the certificates etc. You may change the defaults in root-ca.cfg file after it is generated or simply change them within the script. echo "##### STEP 2: Create CA config" cat >> root-ca.cfg ${host}.pem done This script is in a for loop to generate multiple certificates. 3 key steps are involved with each certificate: Use openssl genrsa command to create a new key pair Use openssl req command to generate a signing request for the key Use openssl ca command to sign the key and output a certificate, using the Signing CA we created earlier as the signer Notice the variable $ou_member . This signifies the difference between server certificates and client certificates. Server and client certificates must differ in the organization part of the Distinguished Names, or in another word and must differ at least in one of the O, OU, or DC values. 5. Generate and Sign client certificates These certificates are used by clients, such as mongo shell, mongodump, and Java/python/C# drivers to connect to a MongoDB cluster. This step is essentially the same as step 4 except for the use of $ou_client . This will make the combination of the DC/OU/O for these certificates will be different from the server certs above. echo "##### STEP 5: Create client certificates" # Pay attention to the OU part of the subject in "openssl req" command for host in "${mongodb_client_hosts[@]}"; do echo "Generating key for $host" openssl genrsa -out ${host}.key 2048 openssl req -new -days 365 -key ${host}.key -out ${host}.csr -subj "$dn_prefix/OU=$ou_client/CN=${host}" openssl ca -batch -name SigningCA -config root-ca.cfg -out ${host}.crt -infiles ${host}.csr cat ${host}.crt ${host}.key > ${host}.pem done 6. Bring up replicaset in non-auth mode MongoDB does not create a default root/admin user when enabling authentication, and there is no exception with X.509 mode. Instead, the best practice is to create an initial admin user first, then to enable authentication after the admin user has been created. Here we're starting a replica set in non-auth mode. echo "##### STEP 6: Start up replicaset in non-auth mode" mport=$mongodb_port for host in "${mongodb_server_hosts[@]}"; do echo "Starting server $host in non-auth mode" mkdir -p ./db/${host} mongod --replSet set509 --port $mport --dbpath ./db/$host \ --fork --logpath ./db/${host}.log let "mport++" done sleep 3 Now our replica set is up, we need to initialize the replica set and add a user. 7. Initialize replicaset and add initial user When using X.509 client authentication, each client must have a user created in MongoDB and the user must be granted the necessary permissions. The username must be same as the client's DN (Distinguished Name), which can be obtained by running an openssl command: # obtain the subject from the client key: client_subject=`openssl x509 -in client1.pem -inform PEM -subject -nameopt RFC2253 | grep subject | awk '{sub("subject= ",""); print}'` This would return something like: CN=client1,OU=MyClients,O=MongoDB China,L=Shenzhen,ST=GD,C=CN Obviously it's not mandatory to use the openssl command. It's fairly straightforward to deduce the subject string by concatenating the relevant parts of the DN as shown above. Once we have the DN name, let's initialize the replica set and add a user: myhostname=`hostname` cat > setup_auth.js

March 29, 2016