SERVER-123564 Add more tests for truncateRange oplog command inside applyOps (#52562)

GitOrigin-RevId: b172ade4b733171fed03fffe44411bfca8ead8f4
This commit is contained in:
Jan 2026-04-27 15:34:56 +02:00 committed by MongoDB Bot
parent 1190a650cc
commit eb468d18f4
6 changed files with 161 additions and 3 deletions

View File

@ -3,3 +3,7 @@ filters:
- "*":
approvers:
- 10gen/server-security
- "truncate_range*.js":
approvers:
- 10gen/query-execution
- 10gen/server-security

View File

@ -0,0 +1,9 @@
version: 1.0.0
filters:
- "*":
approvers:
- 10gen/server-security
- "truncate_range_base.js":
approvers:
- 10gen/query-execution
- 10gen/server-security

View File

@ -0,0 +1,116 @@
// Auth-test the truncateRange oplog command, when executed inside an applyOps command.
export function runTest(mongod) {
const roleName = (dbName, collName) => `${dbName}_${collName}_remove`;
const databases = ["test1", "test2"];
const collections = ["coll1", "coll2"];
let docs = [];
for (let i = 0; i < 10; ++i) {
docs.push({_id: i});
}
const admin = mongod.getDB("admin");
admin.createUser({user: "admin", pwd: "pass", roles: jsTest.adminUserRoles});
assert(admin.auth("admin", "pass"));
// Set up test databases and collections.
databases.forEach((dbName) => {
collections.forEach((collName) => {
// truncateRange only works when there is a clustered index, so create one.
assert.commandWorked(
mongod.getDB(dbName).createCollection(collName, {clusteredIndex: {key: {_id: 1}, unique: true}}),
);
assert.commandWorked(mongod.getDB(dbName).getCollection(collName).insertMany(docs));
admin.createRole({
role: roleName(dbName, collName),
privileges: [
// applyOps privilege is needed to send a truncateRange command.
{resource: {cluster: true}, actions: ["applyOps"]},
{resource: {db: dbName, collection: collName}, actions: ["remove"]},
],
roles: [],
});
});
});
// Create various test users.
// user1 has privileges on some of the collections.
admin.createUser({
user: "user1",
pwd: "pass",
roles: [roleName(databases[0], collections[0])],
});
// user2 has privileges on some other collections.
admin.createUser({
user: "user2",
pwd: "pass",
roles: [roleName(databases[0], collections[1]), roleName(databases[1], collections[0])],
});
// user3 has no privileges.
admin.createUser({
user: "user3",
pwd: "pass",
roles: [],
});
// user4 is a system user and is allowed to do everything, but still cannot run an applyOps containing a 'truncateRange'.
admin.createUser({
user: "user4",
pwd: "pass",
roles: ["__system"],
});
admin.logout();
const runAuthTest = function (test) {
// Run test for the different databases and collections.
databases.forEach((dbName) => {
collections.forEach((collName) => {
assert(admin.auth("admin", "pass"));
const db = admin.getSiblingDB(dbName);
const docs = db.getCollection(collName).find().sort({_id: 1}).showRecordId().toArray();
admin.logout();
admin.auth(test.user, "pass");
// Run truncateRange command as part of an applyOps command. This is necessary because there
// is no user-facing truncateRange command in the server.
const applyOpsCmd = {
applyOps: [
{
op: "c",
ns: `${test.dbName}.$cmd`,
o: {
"truncateRange": `${test.dbName}.${test.collName}`,
"minRecordId": docs[0].$recordId,
"maxRecordId": docs[1].$recordId,
"bytesDeleted": 1, // just a placeholder
"docsDeleted": 1,
},
},
],
};
if (test.expectedError) {
assert.commandFailedWithCode(db.runCommand(applyOpsCmd), [test.expectedError]);
} else {
assert.commandWorked(db.runCommand(applyOpsCmd));
}
admin.logout();
});
});
};
// The following commands run into the "Unrecognized command in op" error in oplog_application_checks.cpp.
runAuthTest({user: "user1", expectedError: ErrorCodes.FailedToParse}); // Partial privileges.
runAuthTest({user: "user2", expectedError: ErrorCodes.FailedToParse}); // Partial privileges.
runAuthTest({user: "user4", expectedError: ErrorCodes.FailedToParse}); // __system privileges.
// The following commands trigger an "Unauthorized" error.
runAuthTest({user: "user3", expectedError: ErrorCodes.Unauthorized}); // No privileges.
runAuthTest({user: "doesNotExist", expectedError: ErrorCodes.Unauthorized}); // User does not exist.
}

View File

@ -0,0 +1,14 @@
/*
* Auth test for the truncateRange command wrapped in an applyOps command, running against
* mongod.
* @tags: [
* featureFlagUseReplicatedTruncatesForDeletions,
* requires_fcv_83
* ]
*/
import {runTest} from "jstests/auth/lib/truncate_range_base.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";
const mongod = MongoRunner.runMongod({auth: ""});
runTest(mongod);
MongoRunner.stopMongod(mongod);

View File

@ -0,0 +1,16 @@
/*
* Auth test for the truncateRange command wrapped in an applyOps command, running in a replica set.
* @tags: [
* featureFlagUseReplicatedTruncatesForDeletions,
* requires_replication,
* requires_fcv_83
* ]
*/
import {runTest} from "jstests/auth/lib/truncate_range_base.js";
import {ReplSetTest} from "jstests/libs/replsettest.js";
const replTest = new ReplSetTest({name: jsTestName(), nodes: 2, keyFile: "jstests/libs/key1"});
replTest.startSet();
replTest.initiate();
runTest(replTest.getPrimary());
replTest.stopSet();

View File

@ -68,7 +68,7 @@ namespace mongo {
UUID OplogApplicationChecks::getUUIDFromOplogEntry(const BSONObj& oplogEntry) {
BSONElement uiElem = oplogEntry["ui"];
return uassertStatusOK(UUID::parse(uiElem));
};
}
Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opCtx,
const DatabaseName& dbName,
@ -131,7 +131,7 @@ Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opC
}
auto dbNameForAuthCheck = nss.dbName();
if (commandName == "renameCollection") {
if (commandName == "renameCollection"_sd) {
// renameCollection commands must be run on the 'admin' database. Its arguments are
// fully qualified namespaces. Catalog internals don't know the op produced by running
// renameCollection was originally run on 'admin', so we must restore this.
@ -183,7 +183,6 @@ Status OplogApplicationChecks::checkOperationAuthorization(OperationContext* opC
o, write_ops::UpdateModification::DiffOptions{}),
upsert);
} else if (opType == "d"_sd) {
return auth::checkAuthForDelete(authSession, opCtx, nss, o);
} else if (opType == "db"_sd) {
// It seems that 'db' isn't used anymore. Require all actions to prevent casual use.