diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2021-11-09 20:20:06 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-12-20 18:46:05 +0000 |
commit | 68827214ba8746851a29454b4ef405ef629d7721 (patch) | |
tree | 69e11d36840a96cbfa122664626a39c48b4f8e81 | |
parent | b53fc88485f274d9b20cc93b53058f77d8859d93 (diff) | |
download | mongo-68827214ba8746851a29454b4ef405ef629d7721.tar.gz |
SERVER-61616 Add allowedWithTenant() command property
-rw-r--r-- | jstests/auth/security_token.js | 11 | ||||
-rw-r--r-- | src/mongo/db/commands.h | 7 | ||||
-rw-r--r-- | src/mongo/db/commands/connection_status.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/commands/dbcommands.cpp | 26 | ||||
-rw-r--r-- | src/mongo/db/commands/generic.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/commands/generic_servers.cpp | 31 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 4 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_build_info.cpp | 28 |
8 files changed, 93 insertions, 40 deletions
diff --git a/jstests/auth/security_token.js b/jstests/auth/security_token.js index a1a0c6c2d3d..e0972118af7 100644 --- a/jstests/auth/security_token.js +++ b/jstests/auth/security_token.js @@ -112,16 +112,17 @@ function runTest(conn, enabled, rst = undefined) { // Negative test, logMessage requires logMessage privilege on cluster (not granted) assert.commandFailed(tokenDB.runCommand({logMessage: 'This is a test'})); - // Positive test, writing to a new collection. - assert.writeOK(tokenConn.getDB('test').coll1.insert({x: 1})); + // CRUD operations not yet supported in multitenancy using security token. + assert.writeError(tokenConn.getDB('test').coll1.insert({x: 1})); const log = checkLog.getGlobalLog(conn).map((l) => JSON.parse(l)); - // We performed 4 commands as a token auth'd user. - // We should see four post-operation logout events. + // We successfully dispatched 2 commands as a token auth'd user. + // The failed commands did not dispatch because they are forbidden in multitenancy. + // We should see two post-operation logout events. const logoutMessages = log.filter((l) => (l.id === kLogoutMessageID)); assert.eq(logoutMessages.length, - 4, + 2, 'Unexpected number of logout messages: ' + tojson(logoutMessages)); // None of those authorization sessions should remain active into their next requests. diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h index 146ca75dca7..778c62cb2b6 100644 --- a/src/mongo/db/commands.h +++ b/src/mongo/db/commands.h @@ -579,6 +579,13 @@ public: return true; } + /** + * By default, no newly created command is permitted under multitenancy. + * Implementations must override this to true to permit use. + */ + virtual bool allowedWithSecurityToken() const { + return false; + } /** * Get the authorization contract for this command. nullptr means no contract has been diff --git a/src/mongo/db/commands/connection_status.cpp b/src/mongo/db/commands/connection_status.cpp index da2090b9d1b..1e688d2274b 100644 --- a/src/mongo/db/commands/connection_status.cpp +++ b/src/mongo/db/commands/connection_status.cpp @@ -114,6 +114,10 @@ public: return false; } + bool allowedWithSecurityToken() const final { + return true; + } + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { return AllowedOnSecondary::kAlways; } diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index 78b5f25c93c..72720f43f44 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -715,24 +715,30 @@ class CmdBuildInfo : public BasicCommand { public: CmdBuildInfo() : BasicCommand("buildInfo", "buildinfo") {} - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { return AllowedOnSecondary::kAlways; } - bool requiresAuth() const override { + bool requiresAuth() const final { return false; } - virtual bool adminOnly() const { + bool adminOnly() const final { return false; } - virtual bool supportsWriteConcern(const BSONObj& cmd) const override { + + bool allowedWithSecurityToken() const final { + return true; + } + + bool supportsWriteConcern(const BSONObj& cmd) const final { return false; } - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) const {} // No auth required - std::string help() const override { + + void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) const final {} // No auth required + std::string help() const final { return "get version #, etc.\n" "{ buildinfo:1 }"; } @@ -740,13 +746,13 @@ public: bool run(OperationContext* opCtx, const std::string& dbname, const BSONObj& jsobj, - BSONObjBuilder& result) { + BSONObjBuilder& result) final { VersionInfoInterface::instance().appendBuildInfo(&result); appendStorageEngineList(opCtx->getServiceContext(), &result); return true; } - Future<void> runAsync(std::shared_ptr<RequestExecutionContext> rec, std::string) override { + Future<void> runAsync(std::shared_ptr<RequestExecutionContext> rec, std::string) final { auto opCtx = rec->getOpCtx(); return BuildInfoExecutor::get(opCtx->getServiceContext())->schedule(std::move(rec)); } diff --git a/src/mongo/db/commands/generic.cpp b/src/mongo/db/commands/generic.cpp index 1a6e03f1f94..e472bed49d7 100644 --- a/src/mongo/db/commands/generic.cpp +++ b/src/mongo/db/commands/generic.cpp @@ -54,32 +54,36 @@ using std::vector; class PingCommand : public PingCmdVersion1Gen<PingCommand> { public: - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { return AllowedOnSecondary::kAlways; } - std::string help() const override { + + std::string help() const final { return "a way to check that the server is alive. responds immediately even if server is " "in a db lock."; } - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) const {} // No auth required - virtual bool requiresAuth() const override { + + bool requiresAuth() const final { return false; } + + bool allowedWithSecurityToken() const final { + return true; + } + class Invocation final : public InvocationBaseGen { public: using InvocationBaseGen::InvocationBaseGen; - virtual bool supportsWriteConcern() const override { + bool supportsWriteConcern() const final { return false; } - virtual bool allowsAfterClusterTime() const override { + bool allowsAfterClusterTime() const final { return false; } NamespaceString ns() const override { return NamespaceString(request().getDbName()); } - virtual Reply typedRun(OperationContext* opCtx) override { + Reply typedRun(OperationContext* opCtx) final { // IMPORTANT: Don't put anything in here that might lock db - including authentication return Reply{}; } diff --git a/src/mongo/db/commands/generic_servers.cpp b/src/mongo/db/commands/generic_servers.cpp index 72ed3f745dd..17df971cd85 100644 --- a/src/mongo/db/commands/generic_servers.cpp +++ b/src/mongo/db/commands/generic_servers.cpp @@ -54,12 +54,17 @@ namespace mongo { namespace { -template <typename RequestT, bool AdminOnly = true> -class GenericTC : public TypedCommand<GenericTC<RequestT, AdminOnly>> { +struct AdminOnlyNoTenant { + static constexpr bool kAdminOnly = true; + static constexpr bool kAllowedWithSecurityToken = false; +}; + +template <typename RequestT, typename Traits = AdminOnlyNoTenant> +class GenericTC : public TypedCommand<GenericTC<RequestT, Traits>> { public: using Request = RequestT; using Reply = typename RequestT::Reply; - using TC = TypedCommand<GenericTC<RequestT, AdminOnly>>; + using TC = TypedCommand<GenericTC<RequestT, Traits>>; class Invocation final : public TC::InvocationBase { public: @@ -79,7 +84,11 @@ public: }; bool adminOnly() const final { - return AdminOnly; + return Traits::kAdminOnly; + } + + bool allowedWithSecurityToken() const final { + return Traits::kAllowedWithSecurityToken; } typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { @@ -87,8 +96,13 @@ public: } }; +struct AnyDbAllowTenant { + static constexpr bool kAdminOnly = false; + static constexpr bool kAllowedWithSecurityToken = true; +}; + // { features: 1 } -using FeaturesCmd = GenericTC<FeaturesCommand, false>; +using FeaturesCmd = GenericTC<FeaturesCommand, AnyDbAllowTenant>; template <> void FeaturesCmd::Invocation::doCheckAuthorization(OperationContext* opCtx) const { if (request().getOidReset().value_or(false)) { @@ -116,8 +130,13 @@ FeaturesReply FeaturesCmd::Invocation::typedRun(OperationContext*) { } FeaturesCmd featuresCmd; +struct AnyDbNoTenant { + static constexpr bool kAdminOnly = false; + static constexpr bool kAllowedWithSecurityToken = false; +}; + // { hostInfo: 1 } -using HostInfoCmd = GenericTC<HostInfoCommand, false>; +using HostInfoCmd = GenericTC<HostInfoCommand, AnyDbNoTenant>; template <> void HostInfoCmd::Invocation::doCheckAuthorization(OperationContext* opCtx) const { auto* as = AuthorizationSession::get(opCtx->getClient()); diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 8fa9892aacf..1dd97bef48b 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -1290,6 +1290,10 @@ void ExecCommandDatabase::_initiateCommand() { }); rpc::readRequestMetadata(opCtx, request, command->requiresAuth()); + uassert(ErrorCodes::Unauthorized, + str::stream() << "Command " << command->getName() + << " is not supported in multitenancy mode", + command->allowedWithSecurityToken() || auth::getSecurityToken(opCtx) == boost::none); _tokenAuthorizationSessionGuard.emplace(opCtx); rpc::TrackingMetadata::get(opCtx).initWithOperName(command->getName()); diff --git a/src/mongo/s/commands/cluster_build_info.cpp b/src/mongo/s/commands/cluster_build_info.cpp index 0cf74905f7b..309c1b1c3cd 100644 --- a/src/mongo/s/commands/cluster_build_info.cpp +++ b/src/mongo/s/commands/cluster_build_info.cpp @@ -68,23 +68,31 @@ class ClusterCmdBuildInfo : public BasicCommand { public: ClusterCmdBuildInfo() : BasicCommand("buildInfo", "buildinfo") {} - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { return AllowedOnSecondary::kAlways; } - bool requiresAuth() const override { + bool requiresAuth() const final { return false; } - virtual bool adminOnly() const { + + bool adminOnly() const final { return false; } - virtual bool supportsWriteConcern(const BSONObj& cmd) const override { + + bool allowedWithSecurityToken() const { + return true; + } + + bool supportsWriteConcern(const BSONObj& cmd) const final { return false; } - virtual void addRequiredPrivileges(const std::string& dbname, - const BSONObj& cmdObj, - std::vector<Privilege>* out) const {} // No auth required - std::string help() const override { + + void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) const final {} // No auth required + + std::string help() const final { return "get version #, etc.\n" "{ buildinfo:1 }"; } @@ -92,12 +100,12 @@ public: bool run(OperationContext* opCtx, const std::string& dbname, const BSONObj& jsobj, - BSONObjBuilder& result) { + BSONObjBuilder& result) final { VersionInfoInterface::instance().appendBuildInfo(&result); return true; } - Future<void> runAsync(std::shared_ptr<RequestExecutionContext> rec, std::string) override { + Future<void> runAsync(std::shared_ptr<RequestExecutionContext> rec, std::string) final { auto opCtx = rec->getOpCtx(); return ClusterBuildInfoExecutor::get(opCtx->getServiceContext())->schedule(std::move(rec)); } |