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:
parent
feadf8f6c2
commit
b794658713
@ -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');
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)) {
|
||||
|
Loading…
Reference in New Issue
Block a user