Introduction
Having answered numerous questions on the new and old authori(s|z)ation within MongoDB, I thought I'd write a short blog post explaining how things work as there seems to be some confusion.What's New
Prior to version 2.4, there was a very basic sense of "Role Based Access Controls" (RBAC) within MongoDB as there were only two roles -- read
- readWrite
Version 2.4 added in the following 3 core roles -
- userAdmin
- dbAdmin
- clusterAdmin
- readAnyDatabase
- readWriteAnyDatabase
- userAdminAnyDatabase
- dbAdminAnyDatabase
In summary,
- userAdmin - is used to add/remove users and give permisions. This role won't be used that often but is clearly very powerful. This removes the capability of the "readWrite" user to view and modify the system.users collection.
- dbAdmin - this user can perform administrative operations (across a single database), such as compacting a collection, create/remove indexes, create/remove collections, drop the database etc. You probably will use this role quite a bit.
- clusterAdmin - this user performs various administration operations across a whole system rather than just a single database. For example, various replica set modification commands, listing of databases, enabling sharding etc.
- I'm assuming that read and readWrite are self-explanatory.
Validation
As of version 2.4.3, it is possible to add a user with an incorrect spelling of the role. However, the operation is effectively a "no-op" because MongoDB does not verify that the role is valid. For example -> use admin > db.system.users.find() { "_id" : ObjectId("
ed10d6c6761b1"), "user" : "userA", "pwd" : "1df873723e72b2323af3a52 f40833cdc", "roles" : [ "userAdminAnyDatabase", "clusterAdmin", "reader" ] } { "_id" : ObjectId("
48311be63b9
48311be63b9ed10
d6c6761b2"), "user" : "userB", "pwd" : "7pqr2f0bc30c4c78f4f5ff2 3986129ac", "roles" : [ "readWriteAnyDatabase", "clusteradmin", "reader" ] }
where "userA" has an incorrect role of reader and "userB" has two incorrect roles of clusteradmin and reader.
Validation and checking of "real" user roles will come later in 2.4 as per SERVER-9446.
How Does the New Authori(s|z)ation Work?
To explain this, I'm actually going to move using "local" authentication to "external" authentication, however, the actual authentication mechanism is irrelevant. At present, MongoDB only supports Kerberos as an external authentication method and it's only available in the Enterprise edition.Therefore, ignoring the majority of the Kerberos workings (I will discuss that in a later blog post), below the user has already connected to the server with the mongo shell and at first, switches to "$external" (not a database but more of an artifact), before authenticating using the Kerberos name (i.e. username plus REALM) -
> use $external switched to db $external
>
db.auth( { mechanism : "GSSAPI" , user: "mongouser@REALM.10GEN.ME" } ) 1
Let's try to add a user with the db.addUser() function without switching to the correct database (admin) -
>db.addUser( { "roles" : [ "readWriteAnyDatabase", "userAdminAnyDatabase", "dbAdminAnyDatabase", "clusterAdmin" ], "user" : "userA@REALM.10GEN.ME", "userSource" : "$external" } )
{
"roles" : [
"readWriteAnyDatabase",
"dbAdminAnyDatabase",
"clusterAdmin"
],
"user" : "userA@REALM.10GEN.ME",
"userSource" : "$external",
"_id" : ObjectId("518386c0ae6f8072686272bb")
}
Wed Feb 27 12:17:37.912 couldn't add user: cannot insert into reserved $ collection src/mongo/shell/db.js:128
As expected (even with userAdminAnyDatabase permissions) it is not possible to add users to the $external artifact. Therefore, switching to admin and adding user "userA" -
realm:PRIMARY
>
use admin switched to db admin realm:PRIMARY
>
db.addUser( { "roles" : [ "readWriteAnyDatabase", "dbAdminAnyDatabase", "clusterAdmin" ], "user" : "userA@REALM.10GEN.ME", "userSource" : "$external" } ) { "roles" : [ "readWriteAnyDatabase", "dbAdminAnyDatabase", "clusterAdmin" ], "user" : "userA@REALM.10GEN.ME", "userSource" : "$external", "_id" : ObjectId("518386ccae6f8072686272bc") }
Still within the admin database, we can see that the user "userA" exists and authenticates from an external source.
realm:PRIMARY> db.system.users.find() { "_id" : ObjectId("
"), "roles" : [ "readWriteAnyDatabase",
512e3fc63a749d1baf9cd1f7
"dbAdminAnyDatabase", "clusterAdmin" ], "user" : "userA@REALM.10GEN.ME", "userSource" : "$external" }
Authori(s|z)ation in Sharded Cluster
Unsurprisingly this causes confusion with MongoDB users and actually, even within 10gen staff (you can't know every single element of a database, right :p ).When you create users through mongos (as recommended), the users for the admin and config databases are stored on the config servers (in their respective system.users collections). The users for other databases are stored in the system.users collections for each database and these credentials are stored on the primary shard (for that sharded database). Every replica set should have a user in the replica set's admin database for performing administrative actions and this admin.system.users collection is obviously unsharded.
So here's a quick example (plagiarised heavily from Spencer -
Consider a sharded cluster with 3 shards:
- shardA
- shardB
- shardC.
- dbA
- dbB
- dbC
- shardA is the primary shard for dbA
- shardB is dbB's primary shard
- and shardC is dbC's primary shard
- 1 ("clusterAdminUser") in the admin database
- and 1 in each of dbA, dbB and dbC - userA, userB and userC respectively.
Now, let's say you make a direct connection to shardA. You will see and be able to authenticate as userA on database dbA. None of the other users will be visible, so you will not be able to authenticate as the clusterAdminUser, userB, or userC.
- If you make a connection to shardA from a different machine, then you will not be authori(s|z)ed to perform any actions at all, unless you authenticate as userA, in which case you will be able to access dbA, but no other databases.
- If you connect to shardA from a localhost connection, however, since that shard doesn't have any users in the admin database, localhost connections will be given full access - see localhost auth exception for more information on that. As a result, any connection from localhost to any of the shards will be able to access any information on that shard, in any database. To prevent this, you can do one of 2 things.
- In 2.4, you can set the enableLocalhostAuthBypass parameter to false. In both 2.4 and pre-2.4 versions, you can add an admin user directly to the shard. It is important to note that once you add a user to the admin database, every connection must authenticate (in order to have access), even those from localhost connections. That user can then be authenticated to so that an administrator making a direct connection to the shard can have authori(s|z)ation to that shard (remember that access to the shard can obviously also be controlled by a firewall, such as iptables). Remember also that each shard's admin database users will be completely distinct from each other, and from the cluster's admin database users.
Some Things to Note
Multiple Sessions
To check who you are logged in as run
> db.runCommand({connectionStatus: 1})
which should return something like{
"authInfo" : {
"authenticatedUsers" : [
{
"user" : "mongouser@REALM.10GEN.ME",
"userSource" : "$external"
}
]
},
"ok" : 1
}
It is worth emphasising that in MongoDB, authenticating as another user does not close the original user session but simply appends the new authentication. Therefore, consider two users "read" and "readWrite". User "read" is logged in under the foo database, with "read" permissions and user "readWrite" is logged in under the admin database with "readWriteAnyDatabase" permissions.
If you run the `connectionStatus` command, you can see under the field authenticatedUsers, there are two users listed -
> db.runCommand({connectionStatus: 1})
"authInfo": {
"authenticatedUsers": [{
"user": "readWrite",
"userSource": "admin"
}, {
"user": "read",
"userSource": "foo"
}]
},
"ok": 1
}
The MongoDB server does not replace the current user session with a new one, instead the new session is appended. If you have two users with different permissions for the same database, than the user that authenticates last overwrites the authenticated session of the first user. This enforces only one authenticated user per database. Say -
> use test
switched to db test
> db.auth('readWrite', 'a')
1
> db.auth('read', 'a')
1
> db.runCommand({ connectionStatus: 1 })
{
"authInfo": {
"authenticatedUsers": [{
"user": "readWrite",
"userSource": "admin"
}, {
"user": "read",
"userSource": "foo"
}]
},
"ok": 1
}
Run the db.logout() command to logout, ensuring you are in the correct database.
To verify a successful logout, run the connectionStatus command again:
> db.runCommand({ connectionStatus: 1 })
{ "authInfo" : { "authenticatedUsers" : [ ] }, "ok" : 1 }
rs.conf()
To run this command, you actually only need read permissions on the local database.
Stopping the Balancer
Unfortunately stopping/starting the balancer is not done by a command but by updating the config.settings collection in config database, i.e.
>
sh.stopBalancer()
results in the following update
mongos> db.settings.update({"_id" : "balancer"}, {"$set" : {"stopped" : true }}, true)
As a result, the user must have readWrite permissions on that database. Therefore, for an administrator to control the various aspects of replication and sharding with a MongoDB cluster, the administrator user will require permissions of readWrite on the config database and clusterAdmin in the admin database.
listDatabases
In order to run "show dbs", i.e. to list the databases, you need to have clusterAdmin permissions. The example below, shows this as well as few other things.
> use admin
switched to db admin
> db.auth( "readWrite" , "a" )
1
> db.system.users.find()
error: { "$err" : "not authorized for query on admin.system.users", "code" : 16549 }
> db.addUser( { user :"userAdmin", pwd : "a", "roles" : [ "userAdminAnyDatabase" ] } )
{
"user" : "userAdmin",
"pwd" : "abe283ad980a8b483d3cc9925fe0b20f",
"roles" : [
"userAdminAnyDatabase"
],
"_id" : ObjectId("51839706ac2cdde5df6ba08d")
}
Fri May 3 06:52:54.265 JavaScript execution failed: couldn't add user: not authorized for insert on admin.system.users at src/mongo/shell/db.js:L128
> db.runCommand( { "connectionStatus" : 1 } )
{
"authInfo" : {
"authenticatedUsers" : [
{
"user" : "readWrite",
"userSource" : "admin"
}
]
},
"ok" : 1
}
> show dbs
Fri May 3 06:39:10.174 JavaScript execution failed: listDatabases failed:{
"note" : "not authorized for command: listDatabases on database admin",
"ok" : 0,
"errmsg" : "unauthorized"
} at src/mongo/shell/mongo.js:L46
> use $external switched to db $external > db.auth({ mechanism: "GSSAPI", user: "mongouser@REALM.10GEN.ME" }) 1 mongos> show dbs $SERVER (empty) $external (empty) accounts 0.125GB admin 0.046875GB config 0.046875GB db1 0.0625GB foo 0.0625GB fred 0.0625GB mark 0.0625GB test 0.0625GB test1 0.0625GB test2 0.0625GB twitter 0.25GB
FYI
Please note that these new role access controls are available in all versions and not restricted to the Enterprise editions.Further Reading
- My "Securing MongoDB" presentation from #MongoDBdays can be found here.
- The official MongoDB documentation for "User privileges" can be found here. This documentation goes through each of the possible roles within MongoDB, the commands that those roles can execute and
- The official MongoDB documentation for "Privilege Documents", which store user credentials and role information, can be found here.
- Some useful tutorials on administrating users etc can be found here.
This post went on longer that it should have, doh :( Hopefully, however, it explains how authori(s|z)ation now works and how it is much more extensive than in previous versions.
Please let me know if there are any errors (it was a little rushed) and I hope to do a post explaining Kerberos authentication within MongoDB before I leave 10gen.
Comments
Post a Comment