summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPol Pinol Castuera <pol.castuera@mongodb.com>2022-09-12 11:04:41 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-09-12 12:04:02 +0000
commitdf26c0510bc089dd3c5a6f452249bde4b186a9b7 (patch)
tree95d17e88a22940273d7ad48e7d2fbadfac461811
parentb1d33c08186ae0d6c8dae7f9531c652c2ece1730 (diff)
downloadmongo-df26c0510bc089dd3c5a6f452249bde4b186a9b7.tar.gz
SERVER-68249 Add required privileges on the $_internalAllCollectionStats aggregation stage
-rw-r--r--jstests/sharding/all_collection_stats_auth.js91
-rw-r--r--jstests/sharding/sharded_data_distribution_auth.js81
-rw-r--r--src/mongo/db/auth/action_type.idl2
-rw-r--r--src/mongo/db/auth/builtin_roles.yml2
-rw-r--r--src/mongo/db/pipeline/aggregate_command.idl8
-rw-r--r--src/mongo/db/pipeline/document_source_internal_all_collection_stats.cpp10
-rw-r--r--src/mongo/db/pipeline/document_source_internal_all_collection_stats.h5
-rw-r--r--src/mongo/db/pipeline/document_source_sharded_data_distribution.cpp8
-rw-r--r--src/mongo/db/pipeline/document_source_sharded_data_distribution.h23
9 files changed, 213 insertions, 17 deletions
diff --git a/jstests/sharding/all_collection_stats_auth.js b/jstests/sharding/all_collection_stats_auth.js
new file mode 100644
index 00000000000..caafe005631
--- /dev/null
+++ b/jstests/sharding/all_collection_stats_auth.js
@@ -0,0 +1,91 @@
+/*
+ * Test to validate the privileges of using $_internalAllCollectionStats stage.
+ *
+ * @tags: [
+ * requires_fcv_62,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+if (!TestData.auth) {
+ jsTestLog("Skipping testing authorization since auth is not enabled");
+ return;
+}
+
+// Test privileges
+function testPrivileges() {
+ // Create new role with the exact privileges to execute $allCollectionStats
+ assert.commandWorked(adminDb.runCommand({
+ createRole: "role_ok_priv",
+ roles: [],
+ privileges: [{resource: {cluster: true}, actions: ["allCollectionStats"]}]
+ }));
+
+ // Creates users with privileges and no privileges
+ assert.commandWorked(adminDb.runCommand({createUser: "user_no_priv", pwd: "pwd", roles: []}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv1", pwd: "pwd", roles: [{role: "role_ok_priv", db: 'admin'}]}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv2", pwd: "pwd", roles: [{role: "clusterMonitor", db: 'admin'}]}));
+
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv1", "pwd"));
+ assert.commandWorked(adminDb.runCommand({
+ aggregate: 1,
+ pipeline: [{$_internalAllCollectionStats: {stats: {storageStats: {}}}}],
+ cursor: {}
+ }));
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv2", "pwd"));
+ assert.commandWorked(adminDb.runCommand({
+ aggregate: 1,
+ pipeline: [{$_internalAllCollectionStats: {stats: {storageStats: {}}}}],
+ cursor: {}
+ }));
+ assert(adminDb.logout());
+
+ // User has no privileges to execute the stage
+ assert(adminDb.auth("user_no_priv", "pwd"));
+ assert.commandFailedWithCode(
+ adminDb.runCommand({
+ aggregate: 1,
+ pipeline: [{$_internalAllCollectionStats: {stats: {storageStats: {}}}}],
+ cursor: {}
+ }),
+ ErrorCodes.Unauthorized,
+ "user should no longer have privileges to execute $_internalAllCollectionStats stage.");
+ assert(adminDb.logout());
+}
+
+// Configure initial sharding cluster
+const st = new ShardingTest({shards: 1});
+const mongos = st.s;
+
+const ns1 = "test.foo";
+const adminDb = mongos.getDB("admin");
+const testDb = mongos.getDB("test");
+
+// Create a super user with __system role.
+assert.commandWorked(adminDb.runCommand({createUser: "super", pwd: "super", roles: ["__system"]}));
+assert(adminDb.logout());
+assert(adminDb.auth("super", "super"));
+
+st.adminCommand({shardcollection: ns1, key: {skey: 1}});
+
+// Insert data to validate the aggregation stage
+for (let i = 0; i < 6; i++) {
+ assert.commandWorked(testDb.getCollection("foo").insert({skey: i}));
+}
+
+testPrivileges();
+
+st.stop();
+})();
diff --git a/jstests/sharding/sharded_data_distribution_auth.js b/jstests/sharding/sharded_data_distribution_auth.js
new file mode 100644
index 00000000000..5d1318bc287
--- /dev/null
+++ b/jstests/sharding/sharded_data_distribution_auth.js
@@ -0,0 +1,81 @@
+/*
+ * Test to validate the privileges of using $shardedDataDistribution stage.
+ *
+ * @tags: [
+ * requires_fcv_62,
+ * ]
+ */
+
+(function() {
+'use strict';
+
+if (!TestData.auth) {
+ jsTestLog("Skipping testing authorization since auth is not enabled");
+ return;
+}
+
+// Test privileges
+function testPrivileges() {
+ // Create new role with the exact privileges to execute $shardedDataDistribution
+ assert.commandWorked(adminDb.runCommand({
+ createRole: "role_ok_priv",
+ roles: [],
+ privileges: [{resource: {cluster: true}, actions: ["shardedDataDistribution"]}]
+ }));
+
+ // Creates users with privileges and no privileges
+ assert.commandWorked(adminDb.runCommand({createUser: "user_no_priv", pwd: "pwd", roles: []}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv1", pwd: "pwd", roles: [{role: "role_ok_priv", db: 'admin'}]}));
+
+ assert.commandWorked(adminDb.runCommand(
+ {createUser: "user_priv2", pwd: "pwd", roles: [{role: "clusterMonitor", db: 'admin'}]}));
+
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv1", "pwd"));
+ assert.commandWorked(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: {}}], cursor: {}}));
+ assert(adminDb.logout());
+
+ // User is in a role with privileges to execute the stage
+ assert(adminDb.auth("user_priv2", "pwd"));
+ assert.commandWorked(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: {}}], cursor: {}}));
+ assert(adminDb.logout());
+
+ // User has no privileges to execute the stage
+ assert(adminDb.auth("user_no_priv", "pwd"));
+ assert.commandFailedWithCode(
+ adminDb.runCommand({aggregate: 1, pipeline: [{$shardedDataDistribution: {}}], cursor: {}}),
+ ErrorCodes.Unauthorized,
+ "user should no longer have privileges to execute $shardedDataDistribution stage.");
+ assert(adminDb.logout());
+}
+
+// Configure initial sharding cluster
+const st = new ShardingTest({shards: 1});
+const mongos = st.s;
+
+const ns1 = "test.foo";
+const adminDb = mongos.getDB("admin");
+const testDb = mongos.getDB("test");
+
+// Create a super user with __system role.
+assert.commandWorked(adminDb.runCommand({createUser: "super", pwd: "super", roles: ["__system"]}));
+assert(adminDb.logout());
+assert(adminDb.auth("super", "super"));
+
+st.adminCommand({shardcollection: ns1, key: {skey: 1}});
+
+// Insert data to validate the aggregation stage
+for (let i = 0; i < 6; i++) {
+ assert.commandWorked(testDb.getCollection("foo").insert({skey: i}));
+}
+
+testPrivileges();
+
+st.stop();
+})();
diff --git a/src/mongo/db/auth/action_type.idl b/src/mongo/db/auth/action_type.idl
index dbc5fe12efb..0d1172330eb 100644
--- a/src/mongo/db/auth/action_type.idl
+++ b/src/mongo/db/auth/action_type.idl
@@ -45,6 +45,7 @@ enums:
values:
addShard : "addShard"
advanceClusterTime : "advanceClusterTime"
+ allCollectionStats: "allCollectionStats"
analyze : "analyze"
anyAction : "anyAction" # Special ActionType that represents *all* actions
appendOplogNote : "appendOplogNote"
@@ -168,6 +169,7 @@ enums:
setParameter : "setParameter"
setUserWriteBlockMode: "setUserWriteBlockMode"
shardCollection : "shardCollection" # ID only
+ shardedDataDistribution : "shardedDataDistribution"
shardingState : "shardingState"
shutdown : "shutdown"
splitChunk : "splitChunk"
diff --git a/src/mongo/db/auth/builtin_roles.yml b/src/mongo/db/auth/builtin_roles.yml
index d0634f8a9f7..43401882084 100644
--- a/src/mongo/db/auth/builtin_roles.yml
+++ b/src/mongo/db/auth/builtin_roles.yml
@@ -244,6 +244,8 @@ roles:
- useUUID
- inprog
- shardingState
+ - allCollectionStats
+ - shardedDataDistribution
- matchType: any_normal
actions: &clusterMonitorRoleDatabaseActions
- collStats
diff --git a/src/mongo/db/pipeline/aggregate_command.idl b/src/mongo/db/pipeline/aggregate_command.idl
index 7cc4840f273..1e772a3bb2d 100644
--- a/src/mongo/db/pipeline/aggregate_command.idl
+++ b/src/mongo/db/pipeline/aggregate_command.idl
@@ -156,6 +156,14 @@ commands:
- privilege: # $backupCursorExtend, backupCursor
resource_pattern: cluster
action_type: fsync
+ - privilege: # $_internalAllCollectionStats
+ agg_stage: _internalAllCollectionStats
+ resource_pattern: cluster
+ action_type: allCollectionStats
+ - privilege: # $shardedDataDistribution
+ agg_stage: shardedDataDistribution
+ resource_pattern: cluster
+ action_type: shardedDataDistribution
# Note that the 'CursorInitialReply' is not the only response that an aggregate command
# could return. With 'explain' or 'exchange', the response would not include the fields in
# 'CursorInitialReply'. But using 'explain' or 'exchange' is unstable, but otherwise the
diff --git a/src/mongo/db/pipeline/document_source_internal_all_collection_stats.cpp b/src/mongo/db/pipeline/document_source_internal_all_collection_stats.cpp
index e4ff28d9fcf..3e22cc826e9 100644
--- a/src/mongo/db/pipeline/document_source_internal_all_collection_stats.cpp
+++ b/src/mongo/db/pipeline/document_source_internal_all_collection_stats.cpp
@@ -43,15 +43,7 @@ DocumentSourceInternalAllCollectionStats::DocumentSourceInternalAllCollectionSta
REGISTER_DOCUMENT_SOURCE(_internalAllCollectionStats,
DocumentSourceInternalAllCollectionStats::LiteParsed::parse,
DocumentSourceInternalAllCollectionStats::createFromBsonInternal,
- AllowedWithApiStrict::kAlways);
-
-PrivilegeVector DocumentSourceInternalAllCollectionStats::LiteParsed::requiredPrivileges(
- bool isMongos, bool bypassDocumentValidation) const {
-
- // TODO: SERVER-68249
-
- return PrivilegeVector{Privilege(ResourcePattern::forAnyNormalResource(), ActionType::find)};
-}
+ AllowedWithApiStrict::kInternal);
DocumentSource::GetNextResult DocumentSourceInternalAllCollectionStats::doGetNext() {
if (!_catalogDocs) {
diff --git a/src/mongo/db/pipeline/document_source_internal_all_collection_stats.h b/src/mongo/db/pipeline/document_source_internal_all_collection_stats.h
index 87217a86ab8..f3c89bbf93a 100644
--- a/src/mongo/db/pipeline/document_source_internal_all_collection_stats.h
+++ b/src/mongo/db/pipeline/document_source_internal_all_collection_stats.h
@@ -68,7 +68,10 @@ public:
}
PrivilegeVector requiredPrivileges(bool isMongos,
- bool bypassDocumentValidation) const final;
+ bool bypassDocumentValidation) const final {
+ return {
+ Privilege(ResourcePattern::forClusterResource(), ActionType::allCollectionStats)};
+ }
bool isInitialSource() const final {
return true;
diff --git a/src/mongo/db/pipeline/document_source_sharded_data_distribution.cpp b/src/mongo/db/pipeline/document_source_sharded_data_distribution.cpp
index 61cc3e44bba..8edacb97b10 100644
--- a/src/mongo/db/pipeline/document_source_sharded_data_distribution.cpp
+++ b/src/mongo/db/pipeline/document_source_sharded_data_distribution.cpp
@@ -46,7 +46,7 @@ using boost::intrusive_ptr;
using std::list;
REGISTER_DOCUMENT_SOURCE(shardedDataDistribution,
- LiteParsedDocumentSourceDefault::parse,
+ DocumentSourceShardedDataDistribution::LiteParsed::parse,
DocumentSourceShardedDataDistribution::createFromBson,
AllowedWithApiStrict::kAlways);
@@ -63,12 +63,6 @@ list<intrusive_ptr<DocumentSource>> DocumentSourceShardedDataDistribution::creat
"The $shardedDataDistribution stage must be run on the admin database",
expCtx->ns.isAdminDB() && expCtx->ns.isCollectionlessAggregateNS());
- // Add 'config.collections' as a resolved namespace
- StringMap<ExpressionContext::ResolvedNamespace> resolvedNamespaces;
- resolvedNamespaces[NamespaceString::kConfigsvrCollectionsNamespace.coll()] = {
- NamespaceString::kConfigsvrCollectionsNamespace, std::vector<BSONObj>{}};
- expCtx->setResolvedNamespaces(resolvedNamespaces);
-
static const BSONObj kAllCollStatsObj =
fromjson("{$_internalAllCollectionStats: {stats: {storageStats: {}}}}}");
static const BSONObj kGroupObj = fromjson(R"({
diff --git a/src/mongo/db/pipeline/document_source_sharded_data_distribution.h b/src/mongo/db/pipeline/document_source_sharded_data_distribution.h
index 7cf96c85972..23705cf6aeb 100644
--- a/src/mongo/db/pipeline/document_source_sharded_data_distribution.h
+++ b/src/mongo/db/pipeline/document_source_sharded_data_distribution.h
@@ -42,6 +42,29 @@ namespace DocumentSourceShardedDataDistribution {
static constexpr StringData kStageName = "$shardedDataDistribution"_sd;
+class LiteParsed final : public LiteParsedDocumentSource {
+public:
+ static std::unique_ptr<LiteParsed> parse(const NamespaceString& nss, const BSONElement& spec) {
+ return std::make_unique<LiteParsed>(spec.fieldName());
+ }
+
+ explicit LiteParsed(std::string parseTimeName)
+ : LiteParsedDocumentSource(std::move(parseTimeName)) {}
+
+ stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final {
+ return {NamespaceString::kConfigsvrCollectionsNamespace};
+ }
+
+ PrivilegeVector requiredPrivileges(bool isMongos, bool bypassDocumentValidation) const final {
+ return {
+ Privilege(ResourcePattern::forClusterResource(), ActionType::shardedDataDistribution)};
+ }
+
+ bool isInitialSource() const final {
+ return true;
+ }
+};
+
static std::list<boost::intrusive_ptr<DocumentSource>> createFromBson(
BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& expCtx);