You are currently viewing Mastering MongoDB Access Control: A Comprehensive Guide

Mastering MongoDB Access Control: A Comprehensive Guide

In today’s data-driven landscape, efficient database management involves delegating specific tasks to different users with varying levels of access. MongoDB, as a modern database system, recognizes this need and provides a powerful mechanism called Role-Based Access Control (RBAC) to control access and privileges within the database. In this comprehensive tutorial, we will explore the inner workings of RBAC, understand the significance of the principle of least privilege, and delve into the practical implementation of MongoDB’s access privileges features.

Throughout this guide, you will gain a solid understanding of RBAC’s functionality and how it enables fine-grained control over user permissions. We will cover key topics such as defining roles, granting privileges, and managing access at both the database and collection levels. Additionally, we will discuss best practices for ensuring secure access to the database and the importance of adhering to the principle of least privilege.

By following this tutorial, you will acquire the necessary skills to effectively leverage MongoDB’s access control features, bolster your database security, and ensure that users and applications have appropriate and controlled access to the data they need.

Prerequisites for Following this Tutorial:

To make the most of this tutorial, ensure that you have the following prerequisites in place:

  1. A server: Prepare a server with a regular, non-root user account that has sudo privileges. Additionally, ensure that your server’s firewall is configured with UFW (Uncomplicated Firewall). If you haven’t completed this setup, you can follow our tutorial on “Initial Server Setup with Ubuntu” to get started.
  2. MongoDB installation: Install MongoDB on your server. For detailed instructions, you can refer to our tutorial on “How to Install MongoDB on Ubuntu 20.04”. Follow the steps provided to set up MongoDB correctly on your system.
  3. MongoDB instance security: It is crucial to secure your MongoDB instance by enabling authentication and creating an administrative user. This step-by-step process ensures the protection of your MongoDB deployment. To secure MongoDB on Ubuntu 20.04, follow our tutorial on “How To Secure MongoDB on Ubuntu 20.04”.

By fulfilling these prerequisites, you will have a properly configured server with MongoDB installed and secured, setting the stage for seamless implementation of the upcoming tutorial on MongoDB access control.

Note: The example tutorials on how to configure your server, install and then secure MongoDB installation refer to Ubuntu 20.04. This tutorial concentrates on MongoDB itself, not the underlying operating system. It will generally work with any MongoDB installation regardless of the operating system as long as authentication has been enabled.

Unlocking Access Control: Understanding MongoDB’s Role-Based Access Control (RBAC)

Access control — also known as authorization — is a security technique that involves determining who can gain access to which resources.

To better understand access control in MongoDB, it can be helpful to first distinguish it from a different but closely related concept: authentication. Authentication is the process of confirming whether a user or client is actually who they claim to be. Authorization, on the other hand, involves setting rules for a given user or group of users to define what actions they can perform and which resources they can access.

The following subsections expand on how MongoDB handles authentication and authorization.

Securing MongoDB: A Guide to Authentication and User Management

In many database management systems, users are identified with just a username and password pair. When connecting to a database with valid credentials, the user is authenticated and granted the level of access associated with that user. In such an approach, the user directory is flat, which means that for the entire database server each username must be unique.

In contrast, MongoDB employs a more complex user directory structure. In MongoDB users are not only identified by their usernames, but also by the database in which they were created. For each user, the database in which they were created is known as that user’s authentication database.

This means that in MongoDB it’s possible to have multiple users with the same username (codeacademia, for example) as long as they are created in different authentication databases. To authenticate as a user, you must provide not only a username and password, but also the name of the authentication database associated with that user.

One might assume that users created in a given authentication database would have access privileges available only to that particular database, but this is not the case. Each user, no matter which authentication database it was created in, can have privileges assigned across different databases.

Mastering MongoDB Authorization: A Comprehensive Guide to Role-Based Access Control (RBAC)

In MongoDB, you control who has access to what resources on a database and to which degree through a mechanism called Role-Based Access Control, often shortened as RBAC.

In Role-Based Access Control, users are not given permissions to perform actions on resources directly, such as inserting a new document into the database or querying a particular collection. This would make the security policies difficult to manage and keep consistent with many users in the system. Instead, the rules allowing actions on particular resources are assigned to roles.

It can be helpful to think of a role as one of a given user’s jobs or responsibilities. For example, it might make sense for a manager to have read and write access to every document in a company’s MongoDB instance, whereas a sales analyst might have a read-only access only to sales records.

Roles are defined with a set of one or more privileges. Each privilege consists of an action (such as creating new documents, retrieving data from a document, or creating and deleting users) and the resource on which that action can be performed (such as a database named reports or a collection called orders). Just like in real life, where a company may have many sales analysts and employees having more than one responsibility, in MongoDB many users can be assigned to the same role and a single user can have many roles granted.

Roles are identified with the combination of the role name and the database, as each role — except those created in the admin database — can only include privileges applying to its own database. By granting a user roles defined in a database other than their authentication database, a user can be given permissions to act on more than one database. Roles can be granted when you create a user or any time after then. Revoking role membership can also be done at will, making it straightforward to decouple user management from access rights management.

MongoDB provides a set of built-in roles describing privileges commonly used in database systems, such as read to grant read-only access, readWrite to grant both read and write permissions, or dbOwner to grant full administrative privileges over a given database. For more specific scenarios, user-defined roles can be also created with custom sets of privileges.

Note: You can find detailed information on all built-in roles provided by MongoDB on the Built-in roles page in the official MongoDB documentation.

Role-Based Access Control makes it possible to assign users only the minimum, precise level of access permissions they need to work on their respective tasks. This is an important security practice known as the principle of least privilege.

By following along with this guide, you will build an example database environment, create a few sample databases and users, each having different levels of access granted, showcasing the Role-Based Access Control in action.

Outlining the Example Scenario and Preparing the Sample Databases

To explain how Role-Based Access Control — RBAC, for short — works in practice, this guide follows an example scenario with an imaginary sales company called Sammy Sales that uses two databases.

The first database (called sales) will store data about customer orders in the company shop with two separate collections: customers for their customers’ personal data and orders for order details.

The second database (this one called reports) will store aggregated reports on monthly sales. This database will contain a single collection named reports.

The company has only two employees, both of whom have a level of database access that follows least privilege approach:

  • Sammy, a sales representative, needs full access to both collections in the sales database, but has no need for working with the reports database.
  • Joe, a sales analyst, needs write access to the reports database to construct reports as well as read-only access to the sales database to retrieve the data.

The requirements are illustrated on the following diagram:

The levels of access required for Sammy and Joe

To create these sample databases, open the MongoDB shell on the server where you installed MongoDB with a command like the following, making sure you authenticate as your administrative user in the process. This example follows the conventions established in the prerequisite  tutorial, in which the administrative MongoDB user is named AdminCodeacademia. Be sure to replace AdminCodeacademia with your own administrative user’s username if different:

mongo -u AdminCodeacademia -p --authenticationDatabase admin

When prompted, enter the password that you set during installation to get access to the shell.

You can verify that you have access to the entire MongoDB instance by issuing the show dbs command:

show dbs

This will return a list of all the databases currently available:

Outputadmin   0.000GB
config  0.000GB
local   0.000GB

After confirming that you can access these databases, switch to the sales database:

use sales

The shell will reply with a short confirmation

Outputswitched to db sales

In MongoDB, there is there is no explicit action to create a database. A database is only created when it stores at least one document. With this in mind, you will need to insert some sample documents to prepare the databases and collections used in examples throughout this guide.

You are now in the sales database, but it won’t actually exist until you insert something into it.

Create a collection named customers within sales and simultaneously insert a new document into it with the following operation:

db.customers.insert({name: 'codeacademia'})

This example document only contains a name field with the value 'codeacademia'. Note that the data itself is not relevant for showcasing how the access rights work in practice, so this step outlines how to create database documents that only contain example mock data.

MongoDB will confirm the insertion with:

OutputWriteResult({ "nInserted" : 1 })

Repeat this process, but this time create a collection named orders. To do this, run the following command. This time, the document’s only field is total and it has a value of 100:

db.orders.insert({total: 100})

MongoDB will once again confirm that the document was properly inserted.

Since you will be working with two databases, you will need to prepare the reports database as well. To do so, first switch to the reports database:

use reports

And insert another document, this time into reports collection:

db.reports.insert({orders: 1})

To confirm that both databases have been properly prepared, issue the show dbs command once again.

show dbs

After running this command a second time, the result will show two new entries for newly-created databases. These databases only persisted after you created the first documents within each of them:

Outputadmin    0.000GB
config   0.000GB
local    0.000GB
reports  0.000GB
sales    0.000GB

The sample databases are now ready. Now, you can create a pair of users who will have the least amount of access privileges to the newly-created databases needed for this example scenario.

Creating the First User

In this step, you’ll create the first of two MongoDB users. This first user will be for Sammy, the company’s sales representative. This account will need full access to the sales database, but no access whatsoever to the reports database.

For this, we’ll use the built-in readWrite role to grant both read and write access to the resources in the sales database. Since Sammy is a sales representative, we’ll also use the sales database as the authentication database for the newly-created user.

First, switch to the sales database:

use sales

The shell will return a confirmation that you’re using the chosen database:

Outputswitched to db sales

Because codeacademia works in the sales department, their MongoDB user account will be created with sales as the authentication database.

Run the following method to create the codeacademia user:

db.createUser(
  {
    user: "codeacademia",
    pwd: passwordPrompt(),
    roles: [
      { role: "readWrite", db: "sales" }
    ]
  }
)

This createUser method includes the following objects:

  • user represents the username, which is codeacademia in this example.
  • pwd represents the password. By using passwordPrompt() you will ensure that MongoDB shell will ask for the password when executing the command to be entered.
  • roles is the list of roles granted. This example assigns codeacademia the readWrite role, granting them read and write access to the sales database. No other roles are assigned to codeacademia now, which means this user will not have any additional access rights immediately after creation.

Note: As mentioned in the prerequisite tutorial, the passwordPrompt() method is only compatible with MongoDB versions 4.2 and newer. If you’re using an older version of Mongo, then you will have to write out this user’s password in cleartext, similarly to how you wrote out the username:

. . .
    pwd: "password",
. . .

If this method is successful, it will return a confirmation message from the MongoDB shell similar to the following:

OutputSuccessfully added user: {
	"user" : "codeacademia",
	"roles" : [
		{
			"role" : "readWrite",
			"db" : "sales"
		}
	]
}

You can now verify that the new user can log in to the database and whether their access rights you specified are properly enforced.

You will keep the current MongoDB shell with your administrative user logged in open for later, so open a separate server session.

From the new server session, open the MongoDB shell. This time, specify specify sammy as the user and sales as the authentication database:

mongo -u codeacademia -p --authenticationDatabase sales

Enter the password you set when creating the codeacademia user. After accessing the shell prompt, execute the show dbs command to list the available databases:

show dbs

In contrast with your administrative account, only one database will be listed for codeacademia, as you’ve only granted them access to the sales database.

Outputsales  0.000GB

Now check whether codeacademia can retrieve objects from both collections in the sales database. Switch to the sales database:

use sales

Then try to retrieve all customers:

db.customers.find()

This find command will return the document you created in this collection in Step 1:

Output{ "_id" : ObjectId("60d888946ae8ac2c9120ec40"), "name" : "codeacademia" }

You can also confirm that the second collection, orders, is available as intended:

db.orders.find()

Output{ "_id" : ObjectId("60d890730d31cc50dedea6ff"), "total" : 100 }

To make sure the access rights for sales database have been properly configured, you can check whether codeacademia can insert new documents as well. Try inserting a new customer:

db.customers.insert({name: 'Ellie'})

Because you granted codeacademia the readWrite role, they are authorized to write new documents to this database. MongoDB will confirm the insertion was completed successfully:

OutputWriteResult({ "nInserted" : 1 })

Lastly, verify whether codeacademia can access the reports database. They will not be able to read or write any data in this database, as you did not grant them access through the assigned roles.

Switch to the reports database:

use reports

This use command will not result in any error on its own. Try accessing the document you inserted in Step 1 by running the following:

db.reports.find()

Now, MongoDB will throw an error message instead of returning any objects:

OutputError: error: {
  "ok" : 0,
  "errmsg" : "not authorized on reports to execute command { find: \"reports\", filter: {}, lsid: { id: UUID(\"cca9e905-89f8-4903-ae12-46f23b43b967\") }, $db: \"reports\" }",
  "code" : 13,
  "codeName" : "Unauthorized"
}

The Unauthorized error message tells you that codeacademia does not have enough access rights to interact with the data in the reports database.

So far, you’ve created the first database user with limited privileges and verified the access rights are properly enforced. Next, you will create a second user with different privileges.

Creating the Second User

Having created the codeacademia MongoDB user for Sammy, the sales representative, you still need an account for Joe, the company’s sales analyst. Recall from the example scenario that Joe’s job function requires a different set of privileges for the databases.

The process of creating this new user account is similar to the process you followed to create the codeacademia user.

Return to the server session where your administrative user is logged in to the MongoDB shell. From there, switch to reports database:

use reports

Because Joe works in the reporting department, their MongoDB user account will be created with reports as the authentication database.

Create the new joe user with the following command:

db.createUser(
  {
    user: "joe",
    pwd: passwordPrompt(),
    roles: [
      { role: "readWrite", db: "reports" },
      { role: "read", db: "sales" }
    ]
  }
)

Notice the differences between the method used to create joe and the one used to create codeacademia in the previous step. This time, you assign two separate roles:

  • readWrite applied to the reports database means joe will be able to read and write sales report data to this database
  • read applied to the sales database makes sure that joe can access the sales data, but will not be able to write any documents into that database

Both of these roles are built-in MongoDB roles.

This command will return a confirmation message similar to the following:

OutputSuccessfully added user: {
	"user" : "joe",
	"roles" : [
		{
			"role" : "readWrite",
			"db" : "reports"
		},
		{
			"role" : "read",
			"db" : "sales"
		}
	]
}

Next, verify that the new user’s permissions are being properly enforced.

Once again, open another server session, as you’ll make use of both the administrative MongoDB user and the codeacademia user in a later step.

Open the MongoDB shell, this time specifying joe as the user and reports as the authentication database:

mongo -u joe -p --authenticationDatabase reports

When prompted, enter the password you set when creating the joe user. Once you have access to the shell prompt, execute the show dbs command to list the databases available to joe:

show dbs

Since joe can use both the sales and reports databases, those two databases will be listed in the output:

Outputreports  0.000GB
sales    0.000GB

Now you can check whether joe can retrieve objects from the sales database.

Switch to sales:

use sales

Run the following find command to try to retrieve all orders:

db.orders.find()

Assuming you set up permissions correctly, this command will return the lone document you created in this collection in Step 1:

Output{ "_id" : ObjectId("60d890730d31cc50dedea6ff"), "total" : 100 }

Next, try inserting a new document into the orders collection:

db.orders.insert({total: 50})

Because you assigned joe only a read role for this database, this insert command will fail with an error message:

OutputWriteCommandError({
  "ok" : 0,
  "errmsg" : "not authorized on sales to execute command { insert: \"orders\", ordered: true, lsid: { id: UUID(\"ebbe853b-e269-463f-a1d4-2c5a5accb966\") }, $db: \"sales\" }",
  "code" : 13,
  "codeName" : "Unauthorized"
})

The Unauthorized message tells you the reason behind the failure — the access rights joe has are not enough to insert a new document.

Next, confirm whether joe can read and write data in the reports database.

Switch to reports:

use reports

Then, try accessing the data within it using the find command:

db.reports.find()

Since joe is allowed to read from the database, MongoDB will respond with the list of currently available documents in this collection:

Output{ "_id" : ObjectId("60d8897d6ae8ac2c9120ec41"), "orders" : 1 }

Then try inserting a new report by running the following command:

db.reports.insert({orders: 2})

This command will also succeed with the output message similar to this:

OutputWriteResult({ "nInserted" : 1 })

With that, you’ve created the second database user with limited privileges, but this time you granted them roles to two separate databases. You also verified that their access rights are properly enforced by the database server. Next, you will grant and then revoke additional permissions to one of the existing users.

Granting and Revoking Roles for Existing Users

In steps 2 and 3, you created new users and assigned them to roles during creation process. In practice, database administrators often need to revoke or grant new privileges to users that have already been created in the system. In this step, you will grant the codeacademia user a new role to allow them to access the reports database and revoke that permission shortly afterwards.

From the administrative shell, switch to the sales database where the user codeacademia was created:

use sales

To verify the user codeacademia is there, execute the show users command:

show users

This command will return a list of all the users in this database as well as their respective roles:

Output{
	"_id" : "sales.codeacademia",
	"userId" : UUID("cbc8ac18-37d8-4531-a52b-e7574044abcd"),
	"user" : "codeacademia",
	"db" : "sales",
	"roles" : [
		{
			"role" : "readWrite",
			"db" : "sales"
		}
	],
	"mechanisms" : [
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}

To assign a new role to this user, you can use the grantRolesToUser method. This method accepts the user’s name and the list of roles to be granted in the same syntax as used when creating a new user.

The goal in this step is to grant codeacademia read-only permissions for reports database, so assign them the read role for that database:

db.grantRolesToUser("codeacademia", [{role: "read", db: "reports"}])

The command yields no output unless there was an error, so the lack of any message is an expected behavior. You can verify the command has taken effect by executing the show users command again:

show users

This command will return output similar to the following:

Output{
	"_id" : "sales.codeacademia",
	"userId" : UUID("cbc8ac18-37d8-4531-a52b-e7574044abcd"),
	"user" : "codeacademia",
	"db" : "sales",
	"roles" : [
		{
			"role" : "read",
			"db" : "reports"
		},
		{
			"role" : "readWrite",
			"db" : "sales"
		}
	],
	"mechanisms" : [
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}

Notice the newly-added role in the roles section.

Now you can check whether codeacademia is indeed able to access the reports database after the change. Switch to the terminal window with codeacademia logged in and try accessing the reports once again.

Switch to the reports database:

use reports

And then run the find command on the reports collection:

db.reports.find()

Last time, the command failed with an error message. This time, though, it will return the list of documents in the reports database:

Output{ "_id" : ObjectId("60d8897d6ae8ac2c9120ec41"), "orders" : 1 }
{ "_id" : ObjectId("60d899cafe3d26bf80e947fd"), "orders" : 2 }

After some time you might want to revoke the codeacademia user’s ability to access the reports. To illustrate this, go back to the administrative console and execute the following command, which will revoke the codeacademia user’s read privileges on the reports database:

db.revokeRolesFromUser("codeacademia", [{role: "read", db: "reports"}])

The revokeRolesFromUser method takes the same set of arguments as grantRolesToUser, but instead removes the specified roles.

Once again, you can verify that the role is no longer available with show users:

Output{
	"_id" : "sales.codeacademia",
	"userId" : UUID("cbc8ac18-37d8-4531-a52b-e7574044abcd"),
	"user" : "codeacademia",
	"db" : "sales",
	"roles" : [
		{
			"role" : "readWrite",
			"db" : "sales"
		}
	],
	"mechanisms" : [
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}

To double check that codeacademia can no longer read from the reports database, try re-running the previous find command in the shell with codeacademia logged in:

db.reports.find()

This time the command will once again fail with an Unauthorized error:

OutputError: error: {
  "ok" : 0,
  "errmsg" : "not authorized on reports to execute command { find: \"reports\", filter: {}, lsid: { id: UUID(\"2c86fba2-7615-40ae-9c3b-2dfdac2ed288\") }, $db: \"reports\" }",
  "code" : 13,
  "codeName" : "Unauthorized"
}

This shows that your revocation of the codeacademia user’s read role was successful.

Leave a Reply