Securing MongoDB With TLS (Part 1 of 3)
Carl Tashian
In the intro post on mongodb.com, we covered why mutual TLS is such a good fit for securing MongoDB. In this post, we're going deploy a Certificate Authority that can issue TLS certificates to MongoDB.
Step-by-step TLS Deployment of MongoDB
Here are the steps required to secure MongoDB with TLS:
- Set up a
step-ca
Certificate Authority server. - For Mongo server validation, issue a certificate and private key to the MongoDB server and configure server TLS.
- For Mongo client validation, issue certificates and private keys to clients and configure client-side TLS.
- For Mongo cluster member validation, issue cluster TLS certificates and private keys to MongoDB nodes.
- Note: It’s possible to stage the migration of an existing MongoDB database to TLS: You can make TLS connections to MongoDB optional, and only require client certificates once you’ve migrated all of your clients.
Part One: Setting up a Certificate Authority
Create a Certificate Authority
I've written a system init script that sets up a CA, configureing it to issue MongoDB client, server, and cluster TLS certificates. Change the configuration values at the top of this script, run it on an Ubuntu 20.04 (Focal) Linux machine, and boom! You now have an online Certificate Authority up and running.
I'm not going to walk through the entire CA script here. Instead, let's step through the parts of the script that configure the Certificate Authority to issue certificates for MongoDB.
The first section of the script installs the step-ca
server and the [step
CLI tool](https://github.com/smallstep/cli) that controls it. (If you want to do this part manually, you can follow our installation docs for step-ca
.)
The next section is where we initialize our Certificate Authority:
# Set up our basic CA configuration and generate root keys
step ca init --name="$CA_NAME"
--dns="$LOCAL_IP,$LOCAL_HOSTNAME,$PUBLIC_IP,$PUBLIC_HOSTNAME"
--address=":443" --provisioner="$CA_EMAIL"
--password-file="$STEPPATH/password.txt"
This step ca init
command creates a basic configuration and CA private keys and certificates so that our Certificate Authority can run. The CA server will run on 0.0.0.0:443
and its TLS certificate will have all of the values specified in --dns
listed as Subjects. Note that this script pulls the _IP
and _HOSTNAME
variables from the AWS metadata API. But if you're not using AWS, you can set these values manually.
The --password-file
points to a file containing the password that will be used both to encrypt the CA private keys, and to secure the CA's admin provisioner. Provisioners are the CA's methods for issuing certificates. The admin provisioner lets you issue any certificate (using the step ca certificate
subcommand)—so keep the password safe!
Before you continue, make a note of the hostname of your CA, and the CA fingerprint (a hex value). You'll need both of these to access the CA remotely. If you ran the script using EC2 User Data, SSH into the CA VM and run
step certificate fingerprint /etc/step-ca/certs/root_ca.crt
as root to get the fingerprint of your CA.
Add MongoDB certificate templates
The next step is to add two certificate templates to step-ca
for MongoDB client and server certificates, so that the certificate subject and key usages on our certificates will match MongoDB's stringent requirements:
mkdir -p /etc/step-ca/templates/x509
# Server cert template.
cat <<EOF > /etc/step-ca/templates/x509/server.tpl
{
"subject": {
"organization": {{ toJson .Organization }},
"commonName": {{ toJson .Subject.CommonName }},
{{- if .OrganizationalUnit }}
"organizationalUnit": {{ toJson .OrganizationalUnit }}
{{- end }}
},
"sans": {{ toJson .SANs }},
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["serverAuth", "clientAuth"]
}
EOF
## Client (and cluster) cert template
cat <<EOF > /etc/step-ca/templates/x509/client.tpl
{
"subject": {
"organization": {{ toJson .Organization }},
{{- if .OrganizationalUnit }}
"organizationalUnit": {{ toJson .OrganizationalUnit }},
{{- end }}
"commonName": {{ toJson .Subject.CommonName }}
},
"sans": {{ toJson .SANs }},
"keyUsage": ["digitalSignature"],
"extKeyUsage": ["clientAuth"]
}
EOF
In MongoDB, client certificates and cluster member certificates are similar enough that they can share a single template. The only difference is that the value of the subject Organizational Unit (OU=) must differ between client certificates and cluster certificates, and must be the same between server certificates and cluster member certificates.
Add MongoDB certificate provisioners
The next step in the script is to create certificate provisioners specific to MongoDB. The script creates three provisioners:
- For MongoDB server certificates, an ACME provisioner. The ACME protocol (RFC8555) is the protocol that Let’s Encrypt uses to automate certificate management for websites. We'll use this in part 2, when we set up a single-node MongoDB server.
- For MongoDB cluster member certificates, a second ACME provisioner. We'll use this in Part 3 when we secure a MongoDB replication cluster.
- For MongoDB client certificates, a JWK "Service User" provisioner. The JWK provisioner uses token authentication to issue client certificates to bots, service accounts, or humans using a signed one-time-use token that contains the certificate request.
MongoDB server certificate provisioner (ACME)
The script creates an ACME provisioner for MongoDB server certificates:
step ca provisioner add "MongoDB Server" --type=acme
Finally, the script will configure the provisioner to use the server.tpl
template, and to issue 90 day TLS certificates by default:
cat <<< $(jq '(.authority.provisioners[] | select(.name == "MongoDB Server")) += {
"claims": {
"maxTLSCertDuration": "2160h",
"defaultTLSCertDuration": "2160h"
},
"options": {
"x509": {
"templateFile": "templates/x509/server.tpl",
"templateData": {
"Organization": "'${DN_ORG_NAME}'",
"OrganizationalUnit": "'${SERVER_DN_ORG_UNIT}'"
}
}
}
}' /etc/step-ca/config/ca.json) > /etc/step-ca/config/ca.jso
MongoDB Cluster Certificate Provisioner (ACME)
The cluster certificate provisioner will also use ACME.
step ca provisioner add "MongoDB Cluster" --type=acme
It uses the client.tpl
template but is otherwise the same as the server provisioner above.
cat <<< $(jq '(.authority.provisioners[] | select(.name == "MongoDB Cluster")) += {
"claims": {
"maxTLSCertDuration": "2160h",
"defaultTLSCertDuration": "2160h"
},
"options": {
"x509": {
"templateFile": "templates/x509/client.tpl",
"templateData": {
"Organization": "'${DN_ORG_NAME}'",
"OrganizationalUnit": "'${SERVER_DN_ORG_UNIT}'"
}
}
}
}' /etc/step-ca/config/ca.json) > /etc/step-ca/config/ca.json
MongoDB Service User Certificate Provisioner
We also need to get certificates to MongoDB clients. The script adds a JWK provisioner for this purpose, and configures it to use the client CA template and a Subject Organizational Unit (OU=) value reserved for MongoDB clients:
echo "$MONGO_SERVICE_USER_CA_PASSWORD" > /etc/step-ca/client-password.txt
step ca provisioner add "MongoDB Service User" --create --password-file /etc/step-ca/client-password.txt
cat <<< $(jq '(.authority.provisioners[] | select(.name == "MongoDB Service User")) += {
"claims": {
"maxTLSCertDuration": "2160h",
"defaultTLSCertDuration": "2160h"
},
"options": {
"x509": {
"templateFile": "templates/x509/client.tpl",
"templateData": {
"Organization": "'${DN_ORG_NAME}'",
"OrganizationalUnit": "'${CLIENT_DN_ORG_UNIT}'"
}
}
}
}' /etc/step-ca/config/ca.json) > /etc/step-ca/config/ca.json
Start the Certificate Authority
Finally, the script sets up step-ca
to run as a daemon, and starts it running in the background. To do this part manually, follow Running step-ca
As A Daemon.
curl -sL https://raw.githubusercontent.com/smallstep/certificates/master/systemd/step-ca.service -o /etc/systemd/system/step-ca.service
systemctl daemon-reload
chown -R step:step $(step path)
systemctl enable --now step-ca
After running the script, you should see that step-ca
is running. You can run step ca health
to check it, or look at the logs (journalctl -fu step-ca
).
Now that you have a CA running, it's time to configure the MongoDB server to use TLS certificates.
In part 2, we'll create a simple single-node MongoDB server that requires mutual TLS, and we'll use Mutual TLS to connect to it with a client certificate.
Further reading
MongoDB: Client certificate requirements
MongoDB: Client authentication manual page
MongoDB: Use x.509 Certificate for Membership Authentication
Carl Tashian (Website, LinkedIn) is an engineer, writer, exec coach, and startup all-rounder. He's currently an Offroad Engineer at Smallstep. He co-founded and built the engineering team at Trove, and he wrote the code that opens your Zipcar. He lives in San Francisco with his wife Siobhan and he loves to play the modular synthesizer 🎛️🎚️