Mongodb Create Read Only User for All Databases
The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.
Introduction
Modern database systems are capable of storing and processing huge amounts of data. Because of this, it's relatively uncommon for any one user person to be solely responsible for handling all the activities related to managing a database. Often, different database users require different levels of access to certain parts of a database: some users might need only to read the data in specific databases, while others must be able to insert new documents or modify existing ones. Likewise, an application might require unique permissions that only allow it to access the parts of a database it needs to function.
MongoDB employs a robust mechanism to control access and privileges to a databases system known as Role-Based Access Control (RBAC). In this tutorial, you'll learn how RBAC works, the meaning and purpose of the principle of least privilege, as well as how to use MongoDB's access privileges features in practice.
Prerequisites
To follow this tutorial, you will need:
- A server with a regular, non-root user with
sudo
privileges and a firewall configured with UFW. You can prepare your server by following this initial server setup tutorial. - MongoDB installed on your server. To set this up, follow our tutorial on How to Install MongoDB on Ubuntu 20.04.
- Your server's MongoDB instance secured by enabling authentication and creating an administrative user. To secure MongoDB like this, follow our tutorial on How To Secure MongoDB on Ubuntu 20.04.
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.
How MongoDB Controls Access with Role-Based Access Control
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.
Authentication in MongoDB
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 (sammy, 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.
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.
Step 1 — 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 thereports
database. - Joe, a sales analyst, needs write access to the
reports
database to construct reports as well as read-only access to thesales
database to retrieve the data.
The requirements are illustrated on the following diagram:
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 How To Secure MongoDB on Ubuntu 20.04 tutorial, in which the administrative MongoDB user is named AdminSammy. Be sure to replace AdminSammy with your own administrative user's username if different:
- mongo -u AdminSammy -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:
Output
admin 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
Output
switched 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: 'Sammy'})
This example document only contains a name
field with the value 'Sammy'
. 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:
Output
WriteResult({ "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:
Output
admin 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.
Step 2 — 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:
Output
switched to db sales
Because Sammy 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 sammy user:
- db.createUser(
- {
- user: "sammy",
- pwd: passwordPrompt(),
- roles: [
- { role: "readWrite", db: "sales" }
- ]
- }
- )
This createUser
method includes the following objects:
-
user
represents the username, which is sammy in this example. -
pwd
represents the password. By usingpasswordPrompt()
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 sammy thereadWrite
role, granting them read and write access to thesales
database. No other roles are assigned to sammy now, which means this user will not have any additional access rights immediately after creation.
Note: As mentioned in the prerequisite tutorial on How To Secure MongoDB, 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:
If this method is successful, it will return a confirmation message from the MongoDB shell similar to the following:
Output
Successfully added user: { "user" : "sammy", "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 sammy -p --authenticationDatabase sales
Enter the password you set when creating the sammy 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 sammy, as you've only granted them access to the sales
database.
Output
sales 0.000GB
Now check whether sammy 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" : "Sammy" }
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 sammy can insert new documents as well. Try inserting a new customer:
- db.customers.insert({name: 'Ellie'})
Because you granted sammy the readWrite
role, they are authorized to write new documents to this database. MongoDB will confirm the insertion was completed successfully:
Output
WriteResult({ "nInserted" : 1 })
Lastly, verify whether sammy 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:
Output
Error: 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 sammy 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.
Step 3 — Creating the Second User
Having created the sammy 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 sammy 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 sammy in the previous step. This time, you assign two separate roles:
-
readWrite
applied to thereports
database means joe will be able to read and write sales report data to this database -
read
applied to thesales
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:
Output
Successfully 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 sammy 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:
Output
reports 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:
Output
WriteCommandError({ "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:
Output
WriteResult({ "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.
Step 4 — 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 sammy 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 sammy was created:
- use sales
To verify the user sammy 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.sammy", "userId" : UUID("cbc8ac18-37d8-4531-a52b-e7574044abcd"), "user" : "sammy", "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 sammy read-only permissions for reports
database, so assign them the read
role for that database:
- db.grantRolesToUser("sammy", [{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.sammy", "userId" : UUID("cbc8ac18-37d8-4531-a52b-e7574044abcd"), "user" : "sammy", "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 sammy is indeed able to access the reports
database after the change. Switch to the terminal window with sammy 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 sammy 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 sammy user's read
privileges on the reports
database:
- db.revokeRolesFromUser("sammy", [{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.sammy", "userId" : UUID("cbc8ac18-37d8-4531-a52b-e7574044abcd"), "user" : "sammy", "db" : "sales", "roles" : [ { "role" : "readWrite", "db" : "sales" } ], "mechanisms" : [ "SCRAM-SHA-1", "SCRAM-SHA-256" ] }
To double check that sammy can no longer read from the reports
database, try re-running the previous find
command in the shell with sammy logged in:
- db.reports.find()
This time the command will once again fail with an Unauthorized
error:
Output
Error: 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 sammy user's read
role was successful.
Conclusion
In this article, you learned how to create users with limited access to the database and use the Role-Based Access Control to enforce the least privilege principle on your database, granting only the minimal necessary set of privileges to users. You also learned how to grant and revoke roles from existing users, learning how to manage access rights on a living database server when the necessary permissions change over time and verify that the changes take effect immediately.
With MongoDB's Role-Based Access Control you can define precise access levels using features such as User-Defined Roles, create custom roles when built-in ones are not satisfactory, and Collection-Level Access Control that allows administrators to grant users privileges to specific collections rather than whole databases. We encourage you to learn more about these and other Role-Based Access Control features described in detail in the official MongoDB documentation.
Mongodb Create Read Only User for All Databases
Source: https://www.digitalocean.com/community/tutorials/how-to-use-mongodb-access-control