Security is one of the cornerstones of any enterprise-level system. Not always will you find a system in a completely safe and secure environment to allow unauthenticated user access to it. Apart from test environments, almost every production environment requires proper access rights and perhaps, an audit of the system access too. Mongo security has multiple aspects:
In this recipe and the next one, we will be looking at how to address the first two points mentioned in the preceding bullet list. The last point, about encrypting the data being transmitted on the wire, is not supported by default by the community edition of Mongo, and it will need a rebuild of the Mongo database with the ssl
option enabled.
In this recipe, we will be setting up users for a standalone Mongo instance. We need to start a standalone server listening to any port for client connections; in this case, we will stick to the default 27017
. If you are not aware of how to start a standalone server, refer to the Single node installation of MongoDB recipe in Chapter 1, Installing and Starting the MongoDB Server. We also need to start a shell that will be used for this admin operation. For a replica set, we will only be connected to a primary instance and will perform these operations.
We will add an admin user, a read-only user for the test
database, and a read-write user for the test
database in this recipe.
The following are assumed at this point:
Let's get started:
addUser
. However, in Version 2.6 of MongoDB, the method is createUser
. We will look at both methods of creating the users. Execute steps 3 and 4 if you are working on a MongoDB server, Version 2.4 and steps 5 and 6 if you are working on a MongoDB server, Version 2.6.admin
database:> use admin
admin
database, we will add a user called admin
and the password as admin
:> db.addUser('admin', 'admin') { "user" : "admin", "readOnly" : false, "pwd" : "7c67ef13bbd4cae106d959320af3f704", "_id" : ObjectId("52ea98ef2d00f6e6fb1fcdba") }
test
database as follows:> use test
test
database, we will create two users, a read-only user called read_user
and a read/write user called write_user
. The password for both these users is the same as their usernames.> db.addUser({user:'read_user', pwd:'read_user', roles:['read']}) { "user" : "read_user", "pwd" : "60477dd7460977860674077dc0039102", "roles" : [ "read" ], "_id" : ObjectId("52ee29012d00f6e6fb1fcdbc") } > db.addUser({user:'write_user', pwd:'write_user', roles:['readWrite']}) { "user" : "write_user", "pwd" : "7944cf3480b0eabbf0cff4498ed9652b", "roles" : [ "readWrite" ], "_id" : ObjectId("52ee292c2d00f6e6fb1fcdbd") }
admin
and test
databases in Version 2.6 of MongoDB. These steps are identical to Version 2.4 of MongoDB, except for the name of the methods. There are some additional features for this method that we will see in detail in the next section. First, we start by creating the admin user in the admin
database, as follows:> use admin > db.createUser({ user:'admin', pwd:'admin', customData:{desc:'The admin user for admin db'}, roles:['readWrite', 'dbAdmin', clusterAdmin'] } )
read_user
and write_user
to the test
database. To add the users, execute the following commands from the Mongo shell:> use test > db.createUser({ user:'read_user', pwd:'read_user', customData:{desc:'The read only user for test database'}, roles:['read'] } ) > db.createUser({ user:'write_user', pwd:'write_user', customData:{desc:'The read/write user for test database'}, roles:['readWrite'] } )
--auth
option on the command line, as follows:$ mongod .. <other options as provided earlier> --auth
> db.testAuth.find()
The testAuth
collection need not exist, but you should see an error stating that we are not authorized to query the collection
read_user
as follows:> db.auth('read_user', 'read_user')
find
operation as follows (note that the find
operation should not give an error and may not return any results, depending on whether the collection exists or not):> db.testAuth.find()
> db.testAuth.insert({i:1})
auth
function, whereas, in the previous case, we passed two parameters for the username and password.> db.logout() > db.auth({user:'write_user', pwd:'write_user'})
insert
operation again as follows (this time, it should work):> db.testAuth.insert({i:1})
> db.serverStatus()
admin
database. We are currently connected to the server using write_user
, which has read/write permissions on the test
database. From the Mongo shell, try to execute the following commands:> use admin > show collections
admin
database:$ mongo -u admin -p admin admin
admin
database:> show collections
> db.serverStatus()
Execute this step if you are on Version 2.4 of MongoDB and create the admin user using the db.addUser('<user name>', '<password>')
.
test
database and execute the insert
and find
operations as follows:> use test > db.testAuth.insert({i:1}) > db.testAuth.find()
We executed a lot of steps and now we will take a closer look at them.
Initially, the server is started without the --auth
option; hence, no security is enforced by default.
Version 2.4 of MongoDB is where we create a user in the admin
database using the addUser(<userName>, <password>)
form of the method. This creates a user in the admin
database; this special user has read/write access to all the databases and can run admin commands, such as db.serverStatus()
, and other replication and sharding-related commands. All users created in databases other than admin, whether read or write, will only be able to access the collections in their respective databases.
In version 2.6, however, we create the admin user using the db.createUser
method. Let us take a closer look at this method first. The signature of the method to create the user is createUser(user, writeConcern)
. The first parameter is the user, which actually is a JSON document, and the second parameter is the write concern to use for user creation. The JSON document for the user has the following format:
{ 'user' : <user name>, 'pwd' : <password>, 'customData': {<JSON document providing any user specific data>} 'roles':[<roles of the user>] }
The roles provided here can be provided as follows, assuming that the current database when the user is created is test
on the shell:
[{'role' : 'read', 'db':'reports'}, 'readWrite']
This gives the user that is being created read access to the db
reports and readWrite
access to the test
database. Let us see the complete user creation call for the test user:
> use test > db.createUser({ user:'test', pwd:'test', customData:{desc:'read access on reports and readWrite access on test'}, roles:[ {role:'read', db : 'reports'}, 'readWrite' ] } )
The write concern, which is an optional parameter, can be provided as the JSON document. Some sample values are {w:1}
and {w:'majority'}
.
Coming back to the admin user creation, we created the user in step 4 using the createUser
method and gave three roles to this user in the admin
database.
In steps 4 and 6, we created the read and read/write users in the test
database using the addUser
method for version 2.4 and the createUser
method for version 2.6. The JSON document for the creation of a user in version 2.4 is identical to the user JSON document in version 2.6, except for a couple of differences. First, there is no customData
field and second, the roles
array contains string values only for the user roles.
The JSON document for the user in version 2.4 has the following format:
{ 'user' : <user name>, 'pwd' : <password>, 'roles':[<string values for roles of the user>] }
We shut down the MongoDB server after the admin read and read-write user creation, and restart it with the --auth
option.
On starting the server again, we connect to it from the shell, which is in step 9, but unauthenticated. Here, we try to execute a find
query on a collection in the test
database; this fails, as we are unauthenticated. This shows that the server now requires appropriate credentials to execute operations on it. In steps 10 to 11, we log in using read_user
and try to execute a find
operation first, which succeeds, and then an insert
operation, which doesn't, as the user has read privileges only. The way to authenticate a user is by invoking db.auth(<user name>, <password>)
from the shell, and db.logout()
will log out the current logged in user.
In steps 13 to 15, we demonstrate that we can perform insert
operations using write_user
, but admin operations such as db.serverStatus()
cannot be executed as these operations execute adminCommand
on the server. This means that a non admin user is not permitted to invoke these operations. Similarly, when we change the database to admin, the write_user
, which is from the test
database, is not permitted to perform any operations such as getting a list of collections or any operation to query a collection in the admin
database.
In steps 16 to 19, we log in to the shell using the admin user to the admin
database. Previously, we logged in to the database using the auth
method; in this case, we used the -u
and -p
options to provide the username and the password. We also provided the name of the database to connect to, which is admin
in this case. Here, we are able to view the collections on the admin
database and also execute admin operations such as getting the server status. In version 2.6, executing the db.serverStatus
call is possible, as the user is given the clusterAdmin
role.
In step 18, we are able to switch to any other database and execute read/write operations. This is a special privilege for the users of the admin
database, which no other user has. This is possible because we created the user in version 2.4 using the version 2.2 style of user creation, db.addUser(<username>, <password>)
. The admin user created in version 2.6 is not able to query the test
database as it would need appropriate read and read/write privileges on the respective databases to perform these operations.
One final thing to note; apart from writing to a collection, a user with write privileges can also create indexes on the collection in which he has write access.
In this recipe, we saw how we can create different users and what permissions they have, restricting some sets of operations. In the next recipe, we will see how we can have authentication done at the process level. That is, how one Mongo instance can authenticate itself for being added to a replica set.