Shyam Arjarapu

Augh, Someone deleted production data.

“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.

Mastering user-defined role

What is a role

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
  1. A user is granted one or more roles.
  2. A role grants privileges to perform sets of actions on a resource.
  3. A privilege consists of a resource and the actions permitted on them.
  4. A resource is a database, collection or set of collections.
  5. An action specifies the operation allowed on the resource.

Why user-defined role

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
  • read
  • readWrite
Database Administration Roles
  • dbAdmin
  • userAdmin
  • dbOwner
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.

readWrite role has dropCollection action

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.

Hands-On lab exercises

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.

Setup environment

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

Create app_user with readWrite role

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

A user with readWrite role can drop collection

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

Create a user-defined role

Log in to the test environment using the user credentials and create the following on the social database.
  • a user-defined role, readWriteMinusDropRole
  • a user human_user with readWriteMinusDropRole role
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.

User with readWriteMinusDropRole role cannot drop collection

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

Summary

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.
  1. No access to the production environment for developers (more drastic)
  2. If access is required, give ‘read’ role to developers (much needed)
  3. Create a user-defined role with all ‘readWrite’ actions but ‘dropCollection’
  4. If ‘read & write’ permissions is required for any users, assign above user-defined role (highly recommended)
  5. Create a separate app_user with ‘readWrite’ permissions for your application to interact with MongoDB
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”.

Tags

Comments

October 22nd, 2019

Austin, thanks for sharing your opinion. You are correct about the need to have backups done regularly. That’s why my article starts by stating, “Unless you can restore the data from a backup, you are in a terrible situation!”.

“Should a developer have access to a production database?” is more of an organization’s best-practice decision. I worked for many companies where “The principle of least privilege” is strictly enforced. However, if you do work in an organization where you wear multiple hats of Developer, DBA, and Operations, then you may need more privileges in such scenarios.

Referring to your example scenario, I understand that manually updating the “integer field” is an ad-hoc request, and someone needs to edit it manually. The main purpose of the article is to suggest measures one could take to prevent the users from accidentally deleting/dropping the data by using custom roles.

More by Shyam Arjarapu

Topics of interest