Introduction
This article is about the access control necessary to secure a MongoDB database.
Specifically, it is about those features that we can use to ensure that only authorised users are allowed to access the database.
Each MongoDB user should only be able to access only the data that is required for their role in the organisation, as determined by whoever has the task of managing data security within the organisation.
This is required for good stewardship of the data and compliance with international requirements.
Access Control
Access control ensures that the people accessing the database are positively identified and can access, update, or delete the data that they are entitled to.
This is the topic we will cover in this article. From within the database, we can take care of the authentication of clients and the authorization of the operations they wish to perform.
Authentication
When a client, or a user, reaches the database, the first task is to check whether this user is known or not, and can provide credentials to ensure that they are convincingly identified. This is known as authentication.
With MongoDB we can use one of the following tools to deal with the authentication:
Internal tools
- SCRAM. The default authentication mechanism for MongoDB. It verifies the user and password supplied against the user’s name, password and authentication database.
- x.509 Certificates. Instead of usernames and passwords, this mechanism uses x.509 certificates to authenticate clients against servers or members inside a Replica Set or a Sharded Cluster. Wikipedia says: “An x.509 certificate contains a public key and an identity and is either signed by a certificate authority or self-signed. Someone holding the certificate can rely on the public key it contains to establish secure communications”.
External tools
- LDAP. This is a protocol whose most common use is to provide a central place to store usernames and passwords allowing different applications to connect to the LDAP server to validate users.
- Kerberos. This is an industry standard authentication protocol based on tickets.
As a best practice, we will create login credentials for each entity that will need to access the database, but only for those. By doing this, we will be able to audit all the activities done by all the users and accomplish GDPR requirements.
Apart from user authentication, you will also need to authenticate servers and network processes.
Yes, in a Replica Set or a Sharded Cluster all the nodes check each other continuously in order to be sure all of them are known (in other words, to confirm their membership), as well as other tasks such as checking the health of each member in order to determine whether the Replica must accomplish a new election.
So what is an election?
In MongoDB, only one node is able to perform writes. When this node is down, or a network partition comes into play, the remaining nodes begin an election in order to choose the new primary node and get the service running without stopping.
Authorization
The administrator of the database is the right person for the task of granting, or denying, permissions to users for doing operations with database resources.
By using roles, we can specify what action can be done with a resource. Therefore, a role is a privilege granted to a user to do specific tasks with specific resources.
Resources ← Actions ← Roles (Privileges) → Users
MongoDB offers built-in roles and also enables you to define new ones depending on the specific requirements for the database. These roles are defined in terms of actions on resources.
Actions are all those kinds of operations we can run against the database such as find, delete, insert, update, or createIndex. A resource can be a collection, a document, an index, a database, and so on.
Through the use of read-only views, administrators get field-level security by restricting access to sensitive data exposing only a subset of it. Permissions granted against views are specified separately from those granted to the underlying collections.
Each role should grant only the necessary permissions for that role, and users should only be assigned roles that are appropriate for their requirements.
Database for Authentication
MongoDB users must identify themselves with the database in which they were originally created. This is usually the admin database but can be any other.
Whichever database the users have been created on, they will be able to take actions on other databases if a suitable role has been granted to the user.
MongoDB Users
Before you enable access control, you should create a user that can then create users and assign roles to them once access control is enabled.
This user-admin will then be used to create and maintain other users and roles, so needs to be assigned a suitable role to enable it to do so.
In case you do not create this user-admin, you will not be able to log in, or create new users and roles when access control is enabled.
Localhost Exception
If you were to enable access control without previously having created at least a user-admin user, then you would not be able to log in.
The localhost exception is here to avoid this problem by allowing you to create the first user after enabling access control.
To do this, you need to:
- Enable access control
- Connect to the localhost interface
- Create the first user in the admin database that must have enough permissions to manage other users and roles.
This localhost exception applies only when there are still no users created. You must choose between two options: either create your first user before enabling the access control or afterwards via the use of the localhost exception.
How to Enable Access Control
When starting your mongod
service you use parameters to specify the characteristics of your database or, better, use a config file.
Either way, you have to use the security options:
security authorization:enabled
This setting enables or disables the Role-Based Access Control.
How to Create a User
Before creating a MongoDB user, it is worth thinking about the tasks the user is going to perform.
Probably, there will be several users with the same level of permissions, so the smartest option is to create a role and assign it to each of these users.
By changing only a role, you will update the permissions of all users who use it. Otherwise, a change to an access requirement for a group or class of user would need to be done for every user.
The first step is to change context to the database in which you are going to create the role:
> use admin
The second step is to execute this command:
> db.createUser({ user : '<userName>', pwd : '<password>', roles : [ { role : '<roleName>', db : '<dbName>' } | '<roleName>', …] })
If you want to create a user without assigning that user any role you only have to specify an empty roles field.
How to Drop a User
Assuming that you are logged in with a suitable role that allows you to drop users, you will need to change context to the database where it was created…
> use admin
…and you run the command:
> db.dropUser('<userName>')
Where are Users Stored?
To check a user, you must change your context to the database in which the user was created, such as the Admin database.
> use '<dbName>'
You can then use either
> db.system.users.find()
or
> db.getUsers()
But, if you only want to ask for a specific user, use this command:
> db.getUser('<userName>')
How to Login
There are three possible cases, obviously all of them with the same philosophy. Let’s see:
Inside the database
$ mongo > use '<dbName>' > db.auth('<userName>','<password>')
I do not recommend that you use this method because the password is visible while you are typing it.
From the shell
$ mongo --authenticationDatabase <dbName> -u <userName> -p MongoDB shell version v3.6.4-rc0 Enter password: connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.6.4-rc0 MongoDB Enterprise >
This is my preferred option.
In this case, if we do not specify the --authenticationDatabase
parameter, the database will always try to authenticate the user against the database we are about to be connected to.
If we do not specify a database name to connect to, as I did in the example above, the server will do it to the ‘test’ database.
From a MongoDB client
From a MongoDB client, we must use a connection string like this:
mongo://<userName>:<password>@<hostName>:27017/<dbName>?options
How to Logout
The logout ends the current authentication session. You must do it in the same database that you authenticated to.
> use admin > db.logout()
MongoDB Roles
As you already know, a role is a privilege granted to a user for making actions over resources.
The role defines the tasks that the role member is allowed to do and the resources where those tasks can be done.
MongoDB offers built-in roles for the most common purposes. But, also, allows us to create our own roles depending on our specific needs.
Each role is scoped to the database in which it has been created.
A role can only include privileges that apply to its database and can only inherit from other roles in its database.
A role created in the admin database can include privileges that apply to the admin database, other databases or to the cluster resource, and can inherit from roles in other databases as well as the admin database.
So, if you need to inherit from a role created in another database you will have to create your new role in the admin database.
Where are Roles Stored?
I have explained before that you can create roles in the admin database or in any other.
Therefore, if you want to check them, you must do it in the database where they were defined:
> use '<dbName>'
to get all the roles of a database, use
> db.system.roles.find()
or
> db.getRoles()
If you only want to ask for a specific role, you will use this command:
> use '<dbName>' > db.getRole('<roleName>')
Built-in Roles
MongoDB classifies the built-in roles as
- Database User Roles
- Database Administration Roles
- Cluster Administration Roles
- Backup and Restoration Roles
- All-Database Roles
- Superuser Roles
Let’s see each of them in detail.
Database User Roles
The roles available at the database level are:
- read – Read data on all non-system collections
- readWrite – Include all ‘read’ role privileges and the ability to write data on all non-system collections
Database Administration Roles
The database administration roles we can use are the following:
- dbAdmin – Grant privileges to perform administrative tasks
- userAdmin – Allows you to create and modify users and roles on the current database
- dbOwner – This role combines the following:
- readWrite
- dbAdmin
- userAdmin
Cluster Administration Roles
Roles at the admin database for administering the whole system.
- clusterMonitor – Provides read-only access to monitoring tools
- clusterManager – For management and monitoring actions on the cluster
- hostManager – To monitor and manage servers
- clusterAdmin – Combines the other three roles plus dropDatabase action
Backup and Restoration Roles
This role belongs to the admin database.
- backup – Provides the privileges needed for backing up data
- restore – Provides the privileges needed to restore data from backups
All-Database Roles
These roles lie on the admin database and provide privileges which apply to all databases.
- readAnyDatabase – The same as ‘read’ role but applies to all databases
- readWriteAnyDatabase – The same as ‘readWrite’ role but applies to all databases
- userAdminAnyDatabase – The same as ‘userAdmin’ role but applies to all databases
- dbAdminAnyDatabase – The same as ‘dbAdmin’ role but applies to all databases
Superuser Roles
The following roles are not superuser roles directly but are able to assign any user any privilege on any database, also themselves.
- userAdmin
- dbOwner
- userAdminAnyDatabase
The root role provides full privileges on all resources:
- root
How to Check the Privileges of a Role
If you need to know the privileges (inherited from other roles or not) of a role, you can activate the ‘showPrivileges’ field:
> use '<dbName>' > db.getRole('<roleName>', { showPrivileges : true })
Summary of Roles
Whoever is administering the MongoDB estate must find the most suitable roles for his own use-cases.
In my opinion, the following roles are typically the most useful:
- userAdminAnyDatabase
- clusterManager
- clusterMonitor
- backup
- restore
- dbAdmin
- readWrite
- read
How to Grant a Role to a User
You can grant a role as you create the user, or retrospectively.
The next command is valid for assigning a role at the same time you create the user:
> use '<dbName>' > db.createUser( { user: "<userName>", pwd: "<password>", roles: [ { role: "<roleName>", db: "<dbName>" } ] })
And you can use this command to do it later:
> use '<dbName>' > db.grantRolesToUser( '<userName>', [ { role : '<roleName>', db : '<dbName>' }, '<roleName>', … ] )
How to Revoke a Role from a User
> use '<dbName>' > db.revokeRolesFromUser( '<userName>', [ { role : '<roleName>', db : '<dbname>' } | '<roleName>' ] )
User-defined Roles
How to Create a Role
> use '<dbName>' > db.createRole({ role: "<roleName>", privileges: [ { resource: { db : “<dbName>”, collection : “<collectionName>” }, actions: [ '<actionName>' ] } ], roles: [ { role : '<fatherRoleName>', db : '<dbName>'} | '<roleName>' ] })
How to Drop a Role
> use '<dbName>' > db.dropRole('<roleName>')
How to Grant or Revoke Privileges to/from a Role
These commands grant or revoke privileges to a user-defined role.
> use '<dbName>' > db.grantPrivilegesToRole( '<roleName>', [ { resource : { db : '<dbName>', collection : '<collectionName'> }, actions : [ '<actionName>',... ] }, ... ] )
> db.revokePrivilegesFromRole( '<roleName>', [ { resource : { db : '<dbName>', collection : '<collectionName'> }, actions : [ '<actionName>',... ] }, … ] )
How to Grant or Revoke Roles to/from a Role
> use '<dbName>' > db.grantRolesToRole( '<roleName>', [ { role : '<roleName>', db : '<dbName>' } | <roles> ] )
> db.revokeRolesFromRole( '<roleName>', [ { role : '<roleName>', db : '<dbName>' } | <roles> ] )
How to Update a Role
Be careful! As the documentation says: “An update to the privileges or roles array completely replaces the previous array’s values”.
> use '<dbName>' > db.updateRole( '<roleName>', { privileges : [ { resource : { db : '<dbName>', collection : '<collectionName>' }, actions : [ '<actionName>' ] },... ], roles : [ { role : '<roleName>', db : '<dbName>' } | '<roleName>' ] } )