After going through the adventure of deploying a high-availability MongoDB cluster on Docker and sharing it publicly, I decided to complement that tutorial with some security concerns and tips.
In this post, you'll learn a few details about MongoDB deployment vulnerabilities and security mechanisms. And more importantly, how to actually protect your data with these features.
Objectives
- understand database aspects of security.
- find ways to implement authentication, authorization, and accounting (AAA).
- learn how to enable MongoDB security features.
Pre-requisites
Any running MongoDB instance on which you have full access will do. Standalone or replica set, containerized or not. We will also mention some details on MongoDB Docker instances, but we’ll keep Docker-specific security tips for another post.
List of Quick Wins
Accessing data in a database has several stages. We will take a look at these stages and find ways to harden them, to get a cumulative security effect at the end. Each of these stages will, most of the time, have the ability to block the next one (e.g. you need to have network access to get to the authentication part).
1. Network access
MongoDB’s default port is 27017 (TCP). Choosing a different port to operate might confuse some hackers, but it is still a minor security action because of port scanning, so you won't get that much out of it.
Assuming we choose the default port for our service, we will open that port on the database server's firewall. We do not wish to expose the traffic from this port to the internet. There are two approaches to solve that and both can be used simultaneously. One is limiting your traffic to your trusted servers through firewall configuration.
There’s a MongoDB feature you can use for this: IP Binding. You pass the --bind_ip
argument on the MongoDB launch command to enable it. Let's say your app1
server needs to access the MongoDB server for data. To limit traffic for that specific server, you start your server as:
mongod --bind_ip localhost,app1
If you are using Docker, you can avoid this risk by using a Docker network between your database and your client application.
You can add another layer of network security by creating a dedicated network segment for databases, in which you apply an ACL (access list) in the router and/or switch configuration.
2. System access
The second A in AAA means authorization. We know privileged shell access is needed during database installation. When concluding the installation, locking system root user access is part of the drill.
Data analysts need to read database data and applications also need to read and (almost always) write data as well. As this can be addressed with database authentication (more on this on 4. Authorization), make sure to restrict root and other shell access to people who can't do their jobs without it. Only allow it for database and system administrators.
Furthermore, running MongoDB processes with a dedicated operating system user account is a good practice. Ensure that this account has permission to access data but no unnecessary permissions.
3. Authentication
Authentication is the first A in AAA. Authentication-wise, MongoDB supports 4 mechanisms:
- SCRAM (default)
- x.509 certificate authentication
- LDAP proxy authentication
- Kerberos authentication
If you are using MongoDB Enterprise Server, then you can benefit from LDAP and Kerberos support. Integrating your company identity and access management tool will make AAA 3rd A (Accounting) implementation easier, as every user will have a dedicated account associated with his records.
MongoDB has its own SCRAM implementations: SCRAM_SHA1 for versions below 4.0 and SCRAM_SHA256 for 4.0 and above. You can think of SHA-256 as the successor of SHA-1, so pick the latter if available on your database version.
Replica sets keyfiles also use the SCRAM authentication mechanism where these keyfiles contain the shared password between the replica set members. Another internal authentication mechanism supported in replica sets is x.509. You can read more on replica sets and how to generate keyfiles in our previous blog post.
To be able to use the x.509 certificates authentication mechanism, there are some requirements regarding certificate attributes. To enable x.509 authentication, add --tlsMode
, --tlsCertificateKeyFile
and --tlsCAFile
(in case the certificate has a certificate authority). To perform remote connections to the database, specify the --bind_ip
.
mongod --tlsMode requireTLS --tlsCertificateKeyFile <path to TLS/SSL certificate and key PEM file> --tlsCAFile <path to root CA PEM file> --bind_ip <hostnames>
To generate these certificates, you can use the openssl
library on Linux or the equivalent on other operating systems.
openssl x509 -in <pathToClientPEM> -inform PEM -subject -nameopt RFC2253
The command returns the subject string as well as the certificate:
subject= CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry
-----BEGIN CERTIFICATE-----
# ...
-----END CERTIFICATE-----
Next, add a user on the $external database using the obtained subject string like in the example below:
db.getSiblingDB("$external").runCommand(
{
createUser: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry",
roles: [
{ role: "readWrite", db: "test" },
{ role: "userAdminAnyDatabase", db: "admin" }
],
writeConcern: { w: "majority" , wtimeout: 5000 }
}
)
Finally, connect to the database with the arguments for TLS, certificates location, CA file location, authentication database, and the authentication mechanism.
mongo --tls --tlsCertificateKeyFile <path to client PEM file> --tlsCAFile <path to root CA PEM file> --authenticationDatabase '$external' --authenticationMechanism MONGODB-X509
You have now successfully connected to your database using the x.509 authentication mechanism.
4. Authorization
For non-testing environments (like production) it is clearly not recommended to have Access Control disabled, as this grants all privileges to any successful access to the database. To enable authentication, follow the procedure below.
# start MongoDB without access control
mongod
# connect to the instance
mongo
// create the user administrator
use admin
db.createUser(
{
user: "myUserAdmin",
pwd: passwordPrompt(), // or cleartext password
roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
}
)
// shutdown mongod instance
db.adminCommand( { shutdown: 1 } )
# start MongoDB with access control
mongo --auth
If you're using MongoDB on Docker, you can create an administrator through MONGO_INITDB_ROOT_USERNAME
and MONGO_INITDB_ROOT_PASSWORD
environment variables (-e
argument). Like so:
docker run -d -e MONGO_INITDB_ROOT_USERNAME=<username> -e MONGO_INITDB_ROOT_PASSWORD=<password> mongo:4.4
Do not neglect human usability convenience. Make sure all passwords are strong, fit your company's password policy, and are stored securely.
MongoDB has a set of built-in roles and allows us to create new ones. Use roles to help when giving privileges while applying the principle of least privilege on user accounts and avoid user account abuse.
5. Encrypted connections
Let's now see how to configure encrypted connections to protect you from sniffing attacks.
If you think about internet browsers, you notice how they keep pressing for users to navigate on sites that support HTTP over TLS, also known as HTTPS. That enforcement exists for a reason: sensitive data protection, both for the client and the server. TLS is therefore protecting this sensitive data during the client-server communication, bidirectionally.
We have explained how to use TLS certificates on 4. Authentication and now we will see how to encrypt our communications between the database server and a client app through TLS configuration on the application’s MongoDB driver.
First, to configure the MongoDB server to require our TLS certificate, add the --tlsMode
and --tlsCertificateKeyFile
arguments:
mongod --tlsMode requireTLS --tlsCertificateKeyFile <pem>
To test the connection to mongo shell, type in:
mongo --tls --host <hostname.example.com> --tlsCertificateKeyFile <certificate_key_location>
Then, add TLS options to the database connection on your application code. Here is a snippet of a NodeJS application using MongoDB’s official driver package. You can find more of these encryption options on the driver documentation.
const MongoClient = require('mongodb').MongoClient;
const fs = require('fs');
// Read the certificate authority
const ca = [fs.readFileSync(__dirname + "/ssl/ca.pem")];
const client = new MongoClient('mongodb://localhost:27017?ssl=true', {
sslValidate:true,
sslCA:ca
});
// Connect validating the returned certificates from the server
client.connect(function(err) {
client.close();
});
6. Encryption at rest
MongoDB Enterprise Server comes with an Encryption at Rest feature. Through a master and database keys system, this allows us to store our data in an encrypted state by configuring the field as encrypted on rest. You can learn more about the supported standards and enciphering/deciphering keys on the MongoDB documentation.
On the other side, if you will stick with the MongoDB Community, on v4.2 MongoDB started supporting Client-Side Field Level Encryption. Here’s how it works: you generate the necessary keys and load them in your database driver (e.g. NodeJS MongoDB driver). Then, you will be able to encrypt your data before storing it in the database and decrypt it for your application to read it.
Below, you can find a JavaScript code snippet showing data encryption and decryption happening on MongoDB’s NodeJS driver with the help of the npm package mongodb-client-encryption.
const unencryptedClient = new MongoClient(URL, { useUnifiedTopology: true });
try {
await unencryptedClient.connect();
const clientEncryption = new ClientEncryption(unencryptedClient, { kmsProviders, keyVaultNamespace });
async function encryptMyData(value) {
const keyId = await clientEncryption.createDataKey('local');
console.log("keyId", keyId);
return clientEncryption.encrypt(value, { keyId, algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic' });
}
async function decryptMyValue(value) {
return clientEncryption.decrypt(value);
}
const data2 = await encryptMyData("sensitive_data");
const mKey = key + 1;
const collection = unencryptedClient.db("test").collection('coll');
await collection.insertOne({ name: data2, key: mKey });
const a = await collection.findOne({ key: mKey });
console.log("encrypted:", a.name);
const decrypteddata = await decryptMyValue(a.name);
console.log("decrypted:", decrypteddata);
} finally {
await unencryptedClient.close();
}
Conclusion
While this post attempts to cover some of the most important quick wins you can achieve to secure your MongoDB instances, there is much more to MongoDB security.
Upgrading database and driver versions frequently, connecting a monitoring tool, and keeping track of database access and configuration are also good ideas to increase security.
Nevertheless, even if the system was theoretically entirely secured, it is always prone to human mistakes. Make sure the people working with you are conscious of the importance of keeping data secured - properly securing a system is always contingent on all users taking security seriously.
Security is everyone's job. Like in tandem kayaks, it only makes sense if everyone is paddling together in the same direction, with all efforts contributing to the same purpose.
Lastly, although this post has focused on database security, it’s also advisable that you protect the JavaScript source code of your web and mobile apps. See our tutorials on protecting React, Angular, Vue, React Native, Ionic, and NativeScript.