diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2017-12-04 13:52:18 -0500 |
---|---|---|
committer | Sara Golemon <sara.golemon@mongodb.com> | 2017-12-05 19:07:59 -0500 |
commit | a34fa65325dafc01857a4525d0d8b2f26b485965 (patch) | |
tree | 33b77aab43c0ae285ac97468e1c715dc721bfc7a | |
parent | 3d7be48d3b09db2c7ac723043b10c014430e85ed (diff) | |
download | mongo-a34fa65325dafc01857a4525d0d8b2f26b485965.tar.gz |
SERVER-6898 Enable listDatabases for all users
-rw-r--r-- | jstests/auth/lib/commands_lib.js | 12 | ||||
-rw-r--r-- | jstests/auth/list_databases.js | 54 | ||||
-rw-r--r-- | src/mongo/db/commands/list_databases.cpp | 48 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_list_databases_cmd.cpp | 50 |
4 files changed, 123 insertions, 41 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js index 092856a4170..f79c527b2cf 100644 --- a/jstests/auth/lib/commands_lib.js +++ b/jstests/auth/lib/commands_lib.js @@ -3978,17 +3978,7 @@ var authCommandsLib = { testcases: [ { runOnDb: adminDbName, - roles: { - readAnyDatabase: 1, - readWriteAnyDatabase: 1, - dbAdminAnyDatabase: 1, - userAdminAnyDatabase: 1, - clusterMonitor: 1, - clusterAdmin: 1, - backup: 1, - root: 1, - __system: 1 - }, + roles: roles_all, privileges: [{resource: {cluster: true}, actions: ["listDatabases"]}] }, {runOnDb: firstDbName, roles: {}}, diff --git a/jstests/auth/list_databases.js b/jstests/auth/list_databases.js new file mode 100644 index 00000000000..5146328243d --- /dev/null +++ b/jstests/auth/list_databases.js @@ -0,0 +1,54 @@ +// Auth tests for the listDatabases command. + +(function() { + 'use strict'; + + const mongod = MongoRunner.runMongod({auth: ""}); + const admin = mongod.getDB('admin'); + + admin.createUser({user: 'admin', pwd: 'pass', roles: jsTest.adminUserRoles}); + assert(admin.auth('admin', 'pass')); + + // Establish db0..db7 + for (let i = 0; i < 8; ++i) { + mongod.getDB('db' + i).foo.insert({bar: "baz"}); + } + + // Make db0, db2, db4, db6 readable to user1 abd user3. + // Make db0, db1, db2, db3 read/writable to user 2 and user3. + function makeRole(perm, dbNum) { + return {role: perm, db: ("db" + dbNum)}; + } + const readEven = [0, 2, 4, 6].map(function(i) { + return makeRole("read", i); + }); + const readWriteLow = [0, 1, 2, 3].map(function(i) { + return makeRole("readWrite", i); + }); + admin.createUser({user: 'user1', pwd: 'pass', roles: readEven}); + admin.createUser({user: 'user2', pwd: 'pass', roles: readWriteLow}); + admin.createUser({user: 'user3', pwd: 'pass', roles: readEven.concat(readWriteLow)}); + admin.logout(); + + [{user: "user1", dbs: ["db0", "db2", "db4", "db6"]}, + {user: "user2", dbs: ["db0", "db1", "db2", "db3"]}, + {user: "user3", dbs: ["db0", "db1", "db2", "db3", "db4", "db6"]}, + { + user: "admin", + dbs: ["admin", "db0", "db1", "db2", "db3", "db4", "db5", "db6", "db7", "local"] + }, + ].forEach(function(test) { + admin.auth(test.user, 'pass'); + const dbs = assert.commandWorked(admin.runCommand({listDatabases: 1})); + assert.eq(dbs.databases + .map(function(db) { + return db.name; + }) + .sort(), + test.dbs, + test.user + " permissions"); + admin.logout(); + }); + + MongoRunner.stopMongod(mongod); +})(); diff --git a/src/mongo/db/commands/list_databases.cpp b/src/mongo/db/commands/list_databases.cpp index fb719e1bda6..15d1139df8b 100644 --- a/src/mongo/db/commands/list_databases.cpp +++ b/src/mongo/db/commands/list_databases.cpp @@ -28,6 +28,7 @@ * it in the license file. */ +#include "mongo/db/auth/authorization_session.h" #include "mongo/db/catalog/database.h" #include "mongo/db/catalog/database_catalog_entry.h" #include "mongo/db/catalog/database_holder.h" @@ -56,28 +57,32 @@ intmax_t dbSize(const string& database); class CmdListDatabases : public BasicCommand { public: - virtual bool slaveOk() const { + bool slaveOk() const final { return false; } - virtual bool slaveOverrideOk() const { + bool slaveOverrideOk() const final { return true; } - virtual bool adminOnly() const { + bool adminOnly() const final { return true; } - virtual bool supportsWriteConcern(const BSONObj& cmd) const override { + bool supportsWriteConcern(const BSONObj& cmd) const final { return false; } - virtual void help(stringstream& help) const { + void help(stringstream& help) const final { help << "{ listDatabases:1, [filter: <filterObject>] [, nameOnly: true ] }\n" "list databases on this server"; } - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) { - ActionSet actions; - actions.addAction(ActionType::listDatabases); - out->push_back(Privilege(ResourcePattern::forClusterResource(), actions)); + + /* listDatabases is always authorized, + * however the results returned will be redacted + * based on read privileges if auth is enabled + * and the current user does not have listDatabases permisison. + */ + Status checkAuthForCommand(Client* client, + const std::string& dbname, + const BSONObj& cmdObj) final { + return Status::OK(); } CmdListDatabases() : BasicCommand("listDatabases") {} @@ -85,7 +90,7 @@ public: bool run(OperationContext* opCtx, const string& dbname, const BSONObj& jsobj, - BSONObjBuilder& result) { + BSONObjBuilder& result) final { // Parse the filter. std::unique_ptr<MatchExpression> filter; if (auto filterElt = jsobj[kFilterField]) { @@ -118,12 +123,25 @@ public: vector<BSONObj> dbInfos; - bool filterNameOnly = filter && + // If we have ActionType::listDatabases, + // then we don't need to test each record in the output. + // Otherwise, we'll test the database names as we enumerate them. + const auto as = AuthorizationSession::get(opCtx->getClient()); + const bool checkAuth = as && + !as->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), + ActionType::listDatabases); + + const bool filterNameOnly = filter && filter->getCategory() == MatchExpression::MatchCategory::kLeaf && filter->path() == kNameField; intmax_t totalSize = 0; - for (vector<string>::iterator i = dbNames.begin(); i != dbNames.end(); ++i) { - const string& dbname = *i; + for (const auto& dbname : dbNames) { + if (checkAuth && as && + !as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::find)) { + // We don't have listDatabases on the cluser or find on this database. + continue; + } BSONObjBuilder b; b.append("name", dbname); diff --git a/src/mongo/s/commands/cluster_list_databases_cmd.cpp b/src/mongo/s/commands/cluster_list_databases_cmd.cpp index 84f2cdc2ed5..cc61afaa7e6 100644 --- a/src/mongo/s/commands/cluster_list_databases_cmd.cpp +++ b/src/mongo/s/commands/cluster_list_databases_cmd.cpp @@ -35,6 +35,7 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/client/read_preference.h" #include "mongo/client/remote_command_targeter.h" +#include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" #include "mongo/s/catalog/sharding_catalog_client.h" #include "mongo/s/client/shard.h" @@ -55,39 +56,43 @@ class ListDatabasesCmd : public BasicCommand { public: ListDatabasesCmd() : BasicCommand("listDatabases", "listdatabases") {} - virtual bool slaveOk() const { + bool slaveOk() const final { return true; } - virtual bool slaveOverrideOk() const { + bool slaveOverrideOk() const final { return true; } - virtual bool adminOnly() const { + bool adminOnly() const final { return true; } - virtual bool supportsWriteConcern(const BSONObj& cmd) const override { + bool supportsWriteConcern(const BSONObj& cmd) const final { return false; } - virtual void help(std::stringstream& help) const { + void help(std::stringstream& help) const final { help << "list databases in a cluster"; } - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) { - ActionSet actions; - actions.addAction(ActionType::listDatabases); - out->push_back(Privilege(ResourcePattern::forClusterResource(), actions)); + /* listDatabases is always authorized, + * however the results returned will be redacted + * based on read privileges if auth is enabled + * and the current user does not have listDatabases permisison. + */ + Status checkAuthForCommand(Client* client, + const std::string& dbname, + const BSONObj& cmdObj) final { + return Status::OK(); } - virtual bool run(OperationContext* opCtx, - const std::string& dbname_unused, - const BSONObj& cmdObj, - BSONObjBuilder& result) { + + bool run(OperationContext* opCtx, + const std::string& dbname_unused, + const BSONObj& cmdObj, + BSONObjBuilder& result) final { const bool nameOnly = cmdObj["nameOnly"].trueValue(); map<string, long long> sizes; @@ -151,6 +156,14 @@ public: } } + // If we have ActionType::listDatabases, + // then we don't need to test each record in the output. + // Otherwise, we'll test the database names as we enumerate them. + const auto as = AuthorizationSession::get(opCtx->getClient()); + const bool checkAuth = as && + !as->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), + ActionType::listDatabases); + // Now that we have aggregated results for all the shards, convert to a response, // and compute total sizes. long long totalSize = 0; @@ -164,6 +177,13 @@ public: continue; } + if (checkAuth && as && + !as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(name), + ActionType::find)) { + // We don't have listDatabases on the cluser or find on this database. + continue; + } + long long size = i->second; BSONObjBuilder temp; |