MongoDB Authorization Model - User-defined Roles Part 2

Introduction

In the first part (Part 1) of our series on the MongoDB authorization model, we talked about user-defined roles, focusing on the various actions that a user can perform and the resources that these actions can be performed on. Now, we can turn our attention to privileges and see what they look like in MongoDB. In this article, we’ll continue our discussion of user-defined roles in the MongoDB authorization model with a special focus on privileges.

Prerequisites

Before we proceed with our discussion of privileges and how they fit into the authorization model, it’s important to make sure certain prerequisites are in place:

  • MongoDB needs to be properly installed and configured.

  • You’ll need to have a basic understanding of MongoDB built-in roles and how they work.

  • It’s best to have read the first article in this series before proceeding with this one.

NOTE: The MongoDB version used throughout this article is v4.0.10.

Privileges

In MongoDB, a privilege can be thought of as a permission to perform a group of actions against a given resource.

Privileges can be defined via a privilege document; an example of one is shown below:

{
resource: { # (1)
db: <database>,
collection: <collection>
},
actions:{ # (2)
<action>
}
}

In the code shown above, we first define the resource (1) which is composed of the database and collection. We then define the action (2) with a set of actions that we will perform on that resource.

Next, we’ll define a built-in role in order for us to better understand how privileges work.

Let’s start by discussing the simplest of all roles which is the read role. Use the following command db.getRole() in the Mongo shell:

> db.getRole("read")
{
"role" : "read",
"db" : "test",
"isBuiltin" : true,
"roles" : [ ],
"inheritedRoles" : [ ]
}

This example helps us see how the role is defined. It shows the name of the role which is read, the database name test and the array of inherited roles inheritedRoles.

We can see all the privileges that make up this role using the following command:

db.getRole("read", {showPrivileges: true})

The code shown above is almost the same as the example we looked at earlier; however, this time we’re passing in a document saying showPrivileges:true.

The output should look something like the following, although part of the output has been snipped so that we can focus on the part that shows the word privileges:

{
"role" : "read",
"db" : "test",
"isBuiltin" : true,
"roles" : [ ],
"inheritedRoles" : [ ],
"privileges" : [
{
"resource" : {
"db" : "test",
"collection" : ""
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
},

}

You’ll notice a field called privileges, which is composed of an array. We can make things easier to read by assigning the object to a variable so that we can inspect each privilege one at a time. To do this, we’ll just use the following command:

> var readRole = db.getRole("read", {showPrivileges: true})
> readRole.privileges

The output should look something like this:

[
{
"resource" : {
"db" : "test",
"collection" : ""
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
},
{
"resource" : {
"db" : "test",
"collection" : "system.indexes"
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
},
{
"resource" : {
"db" : "test",
"collection" : "system.js"
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
},
{
"resource" : {
"db" : "test",
"collection" : "system.namespaces"
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
}
]

To see how many privileges we have, simply use the following command:

readRole.privileges.length

This time, the result should be 4. We can see the details of the privileges by using this command:

readRole.privileges[0]

You’ll get results that look something like the following:

{
"resource" : {
"db" : "test",
"collection" : ""
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
}

We can see that anyone with a “read” role is able to perform the following:

changeStreamcollStatsdbHashdbStatsfind
killCursorslistCollectionslistIndexes

To see other privileges, we can just use the same code but change the value that we pass as the index or the position of the document. For example, we can view details of the second privilege in the list by using the command readRole.privileges[1]. The output will look like this:

{
"resource" : {
"db" : "test",
"collection" : "system.indexes"
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
}

To see details about the third privilege, use this command readRole.privileges[2]. The results should look like the following:

{
"resource" : {
"db" : "test",
"collection" : "system.js"
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
}

You can see that this holds the Javascript code to be used for server-side scripting.

We can keep going through the set of privileges and view the fourth one by using this command readRole.privileges[3]. This time, the result should look like the following:

{
"resource" : {
"db" : "test",
"collection" : "system.namespaces"
},
"actions" : [
"changeStream",
"collStats",
"dbHash",
"dbStats",
"find",
"killCursors",
"listCollections",
"listIndexes",
"planCacheRead"
]
}

This result is for system.namespaces, which allows users to list all the collections within the specified database.

How to Create a User with a Built-in Role

Now that we’ve talked about privileges as they relate to user authorization in MongoDB, we’ll take our discussion a few steps further with a real-world example.

Let’s look at a hypothetical scenario:

Our example will involve an organization with three database users. Each user performs different roles depending on the function in the organization. Here’s the breakdown of these three users with their respective responsibilities:

  1. Yeshua — Security Officer
  2. Raizel — System Administrator
  3. David — Developer who works on one specific database

Creating the Security Officer Role

Now that we understand our users’ responsibilities, we can assign them the right roles. Let’s begin by connecting to our cluster to create our first user. The first user should be a user who then can create another user; thus, we’ll start with user “Yeshua” as he is the security officer.

Before creating the user “Yeshua”, let’s take a moment to define what actions he’ll need to perform his duty as the security officer.

Responsible ForNot Responsible For
Create/Drop users in any databasePerform backup and restore operation
Can grant and revoke rolesPerform create and drop any given database and collection
Can perform create, update and delete rolesViewing any collection data

We’ve set up a replica set on this machine and enabled the authentication. We’ll connect to this replica set using the mongo command and switch to the admin database by using the following command: use admin.

We can then go ahead and create our first user “Yeshua” using the following command:

db.createUser({ user: 'yeshua', pwd:'password', roles: [{ role: 'userAdminAnyDatabase', db: 'admin' }]})

The output should look like the following:

Successfully created user: {
"user" : "yeshua",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}

We created the user “yeshua” with a role of userAdminAnyDatabase on the admin database– this means he can administer any users in any database.

To find out more information about user “yeshua”, let’s show all of this user’s privileges. We’ll do this by using the following commands:

var userRole = db.getRole('userAdminAnyDatabase',{showPrivileges:true})

First, we’ll see how many privileges we have in this role:

userRole.privileges.length

The output returns “10”. This means that we have 10 privileges, and the results will be very long. For now, let’s just look closely at one of the privileges. We’ll choose the first one from the results set.

You should see something like this:

{
"role" : "userAdminAnyDatabase",
"db" : "admin",
"isBuiltin" : true,
"roles" : [ ],
"inheritedRoles" : [ ],
"privileges" : [
{
"resource" : {
"db" : "",
"collection" : ""
},
"actions" : [
"changeCustomData",
"changePassword",
"createRole",
"createUser",
"dropRole",
"dropUser",
"grantRole",
"revokeRole",
"setAuthenticationRestriction",
"viewRole",
"viewUser"
]
}

The result shown above has a resource that allows us to perform an action on any collection, on any database:

"resource" : {
"db" : "",
"collection" : ""
}

The list shown below includes all the actions that can be performed against any collection:

"actions" : [
"changeCustomData",
"changePassword",
"createRole",
"createUser",
"dropRole",
"dropUser",
"grantRole",
"revokeRole",
"setAuthenticationRestriction",
"viewRole",
"viewUser"
]

After reviewing this output, we can feel confident that our user “yeshua” has all the needed actions and privileges to perform his job as a security officer.

Conclusion

In this article, we continued our discussion of roles and their function in the MongoDB authorization model. Our example involving the security officer showed how to configure the required actions and corresponding privileges for a given user. With the examples and instructions provided in this article, you’ll be ready to configure roles in your own MongoDB deployment and set up your users with the privileges they need.

Pilot the ObjectRocket Platform Free!

Try Fully-Managed CockroachDB, Elasticsearch, MongoDB, PostgreSQL (Beta) or Redis.

Get Started

Keep in the know!

Subscribe to our emails and we’ll let you know what’s going on at ObjectRocket. We hate spam and make it easy to unsubscribe.