How To Secure Your MongoDB Database Server on Ubuntu 14.04

The MongoDB database server is well-known for its unmatched capabilities for processing large NoSQL data. Its latest performance improvements are always under the spotlights and probably not a single MongoDB user has missed the recent news about the change of its default storage engine to the more powerful WiredTiger. At the same time, its security features are less known and often neglected. That's why the purpose of this article is MongoDB security.

Prerequisites

Before following this tutorial, please make sure you complete the following prerequisites:

  • A Ubuntu 14.04 server.
  • Preferably a non-root sudo user.
  • MongoDB installed and configured.

Except otherwise noted, all of the commands that require root privileges in this tutorial should be run as a non-root user with sudo privileges.

Step 1 - Requiring authentication

By default, in MongoDB you can manage any database without authentication. Obviously, this is quite insecure even though the MongoDB service doesn't listen on external interfaces and an attacker will need local access to the server first.

Let's start securing MongoDB by creating an admin user who can manage all the databases. This user will have the role userAdminAnyDatabase.

To create this super admin user, log in MongoDB and connect to the admin database with the command:

mongo admin

As you can see, you don't need a password for operating MongoDB at this point. You don't even need Linux sudo rights and you can run the command as a normal user.

Now let's create the user mongoadmin with password S3cr3t11.@ with the query:

> db.createUser(
  {
    user: "mongoadmin",
    pwd: "S3cr3t11.@",
    roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
  }
)

The name mongoadmin is arbitrary and you can use any other to your liking. A good security practice is to pick up a non-standard name, different from the standard admin. Thus, in the case of a brute-force attack, the attacker will have to guess not only your password but also username.

The output of the above command should confirm the successful creation of the user like this:

Successfully added user: {
        "user" : "mongoadmin",
        "roles" : [
                {
                        "role" : "userAdminAnyDatabase",
                        "db" : "admin"
                }
        ]
}

We'll continue working with the MongoDB shell and exit it later.

So far we have a super admin user who should be used only for administration, i.e. tasks like creating other users and granting them privileges. By default, you cannot and should not use this user to connect to other databases. This might be something new for people acquainted with other database systems where the super admin (root) has limitless privileges.

In addition to that user, let's create another one for connecting to a database. For the example we'll use a database called newdb and add to it an user with read/write permissions called newdbUser.

In the MongoDB shell switch to the newdb with the command:

> use newdb

If you don't have already such a database, it will be automatically created for you. Then add its user with the command:

> db.createUser(
    {
      user: "newdbUser",
      pwd: "S0m3Pass",
      roles: [
         { role: "readWrite", db: "newdb" },
      ]
    }
)

You should see again a confirmation like this:

Successfully added user: {
        "user" : "newdbUser",
        "roles" : [
                {
                        "role" : "readWrite",
                        "db" : "newdb"
                }
        ]
}

After we have these two users created, we can enable authentication in MongoDB. For this purpose exit the MongoDB shell and start editing the main MongoDB configuration file /etc/mongod.conf such as with vim or nano. Add the following two lines there:

security:
   authorization: enabled

After that restart the MongoDB service with the command sudo service mongod restart.

To verify authentication is enabled and required try again to connect to the MongoDB shell like before to an arbitrary database, e.g. newdb: mongo newdb.

Then try to list the collections inside the database with the command show collections.

You should see a not authorized error like this:

2016-03-24T14:13:36.662-0500 E QUERY    Error: listCollections failed: {
        "ok" : 0,
        "errmsg" : "not authorized on admin to execute command { listCollections: 1.0 }",
        "code" : 13
}
    at Error ()
...

When you connect to the MongoDB shell without an user/password, you are connected as anonymous. This way you don't have any privileges and you cannot even list the collections, as the above error shows. However, you may wonder whether you can prevent this anonymous user from even connecting to the service. Unfortunately, there is no such feature but we'll discuss later how you can limit the access to the service on network level.

Exit the MongoDB shell and try to connect again with the newly created user and its password:

mongo newdb -u "newdbUser" -p "S0m3Pass"

Please note that specifying the password on the command line like above is not a good idea, though, and we are using it only so that you can follow more easily the article. If you specify just the -p argument, then you will be prompted automatically for the password and you do not have to specify it on the command line.

Once authenticated, please try again to perform any action on the database, such as listing the collections, and you will not see an error.

So far you have enabled user authentication and even if someone gains access to your server, he or she will not be able to access so easily your MongoDB information without valid authentication. This is a serious improvement in contrast to the default setup.

Step 2 - Securing remote access with iptables

By default, the MongoDB server listens only on the local interface and there is no real need to secure further its communications. Thus, if you intend to connect to MongoDB only locally, you can skip step 2 and step 3.

In some cases, though, you might want to allow remote MongoDB access such as for remote administration or for replication. To do this open again the configuration file /etc/mongod.conf for editing like this. In this file find the network interfaces configuration. We'll change the bindIP parameter from the default 127.0.0.1 to [127.0.0.1,0.0.0.0] like this:

# network interfaces
net:
  port: 27017
  bindIp: [127.0.0.1,0.0.0.0]

Adding 0.0.0.0 to the array of listeners means that MongoDB will listen on all network interfaces. If you want to configure only a specific interface you should specify it in place of 0.0.0.0. You might want also to change the port to a different one than the default 27017. In the latter case, make sure to take this into consideration when configuring your firewall later.

Once you are done with the editing of the file /etc/mongod.conf, close it and restart MongoDB with the command sudo service mongod restart.

Then use netstat to verify MongoDB listens to all interfaces (0.0.0.0) like this: sudo netstat -ntlpa |grep mongod.

If everything is fine, the output should look like this:

tcp  0   0 0.0.0.0:27017   0.0.0.0:*    LISTEN   12288/mongod

Once you have configure MongoDB to listen on an external interface you should limit its network exposure. The best way to accomplish this is by using a firewall such as iptables. You will need a rule for MongoDB similar to this one:

sudo iptables -A INPUT -s YOUR_IP -p tcp --dport 27017 -j ACCEPT

In the above command replace YOUR_IP with the IP from which you will be connecting. You can execute the above command for more than one IP if needed.

However, limiting network exposure with iptables is possible only when you are connecting to MongoDB from the known IP(s) which doesn't change. If you or other legitimate users of MongoDB are connecting from various, dynamically changing IPs you will not be able to limit the connectivity with iptables but instead you should use a vpn. OpenVPN is an excellent VPN solution for this purpose.

Step 3 - Enabling SSL

Besides limiting the network exposure of MongoDB you should also make sure that its communication channel is encrypted. For this purpose MongoDB supports the popular Secure Socket Layer (SSL) encryption. For an SSL certificate we'll use a self-signed certificate because it's just as secure as a commercial one for our very purpose. The drawback of a self-signed certificate is that you will have to disregard warnings about being untrusted and/or invalid.

To create your own self-signed certificate valid for 2 years (730 days) with 2048 bit rsa encryption run the following openssl command:

sudo openssl req -newkey rsa:2048 -new -x509 -days 730 -nodes -out \
 /etc/ssl/mongodb-cert.crt -keyout /etc/ssl/mongodb-cert.key

You will be asked for a few simple questions such as your country, state, etc. Make sure to correctly specify the common name which should be the same as the one which you will use for connecting to the server.

Once the certificate and private key pair is generated you will have the two files /etc/ssl/mongodb-cert.crt and /etc/ssl/mongodb-cert.key. Next you will have to combine them into one pem (container) file with the command:

sudo bash -c "cat /etc/ssl/mongodb-cert.key \
 /etc/ssl/mongodb-cert.crt > /etc/ssl/mongodb.pem"

After that open again MongoDB's configuration file /etc/mongod.conf for editing with vim or nano. Find again the net configuration part and add these two lines:

net:
   ssl:
...
      mode: requireSSL
      PEMKeyFile: /etc/ssl/mongodb.pem

The above lines enforce SSL communication with the key/certificate pair from the file /etc/ssl/mongodb.pem. For this setting to take effect, restart again the MongoDB server with the command: sudo service mongod restart.

Now you can test your new secure connection to MongoDB. When you are connecting with the mongo client you will have to add the extra argument --ssl and also in the case of a self-signed certificate -sslAllowInvalidCertificates. As an example, let's try to connect to the newdb database like before but with the new arguments. Your command should look like this:

mongo newdb -u "newdbUser" -p "S0m3Pass" --ssl \
 -sslAllowInvalidCertificates --host localhost

Once you start working remotely with MongoDB over SSL like this, you can have peace of mind that your communication channel is secured and no eavesdropping can harm you.

Step 4 - Logging

Logging is essential to security. By monitoring who connects to a database you can detect possible security breaches. Furthermore, a failed login may indicate a brute-force attack, which should be stopped as early as possible. Inspecting the logs for such events is especially important when we have already secured the network connectivity to MongoDB and attempts for unauthorized access should be impossible and reveal a serious security hole.

By default, MongoDB's logging is set to Informational level which logs everything except debugging information. The default logging setting also includes the component ACCESS which monitors user authentication. Thus you can find detailed information about users' logins, including the timestamp and the database used. Let's take a look at MongoDB's default log file (/var/log/mongodb/mongod.log) by grepping for the keyword ACCESS in it:

grep ACCESS /var/log/mongodb/mongod.log

On one hand, successful logins should look like this:

2016-03-31T13:58:27.483-0500 I ACCESS [conn2] 
Successfully authenticated as principal newdbUser on newdb

On the other hand, unsuccessful ones will also contain the IP of the client, besides the database, like this:

2016-03-31T15:07:04.807-0500 I ACCESS [conn3] SCRAM-SHA-1
 authentication failed for newdbUser on newdb from client
 127.0.0.1 ; AuthenticationFailed SCRAM-SHA-1 
 authentication failed, storedKey mismatch

The above failed login shows that the user newdbUser tries to log in the database newdb with a wrong password - authentication failed, storedKey mismatch. If there is no such user at all you will see an error UserNotFound like this:

2016-03-31T15:08:19.100-0500 I ACCESS   [conn4] SCRAM-SHA-1
authentication failed for newdbUser on admin from client 
127.0.0.1 ; UserNotFound Could not find user [email protected]

Last but not least, you can also see when users try to execute commands without the necessary privileges. For example, if you try to create a new admin user without the necessary privileges. This will create an Unauthorized event in the log like this:

2016-03-31T15:18:14.340-0500 I ACCESS   [conn7] Unauthorized
 not authorized on admin to execute command { createUser:
 "mongoadmin2", pwd: "xxx", roles: [ { role: "userAdminAnyDatabase",
 db: "admin" } ], digestPassword: false, writeConcern: 
 { w: "majority", wtimeout: 30000.0 } }

These errors in the log are not only valuable to security but you could also troubleshoot connection problems.

Monitoring the logs can be used for some basic security auditing. If you are further interested in this topic, you should know that the enterprise edition of MongoDB has a feature called auditing. This enterprise feature gives powerful options for true auditing of users' interactions with the data. The best thing about it is that you can create even filters to monitor only the activity which interests you. Unfortunately, this enterprise auditing is not available in the free, community edition of MongoDB.

Conclusion

This article explained how you can harden the default MongoDB configuration in order to accomplish some basic security and to be able to connect securely from remote locations. Even though the community edition of MongoDB lacks some of the enterprise features (auditing, storage encryption), it still gives you enough options to accomplish most security hardening tasks.


blog comments powered by Disqus