0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-24 00:17:37 +01:00

SERVER-95701 Write authorization schema document on FCV downgrade when necessary (#29054)

GitOrigin-RevId: 5b6630666e29cd473b68edadd9556bddad78e0a6
This commit is contained in:
Varun Ravichandran 2024-11-18 15:36:18 -05:00 committed by MongoDB Bot
parent feadf8f6c2
commit b794658713
4 changed files with 184 additions and 0 deletions

View File

@ -0,0 +1,126 @@
// Test that binary upgrade/downgrade works with auth schema version.
// Test that initial sync with users works with latest primary last-lts secondaries and vice
// versa.
import "jstests/multiVersion/libs/multi_rs.js";
import {ReplSetTest} from "jstests/libs/replsettest.js";
const keyfile = "jstests/libs/key1";
const authSchemaColl = "system.version";
const schemaVersion28SCRAM = 5;
// We need to upgrade the TestData's privileges so that the test itself can perform the
// necessary commands within rst.upgradeSet.
TestData.auth = true;
TestData.keyFile = keyfile;
TestData.authUser = "__system";
TestData.keyFileData = "foopdedoop";
TestData.authenticationDatabase = "local";
function testChangeBinariesWithAuthzSchemaDoc(originalBinVersion, updatedBinVersion) {
const rst = new ReplSetTest(
{nodes: 2, nodeOptions: {binVersion: originalBinVersion, auth: ''}, keyFile: keyfile});
rst.startSet();
rst.initiate();
// Create a user, which is a necessary precondition for requiring authorization schema document
// on lower binVersion binaries .
let primary = rst.getPrimary();
let adminDB = primary.getDB("admin");
assert.commandWorked(adminDB.runCommand(
{createUser: "admin", pwd: "admin", roles: [{role: "root", db: "admin"}]}));
// If updatedBinVersion is last-lts, then we need to downgrade FCV before downgrading the
// binary.
if (updatedBinVersion === 'last-lts') {
assert.commandWorked(
adminDB.runCommand({setFeatureCompatibilityVersion: lastLTSFCV, confirm: true}));
}
// Query the authorization schema doc to ensure that it exists and is set to
// schemaVersion28SCRAM. On 'last-lts' binaries that are being upgraded, this doc
// should have been created during `createUser`. On `latest` binaries that are being
// downgraded, it should have been created during FCV downgrade since at least 1 user
// document (`admin`) existed.
const currentVersion = adminDB[authSchemaColl].findOne({_id: 'authSchema'}).currentVersion;
assert.eq(currentVersion, schemaVersion28SCRAM);
// Change binaries to updatedBinVersion - this may constitute an upgrade or a downgrade.
// Last-lts binaries write and read the authorization schema document, while latest binaries
// should only write it down during FCV downgrade if there are any user or role docs on-disk.
// Latest binaries never read from it.
// Therefore, upgrade should always work without issues while downgrade
// should work as long as FCV is properly downgraded before binary downgrade.
rst.upgradeSet({binVersion: updatedBinVersion, keyFile: keyfile});
// Retrieve the new primary and run usersInfo to make sure everything works normally.
primary = rst.getPrimary();
adminDB = primary.getDB("admin");
const usersInfoReply = assert.commandWorked(adminDB.runCommand({usersInfo: 1}));
assert(usersInfoReply.hasOwnProperty("users"));
assert.eq(usersInfoReply.users.length, 1);
rst.stopSet();
}
function testMixedVersionInitialSync(primaryBinVersion, newNodeBinVersion) {
// Create a single-node replica set with binVersion primaryBinVersion.
const rst = new ReplSetTest(
{nodes: 1, nodeOptions: {binVersion: primaryBinVersion, auth: ''}, keyFile: keyfile});
rst.startSet();
rst.initiate();
// Create a user, which should automatically create an authorization schema document.
let primary = rst.getPrimary();
let adminDB = primary.getDB("admin");
assert.commandWorked(adminDB.runCommand(
{createUser: "admin", pwd: "admin", roles: [{role: "root", db: "admin"}]}));
// If newNodeBinVersion is 'last-lts', then we need to downgrade FCV prior to adding the new
// node.
if (newNodeBinVersion === 'last-lts') {
assert.commandWorked(
adminDB.runCommand({setFeatureCompatibilityVersion: lastLTSFCV, confirm: true}));
}
// Query the authorization schema doc to ensure that it exists and is set to
// schemaVersion28SCRAM. On 'last-lts' binaries that are being upgraded, this doc
// should have been created during `createUser`. On `latest` binaries that are being
// downgraded, it should have been created during FCV downgrade.
const currentVersion = adminDB[authSchemaColl].findOne({_id: 'authSchema'}).currentVersion;
assert.eq(currentVersion, schemaVersion28SCRAM);
// Add a newNodeBinVersion node to the replica set and check that initial sync succeeds. The new
// node will have no priority or
let secondary = rst.add(
{binVersion: newNodeBinVersion, keyFile: keyfile, rsConfig: {votes: 0, priority: 0}});
// Reinitiate the replica set and check that initial sync has completed on the secondary.
rst.reInitiate();
assert.commandWorked(
primary.getDB("test").coll.insert({awaitRepl: true}, {writeConcern: {w: 2}}));
rst.awaitReplication();
rst.awaitSecondaryNodes();
// Run usersInfo on the secondary node and check that 1 user (admin) has been replicated.
secondary = rst.getSecondary();
const secondaryAdminDB = secondary.getDB("admin");
const usersInfoReply = assert.commandWorked(secondaryAdminDB.runCommand({usersInfo: 1}));
assert(usersInfoReply.hasOwnProperty("users"));
assert.eq(usersInfoReply.users.length, 1);
rst.stopSet();
}
// Upgrade
testChangeBinariesWithAuthzSchemaDoc('last-lts', 'latest');
// Downgrade
testChangeBinariesWithAuthzSchemaDoc('latest', 'last-lts');
// Initial sync for a last-lts node from an latest primary.
testMixedVersionInitialSync('latest', 'last-lts');
// Initial sync for a latest node from a last-lts primary.
testMixedVersionInitialSync('last-lts', 'latest');

View File

@ -53,4 +53,7 @@ constexpr StringData AuthorizationManager::V1_USER_SOURCE_FIELD_NAME;
const Status AuthorizationManager::authenticationFailedStatus(ErrorCodes::AuthenticationFailed,
"Authentication failed.");
const BSONObj AuthorizationManager::versionDocumentQuery = BSON("_id"
<< "authSchema");
} // namespace mongo

View File

@ -128,6 +128,17 @@ public:
*/
static const Status authenticationFailedStatus;
/**
* Query to match the auth schema version document in the versionCollectionNamespace while
* upserting it on FCV downgrade.
*/
static const BSONObj versionDocumentQuery;
/**
* Name of the field in the auth schema version document containing the current schema version.
*/
static constexpr StringData schemaVersionFieldName = "currentVersion"_sd;
/**
* Auth schema version for MongoDB 3.0 SCRAM only mode.
* Users are stored in admin.system.users, roles in admin.system.roles.

View File

@ -74,6 +74,7 @@
#include "mongo/db/concurrency/exception_util.h"
#include "mongo/db/concurrency/lock_manager_defs.h"
#include "mongo/db/database_name.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/dbhelpers.h"
#include "mongo/db/drop_gen.h"
@ -1168,6 +1169,48 @@ private:
}
}
// Insert the authorization schema document in admin.system.version if there are any user or
// role documents on-disk. This must be performed on FCV downgrade since lower-version binaries
// assert that this document exists when users and/or roles exist during initial sync.
void _createAuthzSchemaVersionDocIfNeeded(OperationContext* opCtx) {
// Check if any user or role documents exist on-disk.
bool hasUserDocs, hasRoleDocs = false;
BSONObj userDoc, roleDoc;
{
AutoGetCollectionForReadCommandMaybeLockFree usersColl(
opCtx, NamespaceString::kAdminUsersNamespace);
hasUserDocs = Helpers::findOne(opCtx, usersColl.getCollection(), BSONObj(), userDoc);
}
{
AutoGetCollectionForReadCommandMaybeLockFree rolesColl(
opCtx, NamespaceString::kAdminRolesNamespace);
hasRoleDocs = Helpers::findOne(opCtx, rolesColl.getCollection(), BSONObj(), roleDoc);
}
// If they do, write an authorization schema document to disk set to schemaVersionSCRAM28.
if (hasUserDocs || hasRoleDocs) {
DBDirectClient client(opCtx);
auto result = client.update([&] {
write_ops::UpdateCommandRequest updateOp(
NamespaceString::kServerConfigurationNamespace);
updateOp.setUpdates({[&] {
write_ops::UpdateOpEntry entry;
entry.setQ(AuthorizationManager::versionDocumentQuery);
entry.setU(write_ops::UpdateModification::parseFromClassicUpdate(
BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName
<< AuthorizationManager::schemaVersion28SCRAM))));
entry.setMulti(false);
entry.setUpsert(true);
return entry;
}()});
return updateOp;
}());
write_ops::checkWriteErrors(result);
}
}
// This helper function is for any internal server downgrade cleanup, such as dropping
// collections or aborting. This cleanup will happen after user collection downgrade
// cleanup.
@ -1231,6 +1274,7 @@ private:
}
_cleanUpClusterParameters(opCtx, requestedVersion);
_createAuthzSchemaVersionDocIfNeeded(opCtx);
// Note the config server is also considered a shard, so the ConfigServer and ShardServer
// roles aren't mutually exclusive.
if (role && role->has(ClusterRole::ConfigServer)) {