mongo/jstests/auth/user_defined_roles_on_secon...

258 lines
9.3 KiB
JavaScript

/*
* Copyright (C) 2013 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
/**
* These tests verify that modifications to user-defined roles propagate to secondary nodes.
*
* First, it sets up a 1-node replicaset and adds some roles.
* Then, it adds a second node to the replicaset, and verifies that the roles sync correctly.
* Then, it runs a variety of operations on the primary, and ensures that they replicate correctly.
* @tags: [requires_replication]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {setLogVerbosity} from "jstests/replsets/rslib.js";
let name = "user_defined_roles_on_secondaries";
let m0;
function assertListContainsRole(list, role, msg) {
let i;
for (i = 0; i < list.length; ++i) {
if (list[i].role == role.role && list[i].db == role.db) return;
}
doassert("Could not find value " + tojson(role) + " in " + tojson(list) + (msg ? ": " + msg : ""));
}
//
// Create a 1-node replicaset and add two roles, inheriting the built-in read role on db1.
//
// read
// / \
// r1 r2
//
let rstest = new ReplSetTest({name: name, nodes: 1, nodeOptions: {}});
rstest.startSet();
rstest.initiate();
m0 = rstest.nodes[0];
m0.getDB("db1").createRole({
role: "r1",
roles: ["read"],
privileges: [{resource: {db: "db1", collection: "system.users"}, actions: ["find"]}],
});
m0.getDB("db1").createRole({
role: "r2",
roles: ["read"],
privileges: [{resource: {db: "db1", collection: "log"}, actions: ["insert"]}],
});
//
// Add a second node to the set, and add a third role, dependent on the first two.
//
// read
// / \
// r1 r2
// \ /
// r3
//
rstest.add();
rstest.reInitiate();
// Do a write after the node has completed initial sync.
rstest.awaitSecondaryNodes();
assert.commandWorked(
rstest
.getPrimary()
.getDB("db1")
["aCollection"].insert({a: "afterSecondNodeAdded"}, {writeConcern: {w: 2}}),
);
rstest
.getPrimary()
.getDB("db1")
.createRole(
{
role: "r3",
roles: ["r1", "r2"],
privileges: [{resource: {db: "db1", collection: "log"}, actions: ["update"]}],
},
{w: 2},
);
// Verify that both members of the set see the same role graph.
rstest.nodes.forEach(function (node) {
let role = node.getDB("db1").getRole("r3");
assert.eq(2, role.roles.length, tojson(node));
assertListContainsRole(role.roles, {role: "r1", db: "db1"}, node);
assertListContainsRole(role.roles, {role: "r2", db: "db1"}, node);
assert.eq(3, role.inheritedRoles.length, tojson(node));
assertListContainsRole(role.inheritedRoles, {role: "r1", db: "db1"}, node);
assertListContainsRole(role.inheritedRoles, {role: "r2", db: "db1"}, node);
assertListContainsRole(role.inheritedRoles, {role: "read", db: "db1"}, node);
});
// Verify that updating roles propagates.
rstest.getPrimary().getDB("db1").revokeRolesFromRole("r1", ["read"], {w: 2});
rstest.getPrimary().getDB("db1").grantRolesToRole("r1", ["dbAdmin"], {w: 2});
rstest.nodes.forEach(function (node) {
let role = node.getDB("db1").getRole("r1");
assert.eq(1, role.roles.length, tojson(node));
assertListContainsRole(role.roles, {role: "dbAdmin", db: "db1"});
});
setLogVerbosity(rstest.nodes, {"command": {"verbosity": 2}});
// Verify that dropping roles propagates.
jsTestLog("Dropping role r2");
assert.eq(true, rstest.getPrimary().getDB("db1").dropRole("r2", {w: 2}));
assert.eq(null, rstest.getPrimary().getDB("db1").getRole("r2"));
rstest.nodes.forEach(function (node) {
jsTestLog(`Testing node ${node}`);
const roles = assert.commandWorked(node.getDB("db1").runCommand({rolesInfo: 1}));
jsTestLog(`Roles: ${tojson(roles)}`);
assert.eq(null, node.getDB("db1").getRole("r2"));
let role = node.getDB("db1").getRole("r3");
assert.eq(1, role.roles.length, tojson(node));
assertListContainsRole(role.roles, {role: "r1", db: "db1"}, node);
assert.eq(2, role.inheritedRoles.length, tojson(node));
assertListContainsRole(role.inheritedRoles, {role: "r1", db: "db1"}, node);
assertListContainsRole(role.inheritedRoles, {role: "dbAdmin", db: "db1"}, node);
});
// Verify that applyOps commands propagate.
// NOTE: This section of the test depends on the oplog and roles schemas.
assert.commandWorked(
rstest
.getPrimary()
.getDB("admin")
.runCommand({
applyOps: [
{op: "c", ns: "admin.$cmd", o: {create: "system.roles"}},
{
op: "i",
ns: "admin.system.roles",
o: {
_id: "db1.s1",
role: "s1",
db: "db1",
roles: [{role: "read", db: "db1"}],
privileges: [{resource: {db: "db1", collection: "system.users"}, actions: ["find"]}],
},
},
{
op: "i",
ns: "admin.system.roles",
o: {
_id: "db1.s2",
role: "s2",
db: "db1",
roles: [{role: "read", db: "db1"}],
privileges: [{resource: {db: "db1", collection: "log"}, actions: ["insert"]}],
},
},
{op: "c", ns: "admin.$cmd", o: {drop: "system.roles"}},
{op: "c", ns: "admin.$cmd", o: {create: "system.roles"}},
{
op: "i",
ns: "admin.system.roles",
o: {
_id: "db1.t1",
role: "t1",
db: "db1",
roles: [{role: "read", db: "db1"}],
privileges: [{resource: {db: "db1", collection: "system.users"}, actions: ["find"]}],
},
},
{
op: "i",
ns: "admin.system.roles",
o: {
_id: "db1.t2",
role: "t2",
db: "db1",
roles: [],
privileges: [{resource: {db: "db1", collection: "log"}, actions: ["insert"]}],
},
},
{
op: "i",
ns: "admin.system.roles",
o: {
_id: "db1.t3",
role: "t3",
db: "db1",
roles: [
{role: "t1", db: "db1"},
{role: "t2", db: "db1"},
],
privileges: [],
},
},
{
op: "u",
ns: "admin.system.roles",
o: {$v: 2, diff: {u: {roles: [{role: "readWrite", db: "db1"}]}}},
o2: {_id: "db1.t2"},
},
],
}),
);
rstest.awaitReplication();
rstest.nodes.forEach(function (node) {
let role = node.getDB("db1").getRole("t1");
assert.eq(1, role.roles.length, tojson(node));
assertListContainsRole(role.roles, {role: "read", db: "db1"}, node);
role = node.getDB("db1").getRole("t2");
assert.eq(1, role.roles.length, tojson(node));
assertListContainsRole(role.roles, {role: "readWrite", db: "db1"}, node);
});
// Verify that irrelevant index creation doesn't impair graph resolution
assert.commandWorked(rstest.getPrimary().getDB("admin").col.save({data: 5}));
assert.commandWorked(
rstest
.getPrimary()
.getDB("admin")
.runCommand({createIndexes: "col", indexes: [{key: {data: 1}, name: "testIndex"}]}),
);
rstest.awaitReplication();
rstest.nodes.forEach(function (node) {
let role = node.getDB("db1").getRole("t3");
assert.eq(4, role.inheritedRoles.length, tojson(node));
});
rstest.stopSet();