mirror of https://github.com/mongodb/mongo
135 lines
5.3 KiB
JavaScript
135 lines
5.3 KiB
JavaScript
/**
|
|
* Test that the coordinateCommitTransaction command can only be run when
|
|
* authorized to do so.
|
|
*
|
|
*/
|
|
import {ReplSetTest} from "jstests/libs/replsettest.js";
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
|
|
const keyFile = "jstests/libs/key1";
|
|
const collName = "mycoll";
|
|
const id = UUID("eeeeeeee-eeee-1111-0000-eeeeeeeeeeee");
|
|
|
|
/**
|
|
* userConnection represent the connection that a user has to the cluster.
|
|
* directConnection represents the connection the malicious user has, which
|
|
* is directly to a shard in a sharded cluster.
|
|
* Both connections are the same when run on a replica set.
|
|
*/
|
|
function testCoordinateCommitTransactionFails(userConnection, directConnection) {
|
|
jsTestLog("Create a 'good' user who is trying to run a transaction via mongos.");
|
|
const userTestDB = userConnection.getDB("test");
|
|
authutil.asCluster(userConnection, keyFile, () => {
|
|
userTestDB.getSiblingDB("admin").createUser({user: "root", pwd: "root", roles: ["root"]});
|
|
userTestDB.getSiblingDB("admin").createUser({user: "ro", pwd: "ro", roles: ["read"]});
|
|
});
|
|
assert.eq(1, userTestDB.getSiblingDB("admin").auth("root", "root"));
|
|
|
|
// In the replica set case, since both connections are the same, we've already
|
|
// created the users. So we don't create the users again.
|
|
if (userConnection != directConnection) {
|
|
// The root user is to simulate someone who leaks the LSID.
|
|
jsTestLog("Create root user and malicious read-only user on the shard.");
|
|
const testDB = directConnection.getDB("test");
|
|
authutil.asCluster(directConnection, keyFile, () => {
|
|
testDB.getSiblingDB("admin").createUser({user: "ro", pwd: "ro", roles: ["read"]});
|
|
testDB.getSiblingDB("admin").createUser({user: "root", pwd: "root", roles: ["root"]});
|
|
});
|
|
}
|
|
|
|
jsTestLog("A user with read-only permissions can't run coordinateCommitTransaction:");
|
|
const connRO = new Mongo(directConnection.host);
|
|
const testDBRO = connRO.getDB("test");
|
|
assert.eq(1, testDBRO.getSiblingDB("admin").auth("ro", "ro"));
|
|
assert.commandFailedWithCode(
|
|
testDBRO.adminCommand({
|
|
coordinateCommitTransaction: 1,
|
|
participants: [],
|
|
}),
|
|
ErrorCodes.Unauthorized,
|
|
);
|
|
|
|
// Insert a document and create the collection because we can't create collections in a
|
|
// cross-shard transaction.
|
|
assert.commandWorked(userTestDB[collName].insert({a: -1}));
|
|
// Next we run one transaction to completion. This is so that config.transactions gets
|
|
// populated. From the config.transactions entry we will figure out what the uid component of
|
|
// the lsid is.
|
|
assert.commandWorked(
|
|
userTestDB.runCommand({
|
|
insert: collName,
|
|
documents: [{a: 0}],
|
|
startTransaction: true,
|
|
lsid: {"id": id},
|
|
txnNumber: NumberLong(0),
|
|
autocommit: false,
|
|
}),
|
|
);
|
|
assert.commandWorked(
|
|
userTestDB.adminCommand({
|
|
commitTransaction: 1,
|
|
lsid: {"id": id},
|
|
txnNumber: NumberLong(0),
|
|
autocommit: false,
|
|
}),
|
|
);
|
|
|
|
// Now we start a second transaction, which we will attempt to commit through the malicious user
|
|
// via the coordinateCommitTransaction command.
|
|
jsTestLog("Good user starts a second transaction:");
|
|
assert.commandWorked(
|
|
userTestDB.runCommand({
|
|
insert: collName,
|
|
documents: [{a: 1}],
|
|
startTransaction: true,
|
|
lsid: {"id": id},
|
|
txnNumber: NumberLong(1),
|
|
autocommit: false,
|
|
}),
|
|
);
|
|
|
|
jsTestLog("Trying to get the uid of user.");
|
|
const directRootConn = new Mongo(directConnection.host);
|
|
const directRootTestDB = directRootConn.getDB("test");
|
|
assert.eq(1, directRootTestDB.getSiblingDB("admin").auth("root", "root"));
|
|
|
|
let txnEntry = directRootTestDB.getSiblingDB("config").transactions.findOne({"_id.id": id});
|
|
jsTestLog("config.transactions entry: " + tojson(txnEntry));
|
|
assert(txnEntry._id.uid);
|
|
|
|
jsTestLog("Sending coordinateCommitTransaction when impersonating lsid (id+uid) should fail:");
|
|
const readOnlyConn = new Mongo(directConnection.host);
|
|
const readOnlyTestDB = readOnlyConn.getDB("test");
|
|
assert.eq(1, readOnlyTestDB.getSiblingDB("admin").auth("ro", "ro"));
|
|
const res = assert.commandFailedWithCode(
|
|
readOnlyTestDB.adminCommand({
|
|
coordinateCommitTransaction: 1,
|
|
lsid: {
|
|
id: id,
|
|
uid: txnEntry._id.uid,
|
|
},
|
|
txnNumber: NumberLong(1),
|
|
autocommit: false,
|
|
participants: [],
|
|
}),
|
|
ErrorCodes.Unauthorized,
|
|
);
|
|
assert(res.errmsg.includes("Unauthorized to set user digest"), res);
|
|
}
|
|
|
|
jsTestLog("Running test on replica set.");
|
|
const rs = new ReplSetTest({nodes: 1, setParameter: {logComponentVerbosity: {transaction: 4}}, keyFile: keyFile});
|
|
rs.startSet();
|
|
rs.initiate();
|
|
testCoordinateCommitTransactionFails(rs.getPrimary(), rs.getPrimary());
|
|
rs.stopSet();
|
|
|
|
jsTestLog("Running test on sharded cluster.");
|
|
const st = new ShardingTest({
|
|
shards: 1,
|
|
rs: {nodes: 1, setParameter: {logComponentVerbosity: {transaction: 4}}},
|
|
keyFile: keyFile,
|
|
});
|
|
testCoordinateCommitTransactionFails(st.s, st._connections[0]);
|
|
st.stop();
|