diff options
author | Spencer Jackson <spencer.jackson@mongodb.com> | 2016-11-09 19:09:33 -0500 |
---|---|---|
committer | Spencer Jackson <spencer.jackson@mongodb.com> | 2016-11-14 17:31:05 -0500 |
commit | b8e6d89476d421a43c706818d397c913fe3e8921 (patch) | |
tree | 44d5ec17347b0539cae956a58492dd5a57386ed7 | |
parent | 4f43174cf45b1319917ecb8d4a4cce65c8029adc (diff) | |
download | mongo-b8e6d89476d421a43c706818d397c913fe3e8921.tar.gz |
SERVER-26839: Improve readWriteDatabase role coverage
-rw-r--r-- | jstests/auth/commands_builtin_roles.js | 10 | ||||
-rw-r--r-- | jstests/auth/commands_user_defined_roles.js | 19 | ||||
-rw-r--r-- | jstests/auth/lib/commands_lib.js | 362 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/auth/privilege_parser.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/auth/role_graph_builtin_roles.cpp | 48 |
6 files changed, 418 insertions, 37 deletions
diff --git a/jstests/auth/commands_builtin_roles.js b/jstests/auth/commands_builtin_roles.js index 2fb65de8663..9708c08e868 100644 --- a/jstests/auth/commands_builtin_roles.js +++ b/jstests/auth/commands_builtin_roles.js @@ -11,8 +11,10 @@ load("jstests/auth/lib/commands_lib.js"); var roles = [ {key: "read", role: "read", dbname: firstDbName}, + {key: "readLocal", role: {role: "read", db: "local"}, dbname: adminDbName}, {key: "readAnyDatabase", role: "readAnyDatabase", dbname: adminDbName}, {key: "readWrite", role: "readWrite", dbname: firstDbName}, + {key: "readWriteLocal", role: {role: "readWrite", db: "local"}, dbname: adminDbName}, {key: "readWriteAnyDatabase", role: "readWriteAnyDatabase", dbname: adminDbName}, {key: "userAdmin", role: "userAdmin", dbname: firstDbName}, {key: "userAdminAnyDatabase", role: "userAdminAnyDatabase", dbname: adminDbName}, @@ -47,10 +49,10 @@ function testProperAuthorization(conn, t, testcase, r) { var runOnDb = conn.getDB(testcase.runOnDb); authCommandsLib.setup(conn, t, runOnDb); - assert(r.db.auth("user|" + r.role, "password")); + assert(r.db.auth("user|" + r.key, "password")); var res = runOnDb.runCommand(t.command); - if (testcase.roles[r.role]) { + if (testcase.roles[r.key]) { if (res.ok == 0 && res.code == authErrCode) { out = "expected authorization success" + " but received " + tojson(res) + " on db " + testcase.runOnDb + " with role " + r.key; @@ -104,7 +106,7 @@ function createUsers(conn) { for (var i = 0; i < roles.length; i++) { r = roles[i]; r.db = conn.getDB(r.dbname); - r.db.createUser({user: "user|" + r.role, pwd: "password", roles: [r.role]}); + r.db.createUser({user: "user|" + r.key, pwd: "password", roles: [r.role]}); } adminDb.logout(); } @@ -122,7 +124,7 @@ function checkForNonExistentRoles() { for (role in testcase.roles) { var roleExists = false; for (var k = 0; k < roles.length; k++) { - if (roles[k].role === role) { + if (roles[k].key === role) { roleExists = true; break; } diff --git a/jstests/auth/commands_user_defined_roles.js b/jstests/auth/commands_user_defined_roles.js index dd6abc7d252..e54c9340e42 100644 --- a/jstests/auth/commands_user_defined_roles.js +++ b/jstests/auth/commands_user_defined_roles.js @@ -123,13 +123,26 @@ function runOneTest(conn, t) { failures.push(t.testname + ": " + msg); } + var specialResource = function(resource) { + if (!resource) + return true; + + // Tests which use {db: "local", collection: "oplog.rs"} will not work with + // {db: "", collection: "oplog.rs"}. oplog.rs is special, and does not match with + // forDatabaseName or anyNormalResource ResourcePatterns. The same is true of + // oplog.$main, but oplog.$main is also an illegal collection name on any database + // other than local. The other collections checked for here in the local database have + // the same property as oplog.rs. + return !resource.db || !resource.collection || + resource.collection.startsWith("system.") || resource.db == "local"; + }; + // Test for proper authorization with the test case's privileges where non-system // collections are modified to be the empty string. msg = testProperAuthorization(conn, t, testcase, testcase.privileges.map(function(priv) { // Make a copy of the privilege so as not to modify the original array. var modifiedPrivilege = Object.extend({}, priv, true); - if (modifiedPrivilege.resource.collection && - !modifiedPrivilege.resource.collection.startsWith('system.')) { + if (modifiedPrivilege.resource.collection && !specialResource(priv.resource)) { modifiedPrivilege.resource.collection = ""; } return modifiedPrivilege; @@ -143,7 +156,7 @@ function runOneTest(conn, t) { msg = testProperAuthorization(conn, t, testcase, testcase.privileges.map(function(priv) { // Make a copy of the privilege so as not to modify the original array. var modifiedPrivilege = Object.extend({}, priv, true); - if (modifiedPrivilege.resource.db) { + if (!specialResource(priv.resource)) { modifiedPrivilege.resource.db = ""; } return modifiedPrivilege; diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js index daea8325abb..e58836ebf05 100644 --- a/jstests/auth/lib/commands_lib.js +++ b/jstests/auth/lib/commands_lib.js @@ -142,8 +142,10 @@ var roles_hostManager = {hostManager: 1, clusterAdmin: 1, root: 1, __system: 1}; var roles_clusterManager = {clusterManager: 1, clusterAdmin: 1, root: 1, __system: 1}; var roles_all = { read: 1, + readLocal: 1, readAnyDatabase: 1, readWrite: 1, + readWriteLocal: 1, readWriteAnyDatabase: 1, userAdmin: 1, userAdminAnyDatabase: 1, @@ -195,7 +197,7 @@ var authCommandsLib = { skipStandalone: true, testcases: [{ runOnDb: "config", - roles: Object.extend({readWriteAnyDatabase: 1}, roles_clusterManager) + roles: roles_clusterManager, }] }, @@ -2354,6 +2356,169 @@ var authCommandsLib = { ] }, { + testname: "find_config_changelog", + command: {find: "changelog"}, + testcases: [ + { + runOnDb: "config", + roles: { + "clusterAdmin": 1, + "clusterManager": 1, + "clusterMonitor": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: + [{resource: {db: "config", collection: "changelog"}, actions: ["find"]}] + }, + ] + }, + { + testname: "find_local_me", + skipSharded: true, + command: {find: "me"}, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterMonitor": 1, + "readLocal": 1, + "readWriteLocal": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: [{resource: {db: "local", collection: "me"}, actions: ["find"]}] + }, + ] + }, + { + testname: "find_oplog_main", + skipSharded: true, + command: {find: "oplog.$main"}, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterMonitor": 1, + "readLocal": 1, + "readWriteLocal": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: + [{resource: {db: "local", collection: "oplog.$main"}, actions: ["find"]}] + }, + ] + }, + { + testname: "find_oplog_rs", + skipSharded: true, + command: {find: "oplog.rs"}, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterMonitor": 1, + "readLocal": 1, + "readWriteLocal": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: [{resource: {db: "local", collection: "oplog.rs"}, actions: ["find"]}] + }, + ] + }, + { + testname: "find_replset_election", + skipSharded: true, + command: {find: "replset.election"}, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterMonitor": 1, + "readLocal": 1, + "readWriteLocal": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: + [{resource: {db: "local", collection: "replset.election"}, actions: ["find"]}] + }, + ] + }, + { + testname: "find_replset_minvalid", + skipSharded: true, + command: {find: "replset.minvalid"}, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterMonitor": 1, + "readLocal": 1, + "readWriteLocal": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: + [{resource: {db: "local", collection: "replset.minvalid"}, actions: ["find"]}] + }, + ] + }, + { + testname: "find_sources", + skipSharded: true, + command: {find: "sources"}, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterMonitor": 1, + "readLocal": 1, + "readWriteLocal": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: [{resource: {db: "local", collection: "sources"}, actions: ["find"]}] + }, + ] + }, + { + testname: "find_startup_log", + command: {find: "startup_log"}, + skipSharded: true, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterMonitor": 1, + "readLocal": 1, + "readWriteLocal": 1, + "backup": 1, + "root": 1, + "__system": 1 + }, + privileges: + [{resource: {db: "local", collection: "startup_log"}, actions: ["find"]}] + }, + ] + }, + { testname: "find_views", setup: function(db) { db.createView("view", "collection", [{$match: {}}]); @@ -2713,6 +2878,189 @@ var authCommandsLib = { ] }, { + testname: "insert", + command: {insert: "foo", documents: [{data: 5}]}, + testcases: [ + { + runOnDb: firstDbName, + roles: roles_write, + privileges: [{resource: {db: firstDbName, collection: "foo"}, actions: ["insert"]}], + }, + { + runOnDb: secondDbName, + roles: {"readWriteAnyDatabase": 1, "root": 1, "__system": 1, "restore": 1}, + privileges: + [{resource: {db: secondDbName, collection: "foo"}, actions: ["insert"]}], + } + + ] + }, + { + testname: "insert_config_changelog", + command: {insert: "changelog", documents: [{data: 5}]}, + testcases: [{ + runOnDb: "config", + roles: + {"clusterAdmin": 1, "clusterManager": 1, "root": 1, "__system": 1, "restore": 1}, + privileges: + [{resource: {db: "config", collection: "changelog"}, actions: ["insert"]}], + }] + }, + { + testname: "insert_me", + command: {insert: "me", documents: [{data: 5}]}, + skipSharded: true, + testcases: [{ + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterManager": 1, + "readWriteLocal": 1, + "root": 1, + "__system": 1, + "restore": 1 + }, + privileges: [{resource: {db: "local", collection: "me"}, actions: ["insert"]}], + }] + }, + /* Untestable, because insert to oplog.$main will always fail + { + testname: "insert_oplog_main", + command: {insert: "oplog.$main", documents: [{ts: Timestamp()}]}, + skipSharded: true, + setup: function(db) { + db.createCollection("oplog.$main", {capped: true, size: 10000}); + }, + teardown: function(db) { + db.oplog.$main.drop(); + }, + testcases: [ + { + runOnDb: "local", + roles: {"clusterAdmin": 1, "clusterMonitor": 1, "readWriteLocal": 1, "restore": 1, + "root": 1, "__system": 1}, + privileges: + [{resource: {db: "local", collection: "oplog.$main"}, actions: ["insert"]}], + }, + + ] + },*/ + { + testname: "insert_oplog_rs", + command: {insert: "oplog.rs", documents: [{ts: Timestamp()}]}, + skipSharded: true, + setup: function(db) { + db.createCollection("oplog.rs", {capped: true, size: 10000}); + }, + teardown: function(db) { + db.oplog.rs.drop(); + }, + testcases: [ + { + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterManager": 1, + "readWriteLocal": 1, + "restore": 1, + "root": 1, + "__system": 1 + }, + privileges: + [{resource: {db: "local", collection: "oplog.rs"}, actions: ["insert"]}], + }, + + ] + }, + + { + testname: "insert_replset_election", + command: {insert: "replset.election", documents: [{data: 5}]}, + skipSharded: true, + testcases: [{ + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterManager": 1, + "readWriteLocal": 1, + "root": 1, + "__system": 1, + "restore": 1 + }, + privileges: + [{resource: {db: "local", collection: "replset.election"}, actions: ["insert"]}], + } + + ] + }, + { + testname: "insert_replset_minvalid", + command: {insert: "replset.minvalid", documents: [{data: 5}]}, + skipSharded: true, + testcases: [{ + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterManager": 1, + "readWriteLocal": 1, + "root": 1, + "__system": 1, + "restore": 1 + }, + privileges: + [{resource: {db: "local", collection: "replset.minvalid"}, actions: ["insert"]}], + } + + ] + }, + { + testname: "insert_system_users", + command: {insert: "system.users", documents: [{data: 5}]}, + testcases: [ + { + runOnDb: "admin", + roles: {"root": 1, "__system": 1, "restore": 1}, + privileges: + [{resource: {db: "admin", collection: "system.users"}, actions: ["insert"]}], + }, + ] + }, + { + testname: "insert_sources", + command: {insert: "sources", documents: [{data: 5}]}, + skipSharded: true, + testcases: [{ + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterManager": 1, + "readWriteLocal": 1, + "root": 1, + "__system": 1, + "restore": 1 + }, + privileges: [{resource: {db: "local", collection: "sources"}, actions: ["insert"]}], + }] + }, + { + testname: "insert_startup_log", + command: {insert: "startup_log", documents: [{data: 5}]}, + skipSharded: true, + testcases: [{ + runOnDb: "local", + roles: { + "clusterAdmin": 1, + "clusterManager": 1, + "readWriteLocal": 1, + "root": 1, + "__system": 1, + "restore": 1 + }, + privileges: + [{resource: {db: "local", collection: "startup_log"}, actions: ["insert"]}], + }] + }, + { testname: "isMaster", command: {isMaster: 1}, testcases: [ @@ -3909,9 +4257,7 @@ var authCommandsLib = { testcases: [ { runOnDb: adminDbName, - // addShardToZone only checks that you can write to config.shards, - // that's why readWriteAnyDatabase passes. - roles: Object.extend({readWriteAnyDatabase: 1}, roles_clusterManager), + roles: roles_clusterManager, privileges: [{resource: {db: 'config', collection: 'shards'}, actions: ['update']}], }, ] @@ -3931,9 +4277,7 @@ var authCommandsLib = { testcases: [ { runOnDb: adminDbName, - // removeShardZone only checks that you can write to config.shards, - // that's why readWriteAnyDatabase passes. - roles: Object.extend({readWriteAnyDatabase: 1}, roles_clusterManager), + roles: roles_clusterManager, privileges: [ {resource: {db: 'config', collection: 'shards'}, actions: ['update']}, {resource: {db: 'config', collection: 'tags'}, actions: ['find']} @@ -3956,9 +4300,7 @@ var authCommandsLib = { testcases: [ { runOnDb: adminDbName, - // updateZoneKeyRange only checks that you can write on config.tags, - // that's why readWriteAnyDatabase passes. - roles: Object.extend({readWriteAnyDatabase: 1}, roles_clusterManager), + roles: roles_clusterManager, privileges: [ {resource: {db: 'config', collection: 'shards'}, actions: ['find']}, { diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index b140ca36dff..6acd07ac4e6 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -641,7 +641,12 @@ static int buildResourceSearchList(const ResourcePattern& target, resourceSearchList[size++] = ResourcePattern::forAnyResource(); if (target.isExactNamespacePattern()) { if (!target.ns().isSystem()) { - resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); + // Some databases should not be matchable with ResourcePattern::forAnyNormalResource. + // 'local' and 'config' are used to store special system collections, which user level + // administrators should not be able to manipulate. + if (target.ns().db() != "local" && target.ns().db() != "config") { + resourceSearchList[size++] = ResourcePattern::forAnyNormalResource(); + } resourceSearchList[size++] = ResourcePattern::forDatabaseName(target.ns().db()); } resourceSearchList[size++] = ResourcePattern::forCollectionName(target.ns().coll()); diff --git a/src/mongo/db/auth/privilege_parser.cpp b/src/mongo/db/auth/privilege_parser.cpp index e9781c818bc..42bae0fcb56 100644 --- a/src/mongo/db/auth/privilege_parser.cpp +++ b/src/mongo/db/auth/privilege_parser.cpp @@ -95,8 +95,13 @@ bool ParsedResource::isValid(std::string* errMsg) const { } if (isCollectionSet() && (!NamespaceString::validCollectionName(getCollection()) && !getCollection().empty())) { - *errMsg = stream() << getCollection() << " is not a valid collection name"; - return false; + // local.oplog.$main is a real collection that the server will create. But, collection + // names with a '$' character are illegal. We must make an exception for this collection + // here so we can grant users access to it. + if (!(getDb() == "local" && getCollection() == "oplog.$main")) { + *errMsg = stream() << getCollection() << " is not a valid collection name"; + return false; + } } return true; } diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp index a25dc14eed9..3e8dd382a50 100644 --- a/src/mongo/db/auth/role_graph_builtin_roles.cpp +++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp @@ -414,14 +414,17 @@ void addClusterMonitorPrivileges(PrivilegeVector* privileges) { Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forAnyNormalResource(), clusterMonitorRoleDatabaseActions)); - addReadOnlyDbPrivileges(privileges, "config"); Privilege::addPrivilegeToPrivilegeVector( privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("local.system.replset")), - ActionType::find)); + Privilege(ResourcePattern::forDatabaseName("config"), clusterMonitorRoleDatabaseActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forDatabaseName("local"), clusterMonitorRoleDatabaseActions)); + addReadOnlyDbPrivileges(privileges, "local"); + addReadOnlyDbPrivileges(privileges, "config"); Privilege::addPrivilegeToPrivilegeVector( privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("local.sources")), + Privilege(ResourcePattern::forExactNamespace(NamespaceString("local", "system.replset")), ActionType::find)); Privilege::addPrivilegeToPrivilegeVector( privileges, @@ -444,27 +447,25 @@ void addClusterManagerPrivileges(PrivilegeVector* privileges) { Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forAnyNormalResource(), clusterManagerRoleDatabaseActions)); - addReadOnlyDbPrivileges(privileges, "config"); - - ActionSet writeActions; - writeActions << ActionType::insert << ActionType::update << ActionType::remove; Privilege::addPrivilegeToPrivilegeVector( privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", "settings")), - writeActions)); + Privilege(ResourcePattern::forDatabaseName("config"), clusterManagerRoleDatabaseActions)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, + Privilege(ResourcePattern::forDatabaseName("local"), clusterManagerRoleDatabaseActions)); Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forExactNamespace(NamespaceString("local", "system.replset")), readRoleActions)); + addReadOnlyDbPrivileges(privileges, "config"); + + ActionSet writeActions; + writeActions << ActionType::insert << ActionType::update << ActionType::remove; Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", "tags")), - writeActions)); - // Primarily for zone commands + privileges, Privilege(ResourcePattern::forDatabaseName("config"), writeActions)); Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forExactNamespace(NamespaceString("config", "shards")), - writeActions)); + privileges, Privilege(ResourcePattern::forDatabaseName("local"), writeActions)); + // Fake collection used for setFeatureCompatibilityVersion permissions. Privilege::addPrivilegeToPrivilegeVector( privileges, @@ -498,6 +499,12 @@ void addBackupPrivileges(PrivilegeVector* privileges) { privileges, Privilege(ResourcePattern::forClusterResource(), clusterActions)); Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forDatabaseName("config"), ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forDatabaseName("local"), ActionType::find)); + + Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forCollectionName("system.indexes"), ActionType::find)); @@ -567,6 +574,12 @@ void addRestorePrivileges(PrivilegeVector* privileges) { Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forAnyResource(), ActionType::listCollections)); + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forDatabaseName("config"), actions)); + + Privilege::addPrivilegeToPrivilegeVector( + privileges, Privilege(ResourcePattern::forDatabaseName("local"), actions)); + // Privileges for user/role management Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forAnyNormalResource(), userAdminRoleActions)); @@ -624,6 +637,7 @@ void addRootRolePrivileges(PrivilegeVector* privileges) { addUserAdminAnyDbPrivileges(privileges); addDbAdminAnyDbPrivileges(privileges); addReadWriteAnyDbPrivileges(privileges); + addBackupPrivileges(privileges); addRestorePrivileges(privileges); Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forAnyResource(), ActionType::validate)); |