mongo/jstests/replsets/advance_cluster_time.js
Zac 591928c619 SERVER-108478 JS formatted by prettier and remove clang-format (#39656)
GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
2025-08-21 17:27:09 +00:00

206 lines
7.7 KiB
JavaScript

/**
* Test that the client cannot gossip clusterTime from one replica set to another if it doesn't
* have the advanceClusterTime privilege.
*
* @tags: [
* ]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
// Multiple users cannot be authenticated on one connection within a session.
TestData.disableImplicitSessions = true;
const kDbName = "testDb";
const kCollName = "testColl";
const kAdminUser = {
name: "admin",
pwd: "admin",
};
const kUserWithoutAdvanceClusterTimePrivilege = {
name: "cannotAdvanceClusterTime",
pwd: "pwd",
};
const kUserWithAdvanceClusterTimePrivilege = {
name: "canAdvanceClusterTime",
pwd: "pwd",
};
function createUsers(primary) {
// Create the admin user and advanceClusterTimeRole.
const adminDB = primary.getDB("admin");
assert.commandWorked(adminDB.runCommand({createUser: kAdminUser.name, pwd: kAdminUser.pwd, roles: ["root"]}));
assert.eq(1, adminDB.auth(kAdminUser.name, kAdminUser.pwd));
assert.commandWorked(
adminDB.runCommand({
createRole: "advanceClusterTimeRole",
privileges: [{resource: {cluster: true}, actions: ["advanceClusterTime"]}],
roles: [],
}),
);
// Create one user without advanceClusterTime privilege, and one with advanceClusterTime
// privilege.
const testDB = primary.getDB(kDbName);
assert.commandWorked(
testDB.runCommand({
createUser: kUserWithoutAdvanceClusterTimePrivilege.name,
pwd: kUserWithoutAdvanceClusterTimePrivilege.pwd,
roles: ["readWrite"],
}),
);
assert.commandWorked(
testDB.runCommand({
createUser: kUserWithAdvanceClusterTimePrivilege.name,
pwd: kUserWithAdvanceClusterTimePrivilege.pwd,
roles: [{role: "advanceClusterTimeRole", db: "admin"}, "readWrite"],
}),
);
adminDB.logout();
}
const rst1 = new ReplSetTest({
nodes: [{setParameter: {"failpoint.alwaysValidateClientsClusterTime": tojson({mode: "alwaysOn"})}}],
name: "rst1",
keyFile: "jstests/libs/key1",
});
const rst2 = new ReplSetTest({nodes: 1, name: "rst2", keyFile: "jstests/libs/key2"});
rst1.startSet();
rst1.initiate();
rst2.startSet();
rst2.initiate();
const rst1Primary = rst1.getPrimary();
const rst2Primary = rst2.getPrimary();
// Binaries earlier than 8.0 fail to parse a missing signature.
// TODO SERVER-88458: Remove.
const canAlwaysParseMissingClusterTimeSignature = !jsTest.options().useRandomBinVersionsWithinReplicaSet;
createUsers(rst1Primary);
createUsers(rst2Primary);
const rst1TestDB = rst1Primary.getDB(kDbName);
const rst2TestDB = rst2Primary.getDB(kDbName);
// Test clusterTime gossip when the client does not have advanceClusterTime privilege.
(() => {
assert.eq(
1,
rst1TestDB.auth(kUserWithoutAdvanceClusterTimePrivilege.name, kUserWithoutAdvanceClusterTimePrivilege.pwd),
);
assert.eq(
1,
rst2TestDB.auth(kUserWithoutAdvanceClusterTimePrivilege.name, kUserWithoutAdvanceClusterTimePrivilege.pwd),
);
const rst1ClusterTime = assert.commandWorked(rst1TestDB.runCommand({find: kCollName})).$clusterTime;
const rst2ClusterTime = assert.commandWorked(
rst2TestDB.runCommand({insert: kCollName, documents: [{_id: 0}]}),
).$clusterTime;
jsTest.log("rst1's clusterTime " + tojson(rst1ClusterTime));
jsTest.log("rst2's clusterTime " + tojson(rst2ClusterTime));
// External clients always receive cluster time signatures.
assert(
rst1ClusterTime.hasOwnProperty("clusterTime") && rst1ClusterTime.hasOwnProperty("signature"),
tojson(rst1ClusterTime),
);
assert(
rst2ClusterTime.hasOwnProperty("clusterTime") && rst2ClusterTime.hasOwnProperty("signature"),
tojson(rst2ClusterTime),
);
// A key's keyId is generated as the node's current clusterTime's Timestamp so it is possible
// for the keyId for rst2ClusterTime to match the key on rst1, and in that case the command
// would fail with TimeProofMismatch instead of KeyNotFound.
assert.commandFailedWithCode(rst1TestDB.runCommand({find: kCollName, $clusterTime: rst2ClusterTime}), [
ErrorCodes.TimeProofMismatch,
ErrorCodes.KeyNotFound,
]);
// Sending no signature without the advanceClusterTime privilege fails because this is treated
// the same as sending the dummy signature with keyId 0.
// Make a copy so anything with a reference to the original cluster time isn't affected.
let clusterTimeCopy = Object.extend({}, rst2ClusterTime);
delete clusterTimeCopy.signature;
assert(!clusterTimeCopy.hasOwnProperty("signature"), tojson(clusterTimeCopy));
assert(rst2ClusterTime.hasOwnProperty("signature"), tojson(rst2ClusterTime));
if (canAlwaysParseMissingClusterTimeSignature) {
assert.commandFailedWithCode(
rst1TestDB.runCommand({find: kCollName, $clusterTime: clusterTimeCopy}),
ErrorCodes.KeyNotFound,
);
} else {
// We test with mixed version replica sets. An 8.0 binary primary can parse the signature
// and will fail like above, but a lower binary will fail to parse.
assert.commandFailedWithCode(rst1TestDB.runCommand({find: kCollName, $clusterTime: clusterTimeCopy}), [
ErrorCodes.NoSuchKey,
ErrorCodes.KeyNotFound,
]);
}
rst1TestDB.logout();
rst2TestDB.logout();
})();
// Test clusterTime gossip when the client does have advanceClusterTime privilege.
(() => {
assert.eq(1, rst1TestDB.auth(kUserWithAdvanceClusterTimePrivilege.name, kUserWithAdvanceClusterTimePrivilege.pwd));
assert.eq(
1,
rst2TestDB.auth(kUserWithoutAdvanceClusterTimePrivilege.name, kUserWithoutAdvanceClusterTimePrivilege.pwd),
);
const rst1ClusterTime = assert.commandWorked(rst1TestDB.runCommand({find: kCollName})).$clusterTime;
const rst2ClusterTime = assert.commandWorked(
rst2TestDB.runCommand({insert: kCollName, documents: [{_id: 1}]}),
).$clusterTime;
jsTest.log("rst1's clusterTime " + tojson(rst1ClusterTime));
jsTest.log("rst2's clusterTime " + tojson(rst2ClusterTime));
// External clients always receive cluster time signatures.
assert(
rst1ClusterTime.hasOwnProperty("clusterTime") && rst1ClusterTime.hasOwnProperty("signature"),
tojson(rst1ClusterTime),
);
assert(
rst2ClusterTime.hasOwnProperty("clusterTime") && rst2ClusterTime.hasOwnProperty("signature"),
tojson(rst2ClusterTime),
);
assert.commandWorked(rst1TestDB.runCommand({find: kCollName, $clusterTime: rst2ClusterTime}));
// Sending no signature without the advanceClusterTime privilege fails because this is treated
// the same as sending the dummy signature with keyId 0.
// Make a copy so anything with a reference to the original cluster time isn't affected.
let clusterTimeCopy = Object.extend({}, rst2ClusterTime);
delete clusterTimeCopy.signature;
assert(!clusterTimeCopy.hasOwnProperty("signature"), tojson(clusterTimeCopy));
assert(rst2ClusterTime.hasOwnProperty("signature"), tojson(rst2ClusterTime));
if (canAlwaysParseMissingClusterTimeSignature) {
assert.commandWorked(rst1TestDB.runCommand({find: kCollName, $clusterTime: clusterTimeCopy}));
} else {
// We test with mixed version replica sets. An 8.0 binary primary can parse the signature
// and will succeed like above, but a lower binary will fail to parse.
assert.commandWorkedOrFailedWithCode(
rst1TestDB.runCommand({find: kCollName, $clusterTime: clusterTimeCopy}),
ErrorCodes.NoSuchKey,
);
}
rst1TestDB.logout();
rst2TestDB.logout();
})();
rst1.stopSet();
rst2.stopSet();