/** * 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();