diff --git a/jstests/replsets/session_collection_reauthenticate.js b/jstests/replsets/session_collection_reauthenticate.js new file mode 100644 index 00000000000..5f8f1edbf6a --- /dev/null +++ b/jstests/replsets/session_collection_reauthenticate.js @@ -0,0 +1,90 @@ +/** + * This test checks that consecutive authentication attempt on the same + * ScopedDbCollection instance does not result in reauthentication warning on primary + * + * Previously we had a warning so this test will fail with any earlier version of the code + * + * @tags: [ + * requires_fcv_83 + * ] + */ + +import {ReplSetTest} from "jstests/libs/replsettest.js"; +import {validateSessionsCollection} from "jstests/libs/sessions_collection.js"; + +// This test makes assertions about the number of sessions, which are not compatible with +// implicit sessions. +TestData.disableImplicitSessions = true; + +const replTest = new ReplSetTest({ + name: "refresh", + nodes: [ + { + /* primary */ + }, + {/* secondary */ rsConfig: {priority: 0}}, + ], + keyFile: "jstests/libs/key1", + nodeOptions: { + setParameter: "connPoolMaxConnsPerHost=1", + }, // global connection pool will contain only one connection +}); +const nodes = replTest.startSet(); + +replTest.initiate(); +const kAdminUser = { + name: "admin", + pwd: "admin", +}; + +const primary = replTest.getPrimary(); +// Create the admin user +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)); + +const primaryAdmin = primary.getDB("admin"); + +replTest.awaitReplication(); +const secondary = replTest.getSecondary(); +const secondaryAdmin = secondary.getDB("admin"); +assert.eq(1, secondaryAdmin.auth(kAdminUser.name, kAdminUser.pwd)); + +// Get the current value of the TTL index so that we can verify it's being properly applied. +let res = assert.commandWorked(primary.adminCommand({getParameter: 1, localLogicalSessionTimeoutMinutes: 1})); + +let timeoutMinutes = res.localLogicalSessionTimeoutMinutes; + +{ + // Sessions collection doesn't yet exist on primary + validateSessionsCollection(primary, false, false, timeoutMinutes); + + replTest.awaitReplication(); + // Sessions collection doesn't yet exist on secondary + validateSessionsCollection(secondary, false, false, timeoutMinutes); + + assert.commandWorked(primaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); + // Created sessions collection + validateSessionsCollection(primary, true, true, timeoutMinutes); +} + +{ + replTest.awaitReplication(); + // Refresh cache on secondary will authenticate a user with new ScopedDbCollection instance + assert.commandWorked(secondaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); + // Another refresh on secondary will authenticate again on the same instance because we limited + // connection pool to be of size 1 + assert.commandWorked(secondaryAdmin.runCommand({refreshLogicalSessionCacheNow: 1})); + replTest.awaitReplication(); +} +// Make sure that the fix worked and no warning logged on primary and secondary +assert( + checkLog.checkContainsWithCountJson(primary, 5626700, {}, 0, null, true), + "Expecting not to find reauthentication warning in primary logs", +); +assert( + checkLog.checkContainsWithCountJson(secondary, 5626700, {}, 0, null, true), + "Expecting not to find reauthentication warning in primary logs", +); + +replTest.stopSet(); diff --git a/src/mongo/client/dbclient_base.cpp b/src/mongo/client/dbclient_base.cpp index 40a5adf294b..8d384c8eb79 100644 --- a/src/mongo/client/dbclient_base.cpp +++ b/src/mongo/client/dbclient_base.cpp @@ -409,6 +409,7 @@ void DBClientBase::_auth(const BSONObj& params) { HostAndPort remote(getServerAddress()); auth::authenticateClient(params, remote, clientName, _makeAuthRunCommandHook()).get(); + _isClientAuthenticated.store(true); } void DBClientBase::authenticateInternalUser(auth::StepDownBehavior stepDownBehavior) { @@ -439,6 +440,7 @@ void DBClientBase::authenticateInternalUser(auth::StepDownBehavior stepDownBehav _makeAuthRunCommandHook(), authProvider) .get(); + _isClientAuthenticated.store(true); } catch (const DBException& e) { if (!serverGlobalParams.quiet.load()) { LOGV2(20117, @@ -474,6 +476,7 @@ void DBClientBase::auth(const DatabaseName& dbname, StringData username, StringD void DBClientBase::logout(const DatabaseName& dbName, BSONObj& info) { runCommand(dbName, BSON("logout" << 1), info); + _isClientAuthenticated.store(false); } bool DBClientBase::isPrimary(bool& isPrimary, BSONObj* info) { diff --git a/src/mongo/client/dbclient_base.h b/src/mongo/client/dbclient_base.h index 84d303619c8..fec3acedf4f 100644 --- a/src/mongo/client/dbclient_base.h +++ b/src/mongo/client/dbclient_base.h @@ -540,6 +540,9 @@ public: return find(std::move(findRequest), readPref, ExhaustMode::kOff); } + bool isAuthenticated() const { + return _isClientAuthenticated.load(); + } /** * Issues a find command described by 'findRequest' and the given read preference. Rather than * returning a cursor to the caller, iterates the cursor under the hood and calls the provided @@ -757,6 +760,8 @@ private: Timestamp _lastOperationTime; ClientAPIVersionParameters _apiParameters; + + AtomicWord _isClientAuthenticated = {false}; }; // DBClientBase BSONElement getErrField(const BSONObj& result); diff --git a/src/mongo/db/session/sessions_collection_rs.cpp b/src/mongo/db/session/sessions_collection_rs.cpp index 148b823692f..1d7bf4e59a2 100644 --- a/src/mongo/db/session/sessions_collection_rs.cpp +++ b/src/mongo/db/session/sessions_collection_rs.cpp @@ -38,6 +38,7 @@ #include "mongo/client/internal_auth.h" #include "mongo/client/read_preference.h" #include "mongo/client/remote_command_targeter_factory_impl.h" +#include "mongo/db/auth/authorization_session.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/local_catalog/lock_manager/d_concurrency.h" #include "mongo/db/local_catalog/lock_manager/lock_manager_defs.h" @@ -82,7 +83,7 @@ auto SessionsCollectionRS::_makePrimaryConnection(OperationContext* opCtx) { auto conn = std::make_unique(res.toString()); // Make a connection to the primary, auth, then send - if (auth::isInternalAuthSet()) { + if (auth::isInternalAuthSet() && !conn->get()->isAuthenticated()) { conn->get()->authenticateInternalUser(); }