In Part 1 of this series, we stepped through the process to create MongoDB system users: admin, MMS monitoring agent, and MMS backup agent users. We now look at ways to create MongoDB application users – the accounts that our applications uses to authenticate with MongoDB.
Application isolation
For example, suppose we have two applications, app1
and app2
, that share the same MongoDB deployment (which might be a standalone, replica set, or sharded cluster). We want to ensure that each application can only read and write to its own set of data for integrity, privacy, and security reasons. So let’s use db.createUser() to assign the dbOwner role to the respective databases:
db = db.getSiblingDB('admin') // Remember to change the passwords from "change_me" to something more secure! db.createUser({ user: 'admin', pwd: 'change_me', roles: [ 'root' ] }) db.createUser({ user: 'app1', pwd: 'change_me', roles: [ { role: 'dbOwner', db: 'app1' ] }) db.createUser({ user: 'app2', pwd: 'change_me', roles: [ { role: 'dbOwner', db: 'app2' ] })
Let’s test that this works by spinning up a clean MongoDB instance in a terminal window:
$ mkdir ~/data $ mongod --dbpath ~/data
Now save the user creation script above to create-app-users-1.js
and execute in another terminal window:
$ mongo create-app-users-1.js
Restart MongoDB with authentication enabled by first terminating the current instance by pressing Ctrl-c
and running:
$ mongod --dbpath ~/data --auth
In another terminal window, connect via mongo shell without authentication and try to read/write the app1
database:
$ mongo MongoDB shell version: 2.6.4 connecting to: test > use app1 switched to db app1 > db.test.insert({a:1}) WriteResult({ "writeError" : { "code" : 13, "errmsg" : "not authorized on app1 to execute command { insert: \"test\", documents: [ { _id: ObjectId('5435dfa6e8ebb07a1eaa14a4'), a: 1.0 } ], ordered: true }" } }) > > db.test.find() error: { "$err" : "not authorized for query on app1.test", "code" : 13 } > > show collections 2014-10-09T02:07:12.055+0100 error: { "$err" : "not authorized for query on app1.system.namespaces", "code" : 13 } at src/mongo/shell/query.js:131 >
It fails as expected. Now repeat while authenticated as the app1
user:
$ mongo admin -u app1 -p change_me MongoDB shell version: 2.6.4 connecting to: admin > use app1 switched to db app1 > db.test.insert({a:1}) WriteResult({ "nInserted" : 1 }) > > db.test.find() { "_id" : ObjectId("5435defc147496da04af01ba"), "a" : 1 } > > show collections system.indexes test >
Works as expected. Now try the same thing with the app2
database while still authenticated as app1
:
> use app2 switched to db app2 > db.test.insert({a:1}) WriteResult({ "writeError" : { "code" : 13, "errmsg" : "not authorized on app2 to execute command { insert: \"test\", documents: [ { _id: ObjectId('5435e0e2e8ebb07a1eaa14a5'), a: 1.0 } ], ordered: true }" } }) > > db.test.find() error: { "$err" : "not authorized for query on app2.test", "code" : 13 } > > show collections 2014-10-09T02:12:11.698+0100 error: { "$err" : "not authorized for query on app2.system.namespaces", "code" : 13 } at src/mongo/shell/query.js:131 >
It fails as expected, so now we know our application users are set up properly.
Application user lockdown
Granting dbOwner
to the application user might be a bit too much for some. For example, not granting index creation permissions will help prevent accidentally foreground index creation, which locks the database until completion. It may even be desirable to whitelist collections that can be read and written to, in order protect against code typos or mistakenly dropping the wrong collections. Or perhaps allow only inserts to a log or audit collection to prevent tampering. To do so, we create a custom role and assign that to our new app3
user:
// See http://docs.mongodb.org/manual/reference/privilege-actions/ for the list // of actions and what they allow. // Allow db.stats(), db.<collection>.stats(), etc. on database db_actions = [ "dbStats", "collStats", "planCacheRead", "planCacheWrite" ] // Allow CRUD, profiler, etc. on collection collection_actions = [ "find", "insert", "update", "remove", "enableProfiler", "killCursors" ] db.createRole({ role: "app3", privileges: [ // Allow common cluster read-only actions like db.serverStatus(), show dbs, and db.currentOp() { resource: { cluster: true }, actions: [ "serverStatus", "listDatabases", "inprog" ] }, { resource: { db: "app3", collection: "" }, actions: db_actions }, { resource: { db: "app3", collection: "coll1" }, actions: collection_actions }, { resource: { db: "app3", collection: "coll2" }, actions: collection_actions }, // Only allow application to insert to the audit collection, to prevent any // possibility of tampering { resource: { db: "app3", collection: "audit" }, actions: [ "insert" ] }, // Allow db.<collection>.getIndexes() { resource: { db: "app3", collection: "system.indexes" }, actions: [ "find" ] }, // Allow 'show collections' { resource: { db: "app3", collection: "system.namespaces" }, actions: [ "find" ] }, ], roles: [ ] }) db.createUser({ user: "app3", pwd: "change_me", roles: [ "app3" ] })
Adapt the script according to your needs and refer the MongoDB documentation for details. In particular, see Built-in Roles, Privilege Actions, db.createUser(), and db.createRole().