summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/sharding/direct_shard_connection_auth.js94
-rw-r--r--src/mongo/db/s/sharding_statistics.cpp6
-rw-r--r--src/mongo/db/s/sharding_statistics.h5
-rw-r--r--src/mongo/db/service_entry_point_common.cpp1
4 files changed, 106 insertions, 0 deletions
diff --git a/jstests/sharding/direct_shard_connection_auth.js b/jstests/sharding/direct_shard_connection_auth.js
new file mode 100644
index 00000000000..88f02adedb2
--- /dev/null
+++ b/jstests/sharding/direct_shard_connection_auth.js
@@ -0,0 +1,94 @@
+/**
+ * Tests that direct shard connections are correctly allowed and disallowed using authentication.
+ *
+ * @tags: [featureFlagCheckForDirectShardOperations, featureFlagClusterCardinalityParameter]
+ */
+(function() {
+'use strict';
+
+// Create a new sharded cluster for testing and enable auth.
+const st = new ShardingTest({name: jsTestName(), keyFile: "jstests/libs/key1", shards: 1});
+
+const shardConn = st.rs0.getPrimary();
+const shardAdminDB = shardConn.getDB("admin");
+const shardAdminTestDB = shardConn.getDB("test");
+const userConn = new Mongo(st.shard0.host);
+const userTestDB = userConn.getDB("test");
+
+function getUnauthorizedDirectWritesCount() {
+ return assert.commandWorked(shardAdminDB.runCommand({serverStatus: 1}))
+ .shardingStatistics.unauthorizedDirectShardOps;
+}
+
+// With only one shard, direct shard operations should be allowed.
+jsTest.log("Running tests with only one shard.");
+{
+ // Direct writes to collections with root priviledge should be authorized.
+ shardAdminDB.createUser({user: "admin", pwd: 'x', roles: ["root"]});
+ assert(shardAdminDB.auth("admin", 'x'), "Authentication failed");
+ assert.commandWorked(shardAdminTestDB.getCollection("coll").insert({value: 1}));
+ assert.eq(getUnauthorizedDirectWritesCount(), 0);
+
+ // Direct writes to collections with read/write priviledge should be authorized.
+ shardAdminTestDB.createUser({user: "user", pwd: "y", roles: ["readWrite"]});
+ assert(userTestDB.auth("user", "y"), "Authentication failed");
+ assert.commandWorked(userTestDB.getCollection("coll").insert({value: 2}));
+ assert.eq(getUnauthorizedDirectWritesCount(), 0);
+
+ // Logging out and dropping users should be authorized.
+ userTestDB.logout();
+ shardAdminTestDB.dropUser("user");
+ assert.eq(getUnauthorizedDirectWritesCount(), 0);
+}
+
+// Adding the second shard will trigger the check for direct shard ops.
+var newShard = new ReplSetTest({name: "additionalShard", nodes: 1});
+newShard.startSet({keyFile: "jstests/libs/key1", shardsvr: ""});
+newShard.initiate();
+let mongosAdminUser = st.s.getDB('admin');
+if (!TestData.configShard) {
+ mongosAdminUser.createUser({user: "globalAdmin", pwd: 'a', roles: ["root"]});
+ assert(mongosAdminUser.auth("globalAdmin", "a"), "Authentication failed");
+} else {
+ assert(mongosAdminUser.auth("admin", "x"), "Authentication failed");
+}
+assert.commandWorked(mongosAdminUser.runCommand({addShard: newShard.getURL()}));
+
+jsTest.log("Running tests with two shards.");
+{
+ // Direct writes to collections with root priviledge (which includes directShardOperations)
+ // should be authorized.
+ assert.commandWorked(shardAdminTestDB.getCollection("coll").insert({value: 3}));
+ assert.eq(getUnauthorizedDirectWritesCount(), 0);
+
+ // Direct writes to collections with read/write priviledge should not be authorized.
+ shardAdminTestDB.createUser({user: "user", pwd: "y", roles: ["readWrite"]});
+ assert(userTestDB.auth("user", "y"), "Authentication failed");
+ assert.commandWorked(userTestDB.getCollection("coll").insert({value: 4}));
+ assert.eq(getUnauthorizedDirectWritesCount(), 1);
+ userTestDB.logout();
+ assert.eq(getUnauthorizedDirectWritesCount(), 1);
+
+ // Direct writes with just read/write and direct operations should be authorized.
+ shardAdminDB.createUser(
+ {user: "user2", pwd: "z", roles: ["readWriteAnyDatabase", "directShardOperations"]});
+ let shardUserWithDirectWritesAdminDB = userConn.getDB("admin");
+ let shardUserWithDirectWritesTestDB = userConn.getDB("test");
+ assert(shardUserWithDirectWritesAdminDB.auth("user2", "z"), "Authentication failed");
+ assert.commandWorked(shardUserWithDirectWritesTestDB.getCollection("coll").insert({value: 5}));
+ assert.eq(getUnauthorizedDirectWritesCount(), 1);
+
+ // Logout should always be authorized and drop user from admin should be authorized.
+ shardUserWithDirectWritesAdminDB.logout();
+ shardAdminTestDB.dropUser("user");
+ shardAdminTestDB.dropUser("user2");
+ mongosAdminUser.logout();
+ assert.eq(getUnauthorizedDirectWritesCount(), 1);
+ // shardAdminDB is used to check the direct writes count, so log it out last.
+ shardAdminDB.logout();
+}
+
+// Stop the sharding test before the additional shard to ensure the test hooks run successfully.
+st.stop();
+newShard.stopSet();
+})();
diff --git a/src/mongo/db/s/sharding_statistics.cpp b/src/mongo/db/s/sharding_statistics.cpp
index 527804c1689..ab141c405df 100644
--- a/src/mongo/db/s/sharding_statistics.cpp
+++ b/src/mongo/db/s/sharding_statistics.cpp
@@ -81,6 +81,12 @@ void ShardingStatistics::report(BSONObjBuilder* builder) const {
// (Ignore FCV check): This feature flag doesn't have any upgrade/downgrade concerns.
if (mongo::feature_flags::gConcurrencyInChunkMigration.isEnabledAndIgnoreFCVUnsafe())
builder->append("chunkMigrationConcurrency", chunkMigrationConcurrencyCnt.load());
+ // The serverStatus command is run before the FCV is initialized so we ignore it when
+ // checking whether the direct shard operations feature flag is enabled.
+ if (mongo::feature_flags::gCheckForDirectShardOperations
+ .isEnabledAndIgnoreFCVUnsafeAtStartup()) {
+ builder->append("unauthorizedDirectShardOps", unauthorizedDirectShardOperations.load());
+ }
}
} // namespace mongo
diff --git a/src/mongo/db/s/sharding_statistics.h b/src/mongo/db/s/sharding_statistics.h
index 9479bbc87bd..541b6df1bd0 100644
--- a/src/mongo/db/s/sharding_statistics.h
+++ b/src/mongo/db/s/sharding_statistics.h
@@ -129,6 +129,11 @@ struct ShardingStatistics {
// Current number for chunkMigrationConcurrency that defines concurrent fetchers and inserters
// used for _migrateClone(step 4) of chunk migration
AtomicWord<int> chunkMigrationConcurrencyCnt{1};
+
+ // Total number of commands run directly against this shard without the directShardOperations
+ // role.
+ AtomicWord<long long> unauthorizedDirectShardOperations{0};
+
/**
* Obtains the per-process instance of the sharding statistics object.
*/
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp
index b4a47137d64..45609430298 100644
--- a/src/mongo/db/service_entry_point_common.cpp
+++ b/src/mongo/db/service_entry_point_common.cpp
@@ -1772,6 +1772,7 @@ void ExecCommandDatabase::_initiateCommand() {
"directShardOperations role. Please connect via a router.",
"command"_attr = request.getCommandName());
}
+ ShardingStatistics::get(opCtx).unauthorizedDirectShardOperations.addAndFetch(1);
}
}
}