diff options
5 files changed, 121 insertions, 1 deletions
diff --git a/buildscripts/resmokeconfig/suites/sharding_auth.yml b/buildscripts/resmokeconfig/suites/sharding_auth.yml index 963f115fe48..c887cbe2e7d 100644 --- a/buildscripts/resmokeconfig/suites/sharding_auth.yml +++ b/buildscripts/resmokeconfig/suites/sharding_auth.yml @@ -18,6 +18,7 @@ selector: - jstests/sharding/advance_cluster_time_action_type.js - jstests/sharding/query/aggregation_currentop.js + - jstests/sharding/internal_transactions_internal_client_restrictions.js - jstests/sharding/kill_sessions.js # Skip these additional tests when running with auth enabled. - jstests/sharding/parallel.js diff --git a/buildscripts/resmokeconfig/suites/sharding_auth_audit.yml b/buildscripts/resmokeconfig/suites/sharding_auth_audit.yml index 81f603f2953..591116fdb54 100644 --- a/buildscripts/resmokeconfig/suites/sharding_auth_audit.yml +++ b/buildscripts/resmokeconfig/suites/sharding_auth_audit.yml @@ -18,6 +18,7 @@ selector: - jstests/sharding/advance_cluster_time_action_type.js - jstests/sharding/query/aggregation_currentop.js + - jstests/sharding/internal_transactions_internal_client_restrictions.js - jstests/sharding/kill_sessions.js # Skip these additional tests when running with auth enabled. - jstests/sharding/parallel.js diff --git a/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml b/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml index 3ccdc5ab0af..2208c88e452 100644 --- a/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml +++ b/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml @@ -11,6 +11,7 @@ selector: - jstests/sharding/*[aA]uth*.js - jstests/sharding/query/*[aA]uth*.js - jstests/sharding/change_streams/*[aA]uth*.js + - jstests/sharding/internal_transactions_internal_client_restrictions.js - jstests/sharding/localhostAuthBypass.js - jstests/sharding/kill_sessions.js diff --git a/jstests/sharding/internal_transactions_internal_client_restrictions.js b/jstests/sharding/internal_transactions_internal_client_restrictions.js new file mode 100644 index 00000000000..1b3d5ee222c --- /dev/null +++ b/jstests/sharding/internal_transactions_internal_client_restrictions.js @@ -0,0 +1,108 @@ +/* + * Tests internal client validation properties for txnRetryCounter. + * + * @tags: [requires_fcv_51, featureFlagInternalTransactions] + */ +(function() { +"use strict"; + +const kCollName = "testColl"; + +function verifyInternalSessionsForExternalClients(testDB, {expectFail}) { + function runInternalSessionCommand(cmd) { + if (expectFail) { + const res = + assert.commandFailedWithCode(testDB.runCommand(cmd), ErrorCodes.InvalidOptions); + assert.eq( + res.errmsg, "Internal sessions are only allowed for internal clients", tojson(res)); + } else { + assert.commandWorked(testDB.runCommand(cmd)); + assert.commandWorked(testDB.runCommand({ + commitTransaction: 1, + lsid: cmd.lsid, + txnNumber: cmd.txnNumber, + txnRetryCounter: cmd.txnRetryCounter, + autocommit: false, + })); + } + } + + // Test with both internal transaction formats. + runInternalSessionCommand({ + find: kCollName, + lsid: {id: UUID(), txnUUID: UUID()}, + txnNumber: NumberLong(1101), + startTransaction: true, + autocommit: false, + }); + runInternalSessionCommand({ + find: kCollName, + lsid: {id: UUID(), txnNumber: NumberLong(3), txnUUID: UUID()}, + txnNumber: NumberLong(1101), + startTransaction: true, + autocommit: false, + }); +} + +function verifyTxnRetryCounterForExternalClients(testDB, {expectFail}) { + const findCmd = { + find: kCollName, + lsid: {id: UUID()}, + txnNumber: NumberLong(1100), + startTransaction: true, + autocommit: false, + txnRetryCounter: NumberInt(0), + }; + + if (expectFail) { + const res = + assert.commandFailedWithCode(testDB.runCommand(findCmd), ErrorCodes.InvalidOptions); + assert.eq(res.errmsg, "txnRetryCounter is only allowed for internal clients", tojson(res)); + } else { + assert.commandWorked(testDB.runCommand(findCmd)); + assert.commandWorked(testDB.runCommand({ + commitTransaction: 1, + lsid: findCmd.lsid, + txnNumber: findCmd.txnNumber, + txnRetryCounter: findCmd.txnRetryCounter, + autocommit: false, + })); + } +} + +const keyFile = "jstests/libs/key1"; +const st = new ShardingTest({shards: 1, config: 1, other: {keyFile}}); + +jsTestLog("Verify internal session and txnRetryCounter require internal privileges on mongod"); + +// Auth as a user with enough privileges to read from any collection, but not to identify as an +// internal client. +const shardDB = st.rs0.getPrimary().getDB("admin"); +shardDB.createUser({user: "shardAdmin", pwd: "password", roles: jsTest.adminUserRoles}); +assert(shardDB.auth("shardAdmin", "password")); + +verifyTxnRetryCounterForExternalClients(shardDB, {expectFail: true}); +verifyInternalSessionsForExternalClients(shardDB, {expectFail: true}); +shardDB.logout(); + +jsTestLog("Verify internal session and txnRetryCounter require internal privileges on mongos"); + +// Auth as a user with enough privileges to read from any collection, but not to identify as an +// internal client. +const mongosDB = st.s.getDB("admin"); +mongosDB.createUser({user: "admin", pwd: "password", roles: jsTest.adminUserRoles}); +assert(mongosDB.auth("admin", "password")); + +verifyTxnRetryCounterForExternalClients(mongosDB, {expectFail: true}); +verifyInternalSessionsForExternalClients(mongosDB, {expectFail: true}); +mongosDB.logout(); + +jsTestLog("Verify internal session and txnRetryCounter work with internal privileges"); + +authutil.asCluster(st.s, keyFile, function() { + verifyTxnRetryCounterForExternalClients(mongosDB, {expectFail: false}); + verifyInternalSessionsForExternalClients(mongosDB, {expectFail: false}); +}); + +st.stop(); +})(); diff --git a/src/mongo/db/initialize_operation_session_info.cpp b/src/mongo/db/initialize_operation_session_info.cpp index c850cf92db5..659d429ef7f 100644 --- a/src/mongo/db/initialize_operation_session_info.cpp +++ b/src/mongo/db/initialize_operation_session_info.cpp @@ -46,6 +46,10 @@ OperationSessionInfoFromClient initializeOperationSessionInfo(OperationContext* bool attachToOpCtx, bool isReplSetMemberOrMongos) { auto osi = OperationSessionInfoFromClient::parse("OperationSessionInfo"_sd, requestBody); + auto isAuthorizedForInternalClusterAction = + AuthorizationSession::get(opCtx->getClient()) + ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), + ActionType::internal); if (opCtx->getClient()->isInDirectClient()) { uassert(50891, @@ -98,6 +102,9 @@ OperationSessionInfoFromClient initializeOperationSessionInfo(OperationContext* if (getParentSessionId(lsid)) { uassert(ErrorCodes::InvalidOptions, + "Internal sessions are only allowed for internal clients", + isAuthorizedForInternalClusterAction); + uassert(ErrorCodes::InvalidOptions, "Internal sessions are not supported outside of transactions", osi.getTxnNumber() && osi.getAutocommit() && !osi.getAutocommit().value()); uassert(ErrorCodes::InvalidOptions, @@ -127,12 +134,14 @@ OperationSessionInfoFromClient initializeOperationSessionInfo(OperationContext* opCtx->setTxnNumber(*osi.getTxnNumber()); if (auto txnRetryCounter = osi.getTxnRetryCounter()) { - // TODO (SERVER-58759): Add a uassert that the client is internal. uassert(ErrorCodes::InvalidOptions, "txnRetryCounter is not enabled", feature_flags::gFeatureFlagInternalTransactions.isEnabled( serverGlobalParams.featureCompatibility)); uassert(ErrorCodes::InvalidOptions, + "txnRetryCounter is only allowed for internal clients", + isAuthorizedForInternalClusterAction); + uassert(ErrorCodes::InvalidOptions, str::stream() << "Cannot specify txnRetryCounter for a retryable write", osi.getAutocommit().has_value()); uassert(ErrorCodes::InvalidOptions, |