mongodb

Enhance your organization security with MongoDB Views

By March 1, 2017 August 21st, 2019 No Comments

Best practice dictates that every database user should only be assigned a minimum set of privileges that on the one hand allows the user to fulfill their mission, and on the other hand minimises the impact of a security breach. For example: a database user used for reporting; it is not nessesary and doesn’t make sense to provide write access since read-only access is adequate.

MongoDB’s authorization model offers a rich set of roles and privileges that covers modern applications needs. In brief, the MongoDB authorization model works by granting roles to users; A role is a collection of privileges. A privilege consists of a resource and permitted actions pairs. A resource may be either a combination of collection(s), database(s), the entire cluster or the almighty anyResource (read more details about resources). An action defines the operations a user may perform on a resource (read more details about actions).

In the MongoDB authorization universe, a collection is the last frontier; An action can be assigned to a collection but not to a collection’s subset: aka documents. So, how do we restrict a database user’s access to a subset of documents within a collection? For example, let’s assume we have a collection that stores reporting data from more than one customer, or department, and there is a requirement that each customer/department should only have read access to its own data. Changing the schema to use one collection per customer/department, or handling the authorization at the application level are both acceptable solutions but there is also a third way: using MongoDB views.

A MongoDB view simplified is a server-side alias to a collection’s aggregation pipeline (read more details about views).

Let’s check a real world example that uses MongoDB views. Assuming that we have a collection “reports.reporting” with two fields:

cid: Customer unique identification number

d: Reporting Data

The document structure of the reports.reporting collection will look like:

> db.reporting.find()
{ "_id" : ObjectId("58b6b2b5f35037b39ced45b9"), "cid" : 2, "d" : "Customer with cid equal 2" }
{ "_id" : ObjectId("58b6b2bdf35037b39ced45ba"), "cid" : 1, "d" : "Customer with cid equal 1" }

Our goal is to restrict each customer’s read access to it’s own data (documents defined by the cid) using MongoDB views.

We are creating a view per customer. Since cid is the field that distinct’s each customer, we are going to use the $match operator followed by each customer’s cid.

> db.createView("view_cid_1", "reporting" , [ { $match: { "cid": 1 } } ])
> db.createView("view_cid_2", "reporting" , [ { $match: { "cid": 2 } } ])

viewcid1 returns documents where cid is equal to 1, and viewcid2 documents with cid equal to 2.

The next step is to create two database users, one for each customer, that provides read-only access to a specific view.

First we create the nessesary roles with the action “find” restricted to each customer view:

> db.createRole({role: "viewer_cid_1", privileges: [{resource: { db: "reports", collection: "view_cid_1" }, actions: [ "find"] }], roles:[]})

> db.createRole({role: "viewer_cid_2", privileges: [{resource: { db: "reports", collection: "view_cid_1" }, actions: [ "find"] }], roles:[]})

then we create one database user per customer and assign the appropriate role:

> db.createUser( { user: "user_cid_1", pwd: "strongpassword", customData: { cid: 1 }, roles: [ { role: "viewer_cid_1", db: "reports" }]})

> db.createUser( { user: "user_cid_2", pwd: "strongpassword", customData: { cid: 2 }, roles: [ { role: "viewer_cid_2", db: "reports" }]})

User usercid1can only query viewcid1. if usercid1 attempts to query reporting or viewcid2 it will get an unauthorized exeption.

> db.view_cid_2.find()
Error: error: {
    "ok" : 0,
    "errmsg" : "not authorized on reports to execute command { find: "view_cid_2", filter: {} }",
    "code" : 13,
    "codeName" : "Unauthorized"

Views introduced in MongoDB 3.4 and a list of limitations. It is important to remember that views are computed on demand during read operations and uses the indexes of the underlying collection. A frequently accessed view, not covered, or covered poorly by indexes may lead to performance degradation.

Operations that lists collections, such as db.getCollectionInfos() and db.getCollectionNames() include views in their outputsMongo so the following javascript may be handy as it prints all views for a database along with the linked collection and the view definition.

db.getCollectionInfos().forEach(function(view){if (view.type == 'view') print("View Name: "+view.name+" ,Collection Name: "+view.options.viewOn+" ,View Definition: "+ JSON.stringify(view.options.pipeline)+"")})

As always, the Datastores team is here to help you design and deploy views for your MongoDB instances. Contact us at: support@objectrocket.com, we’d love to hear from you!