Search This Blog

Loading...

Friday, 3 May 2013

MongoDB Authori(s|z)ation

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
which is quite limited. For example, if the user has "readWrite", that user is essentially "root" and the user can add/remove users as well as inserting data into the database, i.e. there is no role segregation.

Version 2.4 added in the following 3 core roles -
  • userAdmin
  • dbAdmin
  • clusterAdmin
with a notable extension such that there are now 4 roles that apply across all databases -
  • readAnyDatabase
  • readWriteAnyDatabase
  • userAdminAnyDatabase
  • dbAdminAnyDatabase
This increased RBAC is a significant improvement from a security perspective in MongoDB. It is important to note that these 4 roles above (along with clusterAdmin) can only be defined in the admin database (yes, in 2.4, the admin database is still "special"). Therefore, there are total of 9 possible roles within MongoDB (in version 2.4).

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("48311be63b9ed10d6c6761b1"), "user" : "userA", "pwd" : "1df873723e72b2323af3a52
f40833cdc", "roles" : [ "userAdminAnyDatabase", "clusterAdmin", "reader" ] }
{ "_id" : ObjectId("48311be63b9ed10d6c6761b2"), "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("512e3fc63a749d1baf9cd1f7"), "roles" : [     "readWriteAnyDatabase",   "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.
You have 3 databases:
  • dbA
  • dbB
  • dbC
where
  • shardA is the primary shard for dbA
  • shardB is dbB's primary shard
  • and shardC is dbC's primary shard
When adding users in a sharded infrastructure, it is highly recommended to do this through a mongos (remember that connecting to a shard directly can cause many issues, such as inconsistent configurations and data). Therefore, consider the scenario where you connect to a mongos and create 4 users -
  • 1 ("clusterAdminUser") in the admin database
  • and 1 in each of dbA, dbB and dbCuserA, userB and userC respectively.
If you connect to mongos, you will be able to authenticate as any of the 4 afore-mentioned users.

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.
The first step after connecting is to authenticate as user readWrite. The user can view admin.system.users but cannot modify it as userAdmin permissions are required.
> 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
The user only has readWrite permissions and so the user is unable to list the databases on the MongoDB instance.
> 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
Then authenticate as the super-user mongouser, who has clusterAdmin privileges (as shown a few steps before). As a result, the databases can now be listed.

> 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.

No comments:

Post a Comment