How to Secure MongoDB Data Access with Views
Rate this article
Sometimes, MongoDB collections contain sensitive information that require access control. Using the Role-Based Access Control (RBAC) provided by MongoDB, it's easy to restrict access to this collection.
But what if you want to share your collection to a wider audience without exposing sensitive data?
For example, it could be interesting to share your collections with the marketing team for analytics purposes without sharing personal identifiable information (PII) or data you prefer to keep private, like employee salaries.
It's possible to achieve this result with MongoDB views combined with the MongoDB RBAC, and this is what we are going to explore in this blog post.
You'll need either:
- A MongoDB cluster with authentication activated (which is somewhat recommended in production!).
- A MongoDB Atlas cluster.
I'll assume you already have an admin user on your cluster with full authorizations or at least a user that can create views, custom roles. and users. If you are in Atlas, you can create this user in the
Database Access
tab or use the MongoDB Shell, like this:1 mongosh "mongodb://localhost/admin" --quiet --eval "db.createUser({'user': 'root', 'pwd': 'root', 'roles': ['root']});"
1 mongosh "mongodb://localhost" --quiet -u root -p root
In this example, I'll pretend to have an
employees
collection with sensitive data:1 db.employees.insertMany( 2 [ 3 { 4 _id: 1, 5 firstname: 'Scott', 6 lastname: 'Snyder', 7 age: 21, 8 ssn: '351-40-7153', 9 salary: 100000 10 }, 11 { 12 _id: 2, 13 firstname: 'Patricia', 14 lastname: 'Hanna', 15 age: 57, 16 ssn: '426-57-8180', 17 salary: 95000 18 }, 19 { 20 _id: 3, 21 firstname: 'Michelle', 22 lastname: 'Blair', 23 age: 61, 24 ssn: '399-04-0314', 25 salary: 71000 26 }, 27 { 28 _id: 4, 29 firstname: 'Benjamin', 30 lastname: 'Roberts', 31 age: 46, 32 ssn: '712-13-9307', 33 salary: 60000 34 }, 35 { 36 _id: 5, 37 firstname: 'Nicholas', 38 lastname: 'Parker', 39 age: 69, 40 ssn: '320-25-5610', 41 salary: 81000 42 } 43 ] 44 )
Now I want to share this collection to a wider audience, but I don’t want to share the social security numbers and salaries.
1 db.createView('employees_view', 'employees', [{$project: {firstname: 1, lastname: 1, age: 1}}])
Note that I'm not doing
{$project: {ssn: 0, salary: 0}}
because every field except these two would appear in the view.
It works today, but maybe tomorrow, I'll add a credit_card
field in some documents. It would then appear instantly in the view.Let's confirm that the view works:
1 db.employees_view.find()
Results:
1 [ 2 { _id: 1, firstname: 'Scott', lastname: 'Snyder', age: 21 }, 3 { _id: 2, firstname: 'Patricia', lastname: 'Hanna', age: 57 }, 4 { _id: 3, firstname: 'Michelle', lastname: 'Blair', age: 61 }, 5 { _id: 4, firstname: 'Benjamin', lastname: 'Roberts', age: 46 }, 6 { _id: 5, firstname: 'Nicholas', lastname: 'Parker', age: 69 } 7 ]
Depending on your schema design and how you want to filter the fields, it could be easier to use $unset instead of $project. You can learn more in the Practical MongoDB Aggregations Book. But again,
$unset
will just remove the specified fields without filtering new fields that could be added in the future.Now that we have our view, we can share this with restricted access rights. In MongoDB, we need to create a custom role to achieve this.
Here are the command lines if you are not in Atlas.
1 use admin 2 db.createRole( 3 { 4 role: "view_access", 5 privileges: [ 6 {resource: {db: "test", collection: "employees_view"}, actions: ["find"]} 7 ], 8 roles: [] 9 } 10 )
Then we can create the user:
1 use admin 2 db.createUser({user: 'view_user', pwd: '123', roles: ["view_access"]})
If you are in Atlas, database access is managed directly in the Atlas website in the
Database Access
tab. You can also use the Atlas CLI if you feel like it.Then you need to create a custom role.
Note: In Step 2, I only selected the Collection Actions > Query and Write Actions > find option.
Now that your role is created, head back to the
Database Users
tab and create a user with this custom role.Now that our user is created, we can confirm that this new restricted user doesn't have access to the underlying collection but has access to the view.
1 $ mongosh "mongodb+srv://hidingfields.as3qc0s.mongodb.net/test" --apiVersion 1 --username view_user --quiet 2 Enter password: *** 3 Atlas atlas-odym8f-shard-0 [primary] test> db.employees.find() 4 MongoServerError: user is not allowed to do action [find] on [test.employees] 5 Atlas atlas-odym8f-shard-0 [primary] test> db.employees_view.find() 6 [ 7 { _id: 1, firstname: 'Scott', lastname: 'Snyder', age: 21 }, 8 { _id: 2, firstname: 'Patricia', lastname: 'Hanna', age: 57 }, 9 { _id: 3, firstname: 'Michelle', lastname: 'Blair', age: 61 }, 10 { _id: 4, firstname: 'Benjamin', lastname: 'Roberts', age: 46 }, 11 { _id: 5, firstname: 'Nicholas', lastname: 'Parker', age: 69 } 12 ]
In this blog post, you learned how to share your MongoDB collections to a wider audience — even the most critical ones — without exposing sensitive data.
Note that views can use the indexes from the source collection so your restricted user can leverage those for more advanced queries.
You could also choose to add an extra
$match
stage before your $project stage to filter entire documents from ever appearing in the view. You can see an example in the Practical MongoDB Aggregations Book. And don't forget to support the $match
with an index!