From f2e762dc80e63fa47bd4c1d48e05f628464b0f54 Mon Sep 17 00:00:00 2001 From: Billy Donahue Date: Wed, 16 May 2018 16:05:56 -0400 Subject: SERVER-34653 don't parse if early auth-checks can reject. --- src/mongo/db/commands.cpp | 154 ++++++++++++++++++++++++++++++---------------- 1 file changed, 101 insertions(+), 53 deletions(-) (limited to 'src/mongo/db/commands.cpp') diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 30e064221d9..edb6893aac3 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -45,6 +45,7 @@ #include "mongo/db/auth/privilege.h" #include "mongo/db/client.h" #include "mongo/db/command_generic_argument.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/curop.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" @@ -54,6 +55,7 @@ #include "mongo/util/invariant.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" +#include "mongo/util/scopeguard.h" namespace mongo { @@ -69,6 +71,62 @@ const WriteConcernOptions kMajorityWriteConcern( WriteConcernOptions::SyncMode::UNSET, Seconds(60)); +// Returns true if found to be authorized, false if undecided. Throws if unauthorized. +bool checkAuthorizationImplPreParse(OperationContext* opCtx, + const Command* command, + const OpMsgRequest& request) { + auto client = opCtx->getClient(); + if (client->isInDirectClient()) + return true; + uassert(ErrorCodes::Unauthorized, + str::stream() << command->getName() << " may only be run against the admin database.", + !command->adminOnly() || request.getDatabase() == NamespaceString::kAdminDb); + + auto authzSession = AuthorizationSession::get(client); + if (!authzSession->getAuthorizationManager().isAuthEnabled()) { + // Running without auth, so everything should be allowed except remotely invoked + // commands that have the 'localHostOnlyIfNoAuth' restriction. + uassert(ErrorCodes::Unauthorized, + str::stream() << command->getName() + << " must run from localhost when running db without auth", + !command->adminOnly() || !command->localHostOnlyIfNoAuth() || + client->getIsLocalHostConnection()); + return true; // Blanket authorization: don't need to check anything else. + } + if (authzSession->isUsingLocalhostBypass()) + return false; // Still can't decide on auth because of the localhost bypass. + uassert(ErrorCodes::Unauthorized, + str::stream() << "command " << command->getName() << " requires authentication", + !command->requiresAuth() || authzSession->isAuthenticated()); + return false; +} + +void auditLogAuthEventImpl(OperationContext* opCtx, + const Command* command, + const NamespaceString& nss, + const OpMsgRequest& request, + ErrorCodes::Error err) { + class Hook final : public audit::CommandInterface { + public: + explicit Hook(const Command* command, const NamespaceString* nss) + : _command(command), _nss(nss) {} + + void redactForLogging(mutablebson::Document* cmdObj) const override { + _command->redactForLogging(cmdObj); + } + + NamespaceString ns() const override { + return *_nss; + } + + private: + const Command* _command; + const NamespaceString* _nss; + }; + + audit::logCommandAuthzCheck(opCtx->getClient(), request, Hook(command, &nss), err); +} + } // namespace @@ -96,26 +154,18 @@ BSONObj CommandHelpers::runCommandDirectly(OperationContext* opCtx, const OpMsgR return BSONObj(bb.release()); } -void CommandHelpers::logAuthViolation(OperationContext* opCtx, - const CommandInvocation* invocation, - const OpMsgRequest& request, - ErrorCodes::Error err) { - struct Hook final : public audit::CommandInterface { - public: - explicit Hook(const CommandInvocation* invocation) : _invocation{invocation} {} - - void redactForLogging(mutablebson::Document* cmdObj) const override { - _invocation->definition()->redactForLogging(cmdObj); - } - - NamespaceString ns() const override { - return _invocation->ns(); - } +void CommandHelpers::auditLogAuthEvent(OperationContext* opCtx, + const CommandInvocation* invocation, + const OpMsgRequest& request, + ErrorCodes::Error err) { + auditLogAuthEventImpl(opCtx, invocation->definition(), invocation->ns(), request, err); +} - private: - const CommandInvocation* _invocation; - }; - audit::logCommandAuthzCheck(opCtx->getClient(), request, Hook(invocation), err); +void CommandHelpers::auditLogAuthEvent(OperationContext* opCtx, + const Command* command, + const OpMsgRequest& request, + ErrorCodes::Error err) { + auditLogAuthEventImpl(opCtx, command, NamespaceString(request.getDatabase()), request, err); } void CommandHelpers::uassertNoDocumentSequences(StringData commandName, @@ -325,6 +375,17 @@ bool CommandHelpers::isHelpRequest(const BSONElement& helpElem) { return !helpElem.eoo() && helpElem.trueValue(); } +bool CommandHelpers::uassertShouldAttemptParse(OperationContext* opCtx, + const Command* command, + const OpMsgRequest& request) { + try { + return checkAuthorizationImplPreParse(opCtx, command, request); + } catch (const ExceptionFor& e) { + CommandHelpers::auditLogAuthEvent(opCtx, command, request, e.code()); + throw; + } +} + constexpr StringData CommandHelpers::kHelpFieldName; ////////////////////////////////////////////////////////////// @@ -352,12 +413,29 @@ CommandInvocation::~CommandInvocation() = default; void CommandInvocation::checkAuthorization(OperationContext* opCtx, const OpMsgRequest& request) const { + // Always send an authorization event to audit log, even if OK. + ErrorCodes::Error auditCode = ErrorCodes::OK; + auto auditCodeGuard = + MakeGuard([&] { CommandHelpers::auditLogAuthEvent(opCtx, this, request, auditCode); }); try { - _checkAuthorizationImpl(opCtx, request); - CommandHelpers::logAuthViolation(opCtx, this, request, ErrorCodes::OK); + const Command* c = definition(); + if (checkAuthorizationImplPreParse(opCtx, c, request)) { + return; // Blanket authorization: don't need to check anything else. + } + try { + doCheckAuthorization(opCtx); + } catch (const ExceptionFor&) { + namespace mmb = mutablebson; + mmb::Document cmdToLog(request.body, mmb::Document::kInPlaceDisabled); + c->redactForLogging(&cmdToLog); + auto dbname = request.getDatabase(); + uasserted(ErrorCodes::Unauthorized, + str::stream() << "not authorized on " << dbname << " to execute command " + << redact(cmdToLog.getObject())); + } } catch (const DBException& e) { log(LogComponent::kAccessControl) << e.toStatus(); - CommandHelpers::logAuthViolation(opCtx, this, request, e.code()); + auditCode = e.code(); throw; } } @@ -380,7 +458,7 @@ private: bool ok = _command->run(opCtx, _dbName, _request->body, bob); CommandHelpers::appendSimpleCommandStatus(bob, ok); } catch (const ExceptionFor& e) { - CommandHelpers::logAuthViolation(opCtx, this, *_request, e.code()); + CommandHelpers::auditLogAuthEvent(opCtx, this, *_request, e.code()); throw; } } @@ -459,36 +537,6 @@ Status BasicCommand::checkAuthForCommand(Client* client, return Status(ErrorCodes::Unauthorized, "unauthorized"); } -void CommandInvocation::_checkAuthorizationImpl(OperationContext* opCtx, - const OpMsgRequest& request) const { - const Command* c = definition(); - auto client = opCtx->getClient(); - auto dbname = request.getDatabase(); - uassert(ErrorCodes::Unauthorized, - str::stream() << c->getName() << " may only be run against the admin database.", - !c->adminOnly() || dbname == NamespaceString::kAdminDb); - if (!AuthorizationSession::get(client)->getAuthorizationManager().isAuthEnabled()) { - // Running without auth, so everything should be allowed except remotely invoked commands - // that have the 'localHostOnlyIfNoAuth' restriction. - uassert(ErrorCodes::Unauthorized, - str::stream() << c->getName() - << " must run from localhost when running db without auth", - !c->adminOnly() || !c->localHostOnlyIfNoAuth() || - client->getIsLocalHostConnection()); - return; // Blanket authorization: don't need to check anything else. - } - try { - doCheckAuthorization(opCtx); - } catch (const ExceptionFor&) { - namespace mmb = mutablebson; - mmb::Document cmdToLog(request.body, mmb::Document::kInPlaceDisabled); - c->redactForLogging(&cmdToLog); - uasserted(ErrorCodes::Unauthorized, - str::stream() << "not authorized on " << dbname << " to execute command " - << redact(cmdToLog.getObject())); - } -} - void Command::generateHelpResponse(OperationContext* opCtx, rpc::ReplyBuilderInterface* replyBuilder, const Command& command) { -- cgit v1.2.1