mirror of https://github.com/mongodb/mongo
129 lines
5.4 KiB
JavaScript
129 lines
5.4 KiB
JavaScript
/**
|
|
* Test that a sharded explain command with a value set for the lsid's uid field in the inner
|
|
* command invocation will ignore the lsid field and succeed so long as the user is authorized to
|
|
* run the command being explained. Also tests that a user can only set a lsid + uid in the the
|
|
* top-level explain command if they are authorized to do so.
|
|
*
|
|
* @tags: [requires_sharding]
|
|
*/
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
|
|
const st = new ShardingTest({keyFile: "jstests/libs/key1", shards: {rs0: {nodes: 2}}});
|
|
const mongosConn = st.s;
|
|
|
|
// Setup: create user roles.
|
|
const adminDB = mongosConn.getDB("admin");
|
|
adminDB.createUser({user: "admin", pwd: "admin", roles: ["root"]});
|
|
adminDB.auth("admin", "admin");
|
|
|
|
const testDB = mongosConn.getDB("test");
|
|
testDB.test.insert({a: 1});
|
|
|
|
testDB.createUser({user: "readOnlyUser", pwd: "user", roles: jsTest.readOnlyUserRoles});
|
|
testDB.createUser({user: "readWriteUser", pwd: "user", roles: jsTest.basicUserRoles});
|
|
adminDB.createRole({
|
|
role: "impersonateRole",
|
|
roles: [],
|
|
privileges: [
|
|
{resource: {db: "test", collection: "test"}, actions: ["find", "update"]},
|
|
{resource: {cluster: true}, actions: ["impersonate"]},
|
|
],
|
|
});
|
|
adminDB.createUser({user: "impersonateUser", pwd: "user", roles: ["impersonateRole"]});
|
|
adminDB.logout();
|
|
|
|
// LSID helper functions.
|
|
const cmdLsid = UUID();
|
|
const invalidUid = computeSHA256Block("abc");
|
|
const getUserUid = function () {
|
|
const user = adminDB.runCommand({connectionStatus: 1}).authInfo.authenticatedUsers[0];
|
|
return user ? computeSHA256Block(user.user + "@" + user.db) : computeSHA256Block("");
|
|
};
|
|
|
|
const createLsidObj = (uid) => ({lsid: {id: cmdLsid, uid}});
|
|
const commandWithLsid = (cmd, lsidObj, isOuter = false) => {
|
|
return isOuter ? {explain: cmd, ...lsidObj} : {explain: {...cmd, ...lsidObj}};
|
|
};
|
|
|
|
// Explain command helper and runners.
|
|
const assertExplain = (cmd, uid, shouldFail = false, isOuter = false) => {
|
|
const lsidObj = createLsidObj(uid);
|
|
const command = commandWithLsid(cmd, lsidObj, isOuter);
|
|
|
|
shouldFail
|
|
? assert.commandFailedWithCode(testDB.runCommand(command), ErrorCodes.Unauthorized)
|
|
: assert.commandWorked(testDB.runCommand(command));
|
|
};
|
|
|
|
const assertInnerLsidExplainIsUnauthorized = (cmd, uid) => assertExplain(cmd, uid, true);
|
|
const assertInnerLsidExplainWorked = (cmd, uid) => assertExplain(cmd, uid, false);
|
|
const assertTopLevelLsidExplainIsUnauthorized = (cmd, uid) => assertExplain(cmd, uid, true, true);
|
|
const assertTopLevelLsidExplainWorked = (cmd, uid) => assertExplain(cmd, uid, false, true);
|
|
|
|
const runCommandWithLsidTest = (isAuthZforInnerCmd, canSetTopLevelLsid, cmd, invalidUid, userUid) => {
|
|
// A user can put any arbitrary lsid/generic argument in the inner command invocation, as
|
|
// this will be pruned when the command is wrapped into an explain command. The explain will
|
|
// succeed so long as the user is authorized to run the command being explained. A user can
|
|
// only specify an lsid in the top-level explain command if they are authorized to do so, or
|
|
// if the uid in the lsid is the same as the user digest in the opCtx. The explain command
|
|
// will succeed if the user is authorized to run the command being explained.
|
|
if (isAuthZforInnerCmd) {
|
|
assertInnerLsidExplainWorked(cmd, invalidUid);
|
|
assertInnerLsidExplainWorked(cmd, userUid);
|
|
assertTopLevelLsidExplainWorked(cmd, userUid);
|
|
} else {
|
|
assertInnerLsidExplainIsUnauthorized(cmd, invalidUid);
|
|
assertInnerLsidExplainIsUnauthorized(cmd, userUid);
|
|
assertTopLevelLsidExplainIsUnauthorized(cmd, userUid);
|
|
}
|
|
|
|
canSetTopLevelLsid
|
|
? assertTopLevelLsidExplainWorked(cmd, invalidUid)
|
|
: assertTopLevelLsidExplainIsUnauthorized(cmd, invalidUid);
|
|
};
|
|
|
|
// Commands to run explain on.
|
|
const commands = [
|
|
{cmd: {distinct: "test", key: "_id"}, isWrite: false},
|
|
{cmd: {count: "test"}, isWrite: false},
|
|
{cmd: {find: "test", filter: {a: 1}}, isWrite: false},
|
|
{
|
|
cmd: {findAndModify: "test", query: {a: 1}, update: {$set: {b: 2}}, "new": true},
|
|
isWrite: true,
|
|
},
|
|
{cmd: {update: "test", updates: [{q: {a: 1}, u: {c: "foo"}}]}, isWrite: true},
|
|
];
|
|
|
|
// A user is only authorized to run an explain on a command if they:
|
|
// 1. Are authorized to run the command being explained on the associated resource.
|
|
// 2. Are authorized to set any generic arguments attached to the top-level invocation of the
|
|
// explain. In particular, a user can only set a different value in the uid field of an lsid if they
|
|
// have the "impersonate" role. If a user specifies the same uid value as their user digest, the
|
|
// command will succeed if (1) is true.
|
|
const users = [
|
|
{user: "readOnlyUser", pwd: "user", authDB: testDB, canWrite: false, canSetTopLevelLsid: false},
|
|
{user: "readWriteUser", pwd: "user", authDB: testDB, canWrite: true, canSetTopLevelLsid: false},
|
|
{
|
|
user: "impersonateUser",
|
|
pwd: "user",
|
|
authDB: adminDB,
|
|
canWrite: true,
|
|
canSetTopLevelLsid: true,
|
|
},
|
|
{user: "admin", pwd: "admin", authDB: adminDB, canWrite: true, canSetTopLevelLsid: false},
|
|
];
|
|
|
|
users.forEach(({user, pwd, authDB, canWrite, canSetTopLevelLsid}) => {
|
|
authDB.auth(user, pwd);
|
|
const userUid = getUserUid();
|
|
|
|
commands.forEach(({cmd, isWrite}) => {
|
|
const isAuthZforInnerCmd = canWrite || !isWrite;
|
|
runCommandWithLsidTest(isAuthZforInnerCmd, canSetTopLevelLsid, cmd, invalidUid, userUid);
|
|
});
|
|
|
|
authDB.logout();
|
|
});
|
|
|
|
st.stop();
|