summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/resmokeconfig/suites/sharding_auth.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharding_auth_audit.yml1
-rw-r--r--buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml1
-rw-r--r--jstests/sharding/internal_transactions_internal_client_restrictions.js108
-rw-r--r--src/mongo/db/initialize_operation_session_info.cpp11
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,