“A man stretching out his two hands with careless written on them in black and white” by Mitchel Lensink on Unsplash
Have you come across a situation where ‘someone accidentally dropped an entire collection in production?’ Unless you can restore the data from a backup, you are in a terrible situation.
I came across a few clients who faced this situation at one point in time. So, it is important that you take security measures to prevent such situation from happening in the first place. You can easily achieve it by making use of the user-defined roles.
This is one of the many articles in multi-part series, Mastering MongoDB - One tip a day, solely created for you to master MongoDB by learning ‘one tip a day’. In a few series of articles, I would like to give various tips to tighten the security on MongoDB. In this article, I would discuss how an user-defined role can help prevent someone accidentally dropping a collection.
MongoDB employs Role-Based Access Control (RBAC) to govern access to a MongoDB system. A role grant users access to MongoDB resources. Outside of role assignments, the user has no access to the system.
Here are a few concepts that I want you to be aware of
MongoDB provides a number of built-in roles that administrators can use to control access to a MongoDB system. Every database includes the following roles
Database User Roles
Database Administration Roles
However, if these roles cannot describe the desired set of privileges, you can create a new user-defined role by using the db.createRole() method. While creating a role, you can specify the set of privileges you want to grant access.
The database administrators typically make use of the built-in ‘read’ and ‘readWrite’ roles to restrict the access to data. The below ‘getRole’ command shows the various set of actions a user with ‘readWrite’ role can execute.
// command to get readWrite privileges
db.getRole( "readWrite", { showPrivileges: true } ).privileges[0].actions
// output of above command
/*
[
"changeStream",
"collStats",
"convertToCapped",
"createCollection",
"createIndex",
"dbHash",
"dbStats",
"dropCollection",
"dropIndex",
"emptycapped",
"find",
"insert",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead",
"remove",
"renameCollectionSameDB",
"update"
]
*/
The MongoDB getRole command to show all the granted actions for readWrite role
Based on the context of the current article, the riskiest action among them is the ‘dropCollection’ action. If there is truly a need for (a human) user to have a read & write permission, it is recommended to have a user-defined role with all the actions but the ‘dropCollection’ action from ‘readWrite’ role. By assigning this user-defined role to such users, the administrators prevent someone accidentally drop a collection.
This lab exercise helps you create a user-defined role and illustrate how readWriteMinusDropRole can prevent someone accidentally drop a collection when compared to a user with readWrite role.
First, you would need an environment to play around. I recommend using mlaunch, a utility tool from mtools, to setup a test environment on your local machine. If you already have an environment with authentication turned on, you may skip this step.
# Don't have mtools? The easiest way to install mtools is via pip
# pip install mtools
mlaunch init --auth --replicaset --port 28000
# output
# launching: "mongod" on port 28000
# launching: "mongod" on port 28001
# launching: "mongod" on port 28002
# replica set 'replset' initialized.
# waiting for primary to add a user.
# Username "user", password "password"
A bash script to setup a MongoDB environment with authentication on your local machine
Log in to the test environment using the above credentials and create app_user with readWrite role on the social database.
mongo admin --port 28000 -u user -p password --authenticationDatabase admin <<EOF
db.createUser({user: 'app_user', pwd: 'password', roles: [{role: 'readWrite', db: 'social'}]})
EOF
# The output of above command
# MongoDB shell version v3.6.2
# connecting to: mongodb://127.0.0.1:28000/
# MongoDB server version: 3.6.2
# Successfully added user: {
# "user" : "app_user",
# "roles" : [
# {
# "role" : "readWrite",
# "db" : "social"
# }
# ]
# }
# bye
A bash script to create a user with readWrite role on the social database
Log in to the test environment using the app_user credentials and create a sample person document on the social database. Since the app_user has readWrite role which grants access to dropCollection action, the command db.person.drop() execution succeeds and the collection is dropped.
mongo social --port 28000 -u app_user -p password --authenticationDatabase admin
# output of above command
# MongoDB shell version v3.6.2
# connecting to: mongodb://127.0.0.1:28000/social
# MongoDB server version: 3.6.2
# replset:PRIMARY>
# create a document in person collection
db.person.insert({"fname": "Shyam", "lname": "Arjarapu"})
# output of above command
# WriteResult({ "nInserted" : 1 })
db.getCollectionNames()
# The output of above command
# [ "person" ]
# drop the collection
db.person.drop()
# The output of above command
# true
db.getCollectionNames()
# The output of above command
# [ ]
Set of bash & JavaScript to show that user with readWrite role can drop a collection
Log in to the test environment using the user credentials and create the following on the social database.
Notice that there is no dropCollection action from the set of actions being granted to readWriteMinusDropRole.
mongo social --port 28000 -u user -p password --authenticationDatabase admin <<EOF
db.createRole({
role: "readWriteMinusDropRole",
privileges: [
{
resource: { db: "social", collection: ""},
actions: [ "collStats", "dbHash", "dbStats", "find", "killCursors", "listIndexes", "listCollections", "convertToCapped", "createCollection", "createIndex", "dropIndex", "insert", "remove", "renameCollectionSameDB", "update"]} ],
roles: []
}
);
use admin;
db.createUser({user: 'human_user', pwd: 'password', roles: [{role: 'readWriteMinusDropRole', db: 'social'}]})
EOF
# The output of above command
# MongoDB shell version v3.6.2
# connecting to: mongodb://127.0.0.1:28000/social
# MongoDB server version: 3.6.2
# {
# "role" : "readWriteMinusDropRole",
# "privileges" : [
# {
# "resource" : {
# "db" : "social",
# "collection" : ""
# },
# "actions" : [
# "collStats",
# "dbHash",
# "dbStats",
# "find",
# "killCursors",
# "listIndexes",
# "listCollections",
# "convertToCapped",
# "createCollection",
# "createIndex",
# "dropIndex",
# "insert",
# "remove",
# "renameCollectionSameDB",
# "update"
# ]
# }
# ],
# "roles" : [ ]
# }
# switched to db admin
# Successfully added user: {
# "user" : "human_user",
# "roles" : [
# {
# "role" : "readWriteMinusDropRole",
# "db" : "social"
# }
# ]
# }
# bye
A bash script with MongoDB commands to create a user-defined role and a user.
mongo social --port 28000 -u human_user -p password --authenticationDatabase admin
# output of above command
# MongoDB shell version v3.6.2
# connecting to: mongodb://127.0.0.1:28000/social
# MongoDB server version: 3.6.2
# replset:PRIMARY>
# create a document in person collection
db.person.insert({"fname": "Shyam", "lname": "Arjarapu"})
# output of above command
# WriteResult({ "nInserted" : 1 })
db.getCollectionNames()
# The output of above command
# [ "person" ]
# drop the collection
db.person.drop()
# The output of above command
# 2018-05-14T17:01:38.830-0500 E QUERY [thread1] Error: drop failed: {
# "operationTime" : Timestamp(1526335298, 1),
# "ok" : 0,
# "errmsg" : "not authorized on social to execute command { drop: \"person\", $clusterTime: { clusterTime: Timestamp(1526335285, 1), signature: { hash: BinData(0, 7EE640BEC21303BD6514D6791A9F50D6DEB45CC5), keyId: 6555423603385434113 } }, $db: \"social\" }",
# "code" : 13,
# "codeName" : "Unauthorized",
# "$clusterTime" : {
# "clusterTime" : Timestamp(1526335298, 1),
# "signature" : {
# "hash" : BinData(0,"lh/gXN9yKwcrV4asUPFVV8kLAxA="),
# "keyId" : NumberLong("6555423603385434113")
# }
# }
# } :
# _getErrorWithCode@src/mongo/shell/utils.js:25:13
# DBCollection.prototype.drop@src/mongo/shell/collection.js:695:1
# @(shell):1:1
db.getCollectionNames()
# The output of above command
# [ "person" ]
A user with user-defined role, readWriteMinusDropRole, cannot drop a collection
While MongoDB provides various built-in roles that provide the different levels of access commonly needed in a database system, you would need user-defined roles to grant fine-grained actions to MongoDB resources.
Don’t wait till you regret of not doing it earlier. Tighten your security measures using user-defined roles and prevent someone accidentally drop a collection.
Here are a few measures taken by my clients to tighten the security.
With the MongoDB v3.6, you could further tighten the security by defining the range of IP addresses a user is allowed to authenticate from using authenticationRestrictions. But that’s a topic for another day. Hopefully, you learned something new today on you scale the path to “Mastering MongoDB - One tip a day”.