summaryrefslogtreecommitdiff
path: root/src/mongo/db/commands.cpp
diff options
context:
space:
mode:
authorBilly Donahue <billy.donahue@mongodb.com>2018-05-16 16:05:56 -0400
committerBilly Donahue <billy.donahue@mongodb.com>2018-05-25 11:23:23 -0400
commitf2e762dc80e63fa47bd4c1d48e05f628464b0f54 (patch)
treea559db1391671a24b73e8a6bfe9cd52dbe11cebe /src/mongo/db/commands.cpp
parentec2b67ac05f7aaa05b990e18cd7c23109a2e6eb7 (diff)
downloadmongo-f2e762dc80e63fa47bd4c1d48e05f628464b0f54.tar.gz
SERVER-34653 don't parse if early auth-checks can reject.
Diffstat (limited to 'src/mongo/db/commands.cpp')
-rw-r--r--src/mongo/db/commands.cpp154
1 files changed, 101 insertions, 53 deletions
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<ErrorCodes::Unauthorized>& 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<ErrorCodes::Unauthorized>&) {
+ 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<ErrorCodes::Unauthorized>& 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<ErrorCodes::Unauthorized>&) {
- 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) {