void CommandInvocation::checkAuthorization(OperationContext* opCtx, const OpMsgRequest& request) const { // Always send an authorization event to audit log, even if OK. // Not using a scope guard because auditLogAuthEvent could conceivably throw. try { const Command* c = definition(); if (checkAuthorizationImplPreParse(opCtx, c, request)) { // Blanket authorization: don't need to check anything else. } else { try { doCheckAuthorization(opCtx); } catch (const ExceptionFor<ErrorCodes::Unauthorized>&) { namespace mmb = mutablebson; mmb::Document cmdToLog(request.body, mmb::Document::kInPlaceDisabled); c->snipForLogging(&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::auditLogAuthEvent(opCtx, this, request, e.code()); throw; } CommandHelpers::auditLogAuthEvent(opCtx, this, request, ErrorCodes::OK); }
static Status _checkAuthorizationImpl(Command* c, OperationContext* opCtx, const OpMsgRequest& request) { namespace mmb = mutablebson; auto client = opCtx->getClient(); auto dbname = request.getDatabase(); if (c->adminOnly() && dbname != "admin") { return Status(ErrorCodes::Unauthorized, str::stream() << c->getName() << " may only be run against the admin database."); } if (AuthorizationSession::get(client)->getAuthorizationManager().isAuthEnabled()) { Status status = c->checkAuthForRequest(opCtx, request); if (status == ErrorCodes::Unauthorized) { mmb::Document cmdToLog(request.body, mmb::Document::kInPlaceDisabled); c->redactForLogging(&cmdToLog); return Status(ErrorCodes::Unauthorized, str::stream() << "not authorized on " << dbname << " to execute command " << cmdToLog.toString()); } if (!status.isOK()) { return status; } } else if (c->adminOnly() && c->localHostOnlyIfNoAuth() && !client->getIsLocalHostConnection()) { return Status(ErrorCodes::Unauthorized, str::stream() << c->getName() << " must run from localhost when running db without auth"); } return Status::OK(); }
Message opCommandRequestFromOpMsgRequest(const OpMsgRequest& request) { const auto commandName = request.getCommandName(); BufBuilder builder; builder.skip(mongo::MsgData::MsgDataHeaderSize); // Leave room for message header. builder.appendStr(request.getDatabase()); builder.appendStr(commandName); // OP_COMMAND is only used when communicating with 3.4 nodes and they serialize their metadata // fields differently. In addition to field-level differences, some generic arguments are pulled // out to a metadata object, separate from the body. We do all down-conversion here so that the // rest of the code only has to deal with the current format. BSONObjBuilder metadataBuilder; // Will be appended to the message after we finish the body. { BSONObjBuilder bodyBuilder(builder); for (auto elem : request.body) { const auto fieldName = elem.fieldNameStringData(); if (fieldName == "$configServerState") { metadataBuilder.appendAs(elem, "configsvr"); } else if (fieldName == "$readPreference") { BSONObjBuilder ssmBuilder(metadataBuilder.subobjStart("$ssm")); ssmBuilder.append(elem); ssmBuilder.append("$secondaryOk", uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(elem)) .canRunOnSecondary()); } else if (fieldName == "$db") { // skip } else if (fieldGoesInMetadata(commandName, fieldName)) { metadataBuilder.append(elem); } else { bodyBuilder.append(elem); } } for (auto&& seq : request.sequences) { invariant(seq.name.find('.') == std::string::npos); // Only support top-level for now. dassert(!bodyBuilder.asTempObj().hasField(seq.name)); bodyBuilder.append(seq.name, seq.objs); } } metadataBuilder.obj().appendSelfToBufBuilder(builder); MsgData::View msg = builder.buf(); msg.setLen(builder.len()); msg.setOperation(dbCommand); return Message(builder.release()); }
void CommandHelpers::auditLogAuthEvent(OperationContext* opCtx, const CommandInvocation* invocation, const OpMsgRequest& request, ErrorCodes::Error err) { class Hook final : public audit::CommandInterface { public: explicit Hook(const CommandInvocation* invocation, const NamespaceString* nss) : _invocation(invocation), _nss(nss) {} void snipForLogging(mutablebson::Document* cmdObj) const override { if (_invocation) { _invocation->definition()->snipForLogging(cmdObj); } } StringData sensitiveFieldName() const override { if (_invocation) { return _invocation->definition()->sensitiveFieldName(); } return StringData{}; } StringData getName() const override { if (!_invocation) { return "Error"_sd; } return _invocation->definition()->getName(); } NamespaceString ns() const override { return *_nss; } bool redactArgs() const override { return !_invocation; } private: const CommandInvocation* _invocation; const NamespaceString* _nss; }; NamespaceString nss = invocation ? invocation->ns() : NamespaceString(request.getDatabase()); audit::logCommandAuthzCheck(opCtx->getClient(), request, Hook(invocation, &nss), err); }
void waitForReadConcern(OperationContext* opCtx, const CommandInvocation* invocation, const OpMsgRequest& request) const override { Status rcStatus = mongo::waitForReadConcern( opCtx, repl::ReadConcernArgs::get(opCtx), invocation->allowsAfterClusterTime()); if (!rcStatus.isOK()) { if (rcStatus == ErrorCodes::ExceededTimeLimit) { const int debugLevel = serverGlobalParams.clusterRole == ClusterRole::ConfigServer ? 0 : 2; LOG(debugLevel) << "Command on database " << request.getDatabase() << " timed out waiting for read concern to be satisfied. Command: " << redact(ServiceEntryPointCommon::getRedactedCopyForLogging( invocation->definition(), request.body)); } uassertStatusOK(rcStatus); } }
bool BasicCommand::enhancedRun(OperationContext* opCtx, const OpMsgRequest& request, BSONObjBuilder& result) { uassertNoDocumentSequences(request); return run(opCtx, request.getDatabase().toString(), request.body, result); }
Status BasicCommand::checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) const { uassertNoDocumentSequences(request); return checkAuthForOperation(opCtx, request.getDatabase().toString(), request.body); }
Invocation(Command* cmd, const OpMsgRequest& request) : CommandInvocation(cmd), _request(uassertStatusOK( GetMoreRequest::parseFromBSON(request.getDatabase().toString(), request.body))) {}