summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsabella Siu <isabella.siu@10gen.com>2018-12-21 10:54:32 -0500
committerIsabella Siu <isabella.siu@10gen.com>2019-01-08 13:12:37 -0500
commit580c64d93dab07f3cd3d42d6c9aefc4b9cbc40a0 (patch)
treee2d1a166382fb15a0672b1b1791eb52b2775850f
parentd155173622a2160471610749eff59f7a15003489 (diff)
downloadmongo-580c64d93dab07f3cd3d42d6c9aefc4b9cbc40a0.tar.gz
SERVER-38587 Extract authorization logic from OpObserverImpl to new AuthOpObserver
-rw-r--r--src/mongo/SConscript1
-rw-r--r--src/mongo/db/SConscript16
-rw-r--r--src/mongo/db/auth/SConscript30
-rw-r--r--src/mongo/db/auth/auth_op_observer.cpp221
-rw-r--r--src/mongo/db/auth/auth_op_observer.h153
-rw-r--r--src/mongo/db/auth/auth_op_observer_test.cpp166
-rw-r--r--src/mongo/db/db.cpp2
-rw-r--r--src/mongo/db/op_observer_impl.cpp106
-rw-r--r--src/mongo/db/op_observer_impl_test.cpp40
-rw-r--r--src/mongo/db/op_observer_util.cpp93
-rw-r--r--src/mongo/db/op_observer_util.h46
11 files changed, 730 insertions, 144 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 87531817195..dd29b675bc4 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -316,6 +316,7 @@ mongod = env.Program(
'$BUILD_DIR/third_party/shim_snappy',
'base',
'db/auth/authmongod',
+ 'db/auth/auth_op_observer',
'db/background',
'db/bson/dotted_path_support',
'db/catalog/catalog_impl',
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 21295d47a74..6d95b8856e5 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -836,6 +836,21 @@ env.Library(
)
env.Library(
+ target="op_observer_util",
+ source=[
+ "op_observer_util.cpp",
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/base',
+ ],
+ LIBDEPS_PRIVATE=[
+ 'catalog/collection_options',
+ 'index/index_descriptor',
+ ],
+)
+
+
+env.Library(
target="op_observer_impl",
source=[
"op_observer_impl.cpp",
@@ -843,6 +858,7 @@ env.Library(
LIBDEPS=[
'catalog/collection_options',
'op_observer',
+ 'op_observer_util',
'repl/oplog',
's/sharding_api_d',
'views/views_mongod',
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index c86b421d8b7..83b1597c06d 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -58,6 +58,36 @@ env.Library(
)
env.Library(
+ target='auth_op_observer',
+ source=[
+ "auth_op_observer.cpp",
+ ],
+ LIBDEPS_PRIVATE=[
+ 'auth',
+ '$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/catalog/collection_options',
+ '$BUILD_DIR/mongo/db/index/index_descriptor',
+ '$BUILD_DIR/mongo/db/op_observer',
+ '$BUILD_DIR/mongo/db/op_observer_util',
+ '$BUILD_DIR/mongo/db/s/sharding_api_d',
+ ]
+)
+
+env.CppUnitTest(
+ target='auth_op_observer_test',
+ source='auth_op_observer_test.cpp',
+ LIBDEPS=[
+ 'authmocks',
+ 'auth_op_observer',
+ '$BUILD_DIR/mongo/db/common',
+ '$BUILD_DIR/mongo/db/repl/oplog',
+ '$BUILD_DIR/mongo/db/repl/oplog_interface_local',
+ '$BUILD_DIR/mongo/db/repl/replmocks',
+ '$BUILD_DIR/mongo/db/service_context_d_test_fixture',
+ ],
+)
+
+env.Library(
target='user',
source=[
'user.cpp',
diff --git a/src/mongo/db/auth/auth_op_observer.cpp b/src/mongo/db/auth/auth_op_observer.cpp
new file mode 100644
index 00000000000..6f45185a47a
--- /dev/null
+++ b/src/mongo/db/auth/auth_op_observer.cpp
@@ -0,0 +1,221 @@
+
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/auth/auth_op_observer.h"
+
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/catalog/collection_options.h"
+#include "mongo/db/op_observer_util.h"
+#include "mongo/db/operation_context.h"
+
+namespace mongo {
+
+namespace {
+
+const auto documentKeyDecoration = OperationContext::declareDecoration<BSONObj>();
+
+} // namespace
+
+AuthOpObserver::AuthOpObserver() = default;
+
+AuthOpObserver::~AuthOpObserver() = default;
+
+void AuthOpObserver::onInserts(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ std::vector<InsertStatement>::const_iterator first,
+ std::vector<InsertStatement>::const_iterator last,
+ bool fromMigrate) {
+ for (auto it = first; it != last; it++) {
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "i", nss, it->doc, nullptr);
+ }
+}
+
+void AuthOpObserver::onUpdate(OperationContext* opCtx, const OplogUpdateEntryArgs& args) {
+ if (args.updateArgs.update.isEmpty()) {
+ return;
+ }
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "u", args.nss, args.updateArgs.update, &args.updateArgs.criteria);
+}
+
+BSONObj AuthOpObserver::getDocumentKey(OperationContext* opCtx,
+ NamespaceString const& nss,
+ BSONObj const& doc) {
+ auto metadata = CollectionShardingState::get(opCtx, nss)->getMetadataForOperation(opCtx);
+ return metadata->extractDocumentKey(doc).getOwned();
+}
+
+void AuthOpObserver::aboutToDelete(OperationContext* opCtx,
+ NamespaceString const& nss,
+ BSONObj const& doc) {
+ documentKeyDecoration(opCtx) = getDocumentKey(opCtx, nss, doc);
+}
+
+void AuthOpObserver::onDelete(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ StmtId stmtId,
+ bool fromMigrate,
+ const boost::optional<BSONObj>& deletedDoc) {
+ auto& documentKey = documentKeyDecoration(opCtx);
+ invariant(!documentKey.isEmpty());
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "d", nss, documentKey, nullptr);
+}
+
+void AuthOpObserver::onCreateCollection(OperationContext* opCtx,
+ Collection* coll,
+ const NamespaceString& collectionName,
+ const CollectionOptions& options,
+ const BSONObj& idIndex,
+ const OplogSlot& createOpTime) {
+ const auto cmdNss = collectionName.getCommandNS();
+
+ const auto cmdObj = makeCreateCollCmdObj(collectionName, options, idIndex);
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
+}
+
+void AuthOpObserver::onCollMod(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ const BSONObj& collModCmd,
+ const CollectionOptions& oldCollOptions,
+ boost::optional<TTLCollModInfo> ttlInfo) {
+ const auto cmdNss = nss.getCommandNS();
+
+ // Create the 'o' field object.
+ const auto cmdObj = makeCollModCmdObj(collModCmd, oldCollOptions, ttlInfo);
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
+}
+
+void AuthOpObserver::onDropDatabase(OperationContext* opCtx, const std::string& dbName) {
+ const NamespaceString cmdNss{dbName, "$cmd"};
+ const auto cmdObj = BSON("dropDatabase" << 1);
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
+}
+
+repl::OpTime AuthOpObserver::onDropCollection(OperationContext* opCtx,
+ const NamespaceString& collectionName,
+ OptionalCollectionUUID uuid,
+ const CollectionDropType dropType) {
+ const auto cmdNss = collectionName.getCommandNS();
+ const auto cmdObj = BSON("drop" << collectionName.coll());
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
+
+ return {};
+}
+
+void AuthOpObserver::onDropIndex(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ const std::string& indexName,
+ const BSONObj& indexInfo) {
+ const auto cmdNss = nss.getCommandNS();
+ const auto cmdObj = BSON("dropIndexes" << nss.coll() << "index" << indexName);
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, cmdObj, &indexInfo);
+}
+
+void AuthOpObserver::postRenameCollection(OperationContext* const opCtx,
+ const NamespaceString& fromCollection,
+ const NamespaceString& toCollection,
+ OptionalCollectionUUID uuid,
+ OptionalCollectionUUID dropTargetUUID,
+ bool stayTemp) {
+ const auto cmdNss = fromCollection.getCommandNS();
+
+ BSONObjBuilder builder;
+ builder.append("renameCollection", fromCollection.ns());
+ builder.append("to", toCollection.ns());
+ builder.append("stayTemp", stayTemp);
+ if (dropTargetUUID) {
+ dropTargetUUID->appendToBuilder(&builder, "dropTarget");
+ }
+
+ const auto cmdObj = builder.done();
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
+}
+
+void AuthOpObserver::onRenameCollection(OperationContext* const opCtx,
+ const NamespaceString& fromCollection,
+ const NamespaceString& toCollection,
+ OptionalCollectionUUID uuid,
+ OptionalCollectionUUID dropTargetUUID,
+ bool stayTemp) {
+ postRenameCollection(opCtx, fromCollection, toCollection, uuid, dropTargetUUID, stayTemp);
+}
+
+void AuthOpObserver::onApplyOps(OperationContext* opCtx,
+ const std::string& dbName,
+ const BSONObj& applyOpCmd) {
+ const NamespaceString cmdNss{dbName, "$cmd"};
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, applyOpCmd, nullptr);
+}
+
+void AuthOpObserver::onEmptyCapped(OperationContext* opCtx,
+ const NamespaceString& collectionName,
+ OptionalCollectionUUID uuid) {
+ const auto cmdNss = collectionName.getCommandNS();
+ const auto cmdObj = BSON("emptycapped" << collectionName.coll());
+
+ AuthorizationManager::get(opCtx->getServiceContext())
+ ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
+}
+
+void AuthOpObserver::onReplicationRollback(OperationContext* opCtx,
+ const RollbackObserverInfo& rbInfo) {
+ // Invalidate any in-memory auth data if necessary.
+ const auto& rollbackNamespaces = rbInfo.rollbackNamespaces;
+ if (rollbackNamespaces.count(AuthorizationManager::versionCollectionNamespace) == 1 ||
+ rollbackNamespaces.count(AuthorizationManager::usersCollectionNamespace) == 1 ||
+ rollbackNamespaces.count(AuthorizationManager::rolesCollectionNamespace) == 1) {
+ AuthorizationManager::get(opCtx->getServiceContext())->invalidateUserCache(opCtx);
+ }
+}
+
+
+} // namespace mongo
diff --git a/src/mongo/db/auth/auth_op_observer.h b/src/mongo/db/auth/auth_op_observer.h
new file mode 100644
index 00000000000..b0aaa09e0cd
--- /dev/null
+++ b/src/mongo/db/auth/auth_op_observer.h
@@ -0,0 +1,153 @@
+
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/db/op_observer.h"
+
+namespace mongo {
+
+/**
+ * OpObserver for authentication. Observes all secondary replication traffic and filters down to
+ * relevant entries for authentication.
+ */
+class AuthOpObserver final : public OpObserver {
+ MONGO_DISALLOW_COPYING(AuthOpObserver);
+
+public:
+ AuthOpObserver();
+ ~AuthOpObserver();
+
+ void onCreateIndex(OperationContext* opCtx,
+ const NamespaceString& nss,
+ CollectionUUID uuid,
+ BSONObj indexDoc,
+ bool fromMigrate) final {}
+
+ void onInserts(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ std::vector<InsertStatement>::const_iterator first,
+ std::vector<InsertStatement>::const_iterator last,
+ bool fromMigrate) final;
+
+ void onUpdate(OperationContext* opCtx, const OplogUpdateEntryArgs& args) final;
+
+ void aboutToDelete(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const BSONObj& doc) final;
+
+ void onDelete(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ StmtId stmtId,
+ bool fromMigrate,
+ const boost::optional<BSONObj>& deletedDoc) final;
+
+ void onInternalOpMessage(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const boost::optional<UUID> uuid,
+ const BSONObj& msgObj,
+ const boost::optional<BSONObj> o2MsgObj) final {}
+
+ void onCreateCollection(OperationContext* opCtx,
+ Collection* coll,
+ const NamespaceString& collectionName,
+ const CollectionOptions& options,
+ const BSONObj& idIndex,
+ const OplogSlot& createOpTime) final;
+
+ void onCollMod(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ const BSONObj& collModCmd,
+ const CollectionOptions& oldCollOptions,
+ boost::optional<TTLCollModInfo> ttlInfo) final;
+
+ void onDropDatabase(OperationContext* opCtx, const std::string& dbName) final;
+
+ repl::OpTime onDropCollection(OperationContext* opCtx,
+ const NamespaceString& collectionName,
+ OptionalCollectionUUID uuid,
+ CollectionDropType dropType) final;
+
+ void onDropIndex(OperationContext* opCtx,
+ const NamespaceString& nss,
+ OptionalCollectionUUID uuid,
+ const std::string& indexName,
+ const BSONObj& indexInfo) final;
+
+ void onRenameCollection(OperationContext* opCtx,
+ const NamespaceString& fromCollection,
+ const NamespaceString& toCollection,
+ OptionalCollectionUUID uuid,
+ OptionalCollectionUUID dropTargetUUID,
+ bool stayTemp) final;
+
+ repl::OpTime preRenameCollection(OperationContext* opCtx,
+ const NamespaceString& fromCollection,
+ const NamespaceString& toCollection,
+ OptionalCollectionUUID uuid,
+ OptionalCollectionUUID dropTargetUUID,
+ bool stayTemp) final {
+ return repl::OpTime();
+ }
+ void postRenameCollection(OperationContext* opCtx,
+ const NamespaceString& fromCollection,
+ const NamespaceString& toCollection,
+ OptionalCollectionUUID uuid,
+ OptionalCollectionUUID dropTargetUUID,
+ bool stayTemp) final;
+ void onApplyOps(OperationContext* opCtx,
+ const std::string& dbName,
+ const BSONObj& applyOpCmd) final;
+
+ void onEmptyCapped(OperationContext* opCtx,
+ const NamespaceString& collectionName,
+ OptionalCollectionUUID uuid) final;
+
+ void onTransactionCommit(OperationContext* opCtx,
+ boost::optional<OplogSlot> commitOplogEntryOpTime,
+ boost::optional<Timestamp> commitTimestamp) final {}
+
+ void onTransactionPrepare(OperationContext* opCtx, const OplogSlot& prepareOpTime) final {}
+
+ void onTransactionAbort(OperationContext* opCtx,
+ boost::optional<OplogSlot> abortOplogEntryOpTime) final {}
+
+ void onReplicationRollback(OperationContext* opCtx, const RollbackObserverInfo& rbInfo);
+
+ // Contains the fields of the document that are in the collection's shard key, and "_id".
+ static BSONObj getDocumentKey(OperationContext* opCtx,
+ NamespaceString const& nss,
+ BSONObj const& doc);
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/auth/auth_op_observer_test.cpp b/src/mongo/db/auth/auth_op_observer_test.cpp
new file mode 100644
index 00000000000..e8180f935a4
--- /dev/null
+++ b/src/mongo/db/auth/auth_op_observer_test.cpp
@@ -0,0 +1,166 @@
+
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/auth/auth_op_observer.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/client.h"
+#include "mongo/db/concurrency/locker_noop.h"
+#include "mongo/db/db_raii.h"
+#include "mongo/db/dbdirectclient.h"
+#include "mongo/db/keys_collection_client_sharded.h"
+#include "mongo/db/keys_collection_manager.h"
+#include "mongo/db/logical_clock.h"
+#include "mongo/db/logical_time_validator.h"
+#include "mongo/db/repl/oplog.h"
+#include "mongo/db/repl/oplog_interface_local.h"
+#include "mongo/db/repl/repl_client_info.h"
+#include "mongo/db/repl/replication_coordinator_mock.h"
+#include "mongo/db/repl/storage_interface_mock.h"
+#include "mongo/db/service_context_d_test_fixture.h"
+#include "mongo/db/session_catalog_mongod.h"
+#include "mongo/db/storage/ephemeral_for_test/ephemeral_for_test_recovery_unit.h"
+#include "mongo/db/transaction_participant.h"
+#include "mongo/s/config_server_test_fixture.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/util/clock_source_mock.h"
+
+namespace mongo {
+namespace {
+
+using repl::OplogEntry;
+using unittest::assertGet;
+
+class AuthOpObserverTest : public ServiceContextMongoDTest {
+public:
+ void setUp() override {
+ // Set up mongod.
+ ServiceContextMongoDTest::setUp();
+
+ auto service = getServiceContext();
+ auto opCtx = cc().makeOperationContext();
+ repl::StorageInterface::set(service, stdx::make_unique<repl::StorageInterfaceMock>());
+
+ // Set up ReplicationCoordinator and create oplog.
+ repl::ReplicationCoordinator::set(
+ service,
+ stdx::make_unique<repl::ReplicationCoordinatorMock>(service, createReplSettings()));
+ repl::setOplogCollectionName(service);
+ repl::createOplog(opCtx.get());
+
+ // Ensure that we are primary.
+ auto replCoord = repl::ReplicationCoordinator::get(opCtx.get());
+ ASSERT_OK(replCoord->setFollowerMode(repl::MemberState::RS_PRIMARY));
+ }
+
+private:
+ // Creates a reasonable set of ReplSettings for most tests. We need to be able to
+ // override this to create a larger oplog.
+ virtual repl::ReplSettings createReplSettings() {
+ repl::ReplSettings settings;
+ settings.setOplogSizeBytes(5 * 1024 * 1024);
+ settings.setReplSetString("mySet/node1:12345");
+ return settings;
+ }
+};
+
+TEST_F(AuthOpObserverTest, OnRollbackInvalidatesAuthCacheWhenAuthNamespaceRolledBack) {
+ AuthOpObserver opObserver;
+ auto opCtx = cc().makeOperationContext();
+ auto authMgr = AuthorizationManager::get(getServiceContext());
+ auto initCacheGen = authMgr->getCacheGeneration();
+
+ // Verify that the rollback op observer invalidates the user cache for each auth namespace by
+ // checking that the cache generation changes after a call to the rollback observer method.
+ auto nss = AuthorizationManager::rolesCollectionNamespace;
+ OpObserver::RollbackObserverInfo rbInfo;
+ rbInfo.rollbackNamespaces = {AuthorizationManager::rolesCollectionNamespace};
+ opObserver.onReplicationRollback(opCtx.get(), rbInfo);
+ ASSERT_NE(initCacheGen, authMgr->getCacheGeneration());
+
+ initCacheGen = authMgr->getCacheGeneration();
+ rbInfo.rollbackNamespaces = {AuthorizationManager::usersCollectionNamespace};
+ opObserver.onReplicationRollback(opCtx.get(), rbInfo);
+ ASSERT_NE(initCacheGen, authMgr->getCacheGeneration());
+
+ initCacheGen = authMgr->getCacheGeneration();
+ rbInfo.rollbackNamespaces = {AuthorizationManager::versionCollectionNamespace};
+ opObserver.onReplicationRollback(opCtx.get(), rbInfo);
+ ASSERT_NE(initCacheGen, authMgr->getCacheGeneration());
+}
+
+TEST_F(AuthOpObserverTest, OnRollbackDoesntInvalidateAuthCacheWhenNoAuthNamespaceRolledBack) {
+ AuthOpObserver opObserver;
+ auto opCtx = cc().makeOperationContext();
+ auto authMgr = AuthorizationManager::get(getServiceContext());
+ auto initCacheGen = authMgr->getCacheGeneration();
+
+ // Verify that the rollback op observer doesn't invalidate the user cache.
+ auto nss = AuthorizationManager::rolesCollectionNamespace;
+ OpObserver::RollbackObserverInfo rbInfo;
+ opObserver.onReplicationRollback(opCtx.get(), rbInfo);
+ auto newCacheGen = authMgr->getCacheGeneration();
+ ASSERT_EQ(newCacheGen, initCacheGen);
+}
+
+TEST_F(AuthOpObserverTest, MultipleAboutToDeleteAndOnDelete) {
+ auto uuid = UUID::gen();
+ AuthOpObserver opObserver;
+ auto opCtx = cc().makeOperationContext();
+ NamespaceString nss = {"test", "coll"};
+ AutoGetDb autoDb(opCtx.get(), nss.db(), MODE_X);
+ WriteUnitOfWork wunit(opCtx.get());
+ opObserver.aboutToDelete(opCtx.get(), nss, BSON("_id" << 1));
+ opObserver.onDelete(opCtx.get(), nss, uuid, {}, false, {});
+ opObserver.aboutToDelete(opCtx.get(), nss, BSON("_id" << 1));
+ opObserver.onDelete(opCtx.get(), nss, uuid, {}, false, {});
+}
+
+DEATH_TEST_F(AuthOpObserverTest, AboutToDeleteMustPreceedOnDelete, "invariant") {
+ AuthOpObserver opObserver;
+ auto opCtx = cc().makeOperationContext();
+ opCtx->swapLockState(stdx::make_unique<LockerNoop>());
+ NamespaceString nss = {"test", "coll"};
+ opObserver.onDelete(opCtx.get(), nss, {}, {}, false, {});
+}
+
+DEATH_TEST_F(AuthOpObserverTest, EachOnDeleteRequiresAboutToDelete, "invariant") {
+ AuthOpObserver opObserver;
+ auto opCtx = cc().makeOperationContext();
+ opCtx->swapLockState(stdx::make_unique<LockerNoop>());
+ NamespaceString nss = {"test", "coll"};
+ opObserver.aboutToDelete(opCtx.get(), nss, {});
+ opObserver.onDelete(opCtx.get(), nss, {}, {}, false, {});
+ opObserver.onDelete(opCtx.get(), nss, {}, {}, false, {});
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp
index aadd70c2ab4..1a990445279 100644
--- a/src/mongo/db/db.cpp
+++ b/src/mongo/db/db.cpp
@@ -47,6 +47,7 @@
#include "mongo/client/replica_set_monitor.h"
#include "mongo/config.h"
#include "mongo/db/audit.h"
+#include "mongo/db/auth/auth_op_observer.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/sasl_options.h"
#include "mongo/db/catalog/collection.h"
@@ -279,6 +280,7 @@ ExitCode _initAndListen(int listenPort) {
auto opObserverRegistry = stdx::make_unique<OpObserverRegistry>();
opObserverRegistry->addObserver(stdx::make_unique<OpObserverShardingImpl>());
opObserverRegistry->addObserver(stdx::make_unique<UUIDCatalogObserver>());
+ opObserverRegistry->addObserver(stdx::make_unique<AuthOpObserver>());
if (serverGlobalParams.clusterRole == ClusterRole::ShardServer) {
opObserverRegistry->addObserver(stdx::make_unique<ShardServerOpObserver>());
diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp
index c67b51c8717..d9086af8939 100644
--- a/src/mongo/db/op_observer_impl.cpp
+++ b/src/mongo/db/op_observer_impl.cpp
@@ -33,7 +33,6 @@
#include "mongo/db/op_observer_impl.h"
#include "mongo/bson/bsonobjbuilder.h"
-#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/catalog/collection_catalog_entry.h"
#include "mongo/db/catalog/collection_options.h"
#include "mongo/db/catalog/database.h"
@@ -47,6 +46,7 @@
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/logical_time_validator.h"
#include "mongo/db/namespace_string.h"
+#include "mongo/db/op_observer_util.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/repl/oplog.h"
#include "mongo/db/repl/oplog_entry_gen.h"
@@ -130,36 +130,6 @@ void onWriteOpCompleted(OperationContext* opCtx,
txnState);
}
-/**
- * Given a raw collMod command object and associated collection metadata, create and return the
- * object for the 'o' field of a collMod oplog entry. For TTL index updates, we make sure the oplog
- * entry always stores the index name, instead of a key pattern.
- */
-BSONObj makeCollModCmdObj(const BSONObj& collModCmd,
- const CollectionOptions& oldCollOptions,
- boost::optional<TTLCollModInfo> ttlInfo) {
- BSONObjBuilder cmdObjBuilder;
- std::string ttlIndexFieldName = "index";
-
- // Add all fields from the original collMod command.
- for (auto elem : collModCmd) {
- // We normalize all TTL collMod oplog entry objects to use the index name, even if the
- // command used an index key pattern.
- if (elem.fieldNameStringData() == ttlIndexFieldName && ttlInfo) {
- BSONObjBuilder ttlIndexObjBuilder;
- ttlIndexObjBuilder.append("name", ttlInfo->indexName);
- ttlIndexObjBuilder.append("expireAfterSeconds",
- durationCount<Seconds>(ttlInfo->expireAfterSeconds));
-
- cmdObjBuilder.append(ttlIndexFieldName, ttlIndexObjBuilder.obj());
- } else {
- cmdObjBuilder.append(elem);
- }
- }
-
- return cmdObjBuilder.obj();
-}
-
Date_t getWallClockTimeForOpLog(OperationContext* opCtx) {
auto const clockSource = opCtx->getServiceContext()->getFastClockSource();
return clockSource->now();
@@ -410,8 +380,6 @@ void OpObserverImpl::onInserts(OperationContext* opCtx,
size_t index = 0;
for (auto it = first; it != last; it++, index++) {
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "i", nss, it->doc, nullptr);
auto opTime = opTimeList.empty() ? repl::OpTime() : opTimeList[index];
shardObserveInsertOp(opCtx, nss, it->doc, opTime, fromMigrate, inMultiDocumentTransaction);
}
@@ -472,9 +440,6 @@ void OpObserverImpl::onUpdate(OperationContext* opCtx, const OplogUpdateEntryArg
boost::none);
}
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "u", args.nss, args.updateArgs.update, &args.updateArgs.criteria);
-
if (args.nss != NamespaceString::kSessionTransactionsTableNamespace) {
if (!args.updateArgs.fromMigrate) {
shardObserveUpdateOp(opCtx,
@@ -536,9 +501,6 @@ void OpObserverImpl::onDelete(OperationContext* opCtx,
boost::none);
}
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "d", nss, documentKey, nullptr);
-
if (nss != NamespaceString::kSessionTransactionsTableNamespace) {
if (!fromMigrate) {
shardObserveDeleteOp(opCtx,
@@ -594,26 +556,7 @@ void OpObserverImpl::onCreateCollection(OperationContext* opCtx,
const OplogSlot& createOpTime) {
const auto cmdNss = collectionName.getCommandNS();
- BSONObjBuilder b;
- b.append("create", collectionName.coll().toString());
- {
- // Don't store the UUID as part of the options, but instead only at the top level
- CollectionOptions optionsToStore = options;
- optionsToStore.uuid.reset();
- b.appendElements(optionsToStore.toBSON());
- }
-
- // Include the full _id index spec in the oplog for index versions >= 2.
- if (!idIndex.isEmpty()) {
- auto versionElem = idIndex[IndexDescriptor::kIndexVersionFieldName];
- invariant(versionElem.isNumber());
- if (IndexDescriptor::IndexVersion::kV2 <=
- static_cast<IndexDescriptor::IndexVersion>(versionElem.numberInt())) {
- b.append("idIndex", idIndex);
- }
- }
-
- const auto cmdObj = b.done();
+ const auto cmdObj = makeCreateCollCmdObj(collectionName, options, idIndex);
if (!collectionName.isSystemDotProfile()) {
// do not replicate system.profile modifications
@@ -632,9 +575,6 @@ void OpObserverImpl::onCreateCollection(OperationContext* opCtx,
createOpTime);
}
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
-
if (options.uuid) {
opCtx->recoveryUnit()->onRollback([opCtx, collectionName]() {
NamespaceUUIDCache::get(opCtx).evictNamespace(collectionName);
@@ -680,9 +620,6 @@ void OpObserverImpl::onCollMod(OperationContext* opCtx,
OplogSlot());
}
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
-
// Make sure the UUID values in the Collection metadata, the Collection object, and the UUID
// catalog are all present and equal.
invariant(opCtx->lockState()->isDbLockedForMode(nss.db(), MODE_X));
@@ -726,9 +663,6 @@ void OpObserverImpl::onDropDatabase(OperationContext* opCtx, const std::string&
}
NamespaceUUIDCache::get(opCtx).evictNamespacesInDatabase(dbName);
-
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
}
repl::OpTime OpObserverImpl::onDropCollection(OperationContext* opCtx,
@@ -765,9 +699,6 @@ repl::OpTime OpObserverImpl::onDropCollection(OperationContext* opCtx,
MongoDSessionCatalog::invalidateSessions(opCtx, boost::none);
}
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
-
// Evict namespace entry from the namespace/uuid cache if it exists.
NamespaceUUIDCache::get(opCtx).evictNamespace(collectionName);
@@ -795,9 +726,6 @@ void OpObserverImpl::onDropIndex(OperationContext* opCtx,
{},
false /* prepare */,
OplogSlot());
-
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, cmdObj, &indexInfo);
}
@@ -842,26 +770,11 @@ void OpObserverImpl::postRenameCollection(OperationContext* const opCtx,
OptionalCollectionUUID uuid,
OptionalCollectionUUID dropTargetUUID,
bool stayTemp) {
- const auto cmdNss = fromCollection.getCommandNS();
-
- BSONObjBuilder builder;
- builder.append("renameCollection", fromCollection.ns());
- builder.append("to", toCollection.ns());
- builder.append("stayTemp", stayTemp);
- if (dropTargetUUID) {
- dropTargetUUID->appendToBuilder(&builder, "dropTarget");
- }
-
- const auto cmdObj = builder.done();
-
if (fromCollection.isSystemDotViews())
DurableViewCatalog::onExternalChange(opCtx, fromCollection);
if (toCollection.isSystemDotViews())
DurableViewCatalog::onExternalChange(opCtx, toCollection);
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
-
// Evict namespace entry from the namespace/uuid cache if it exists.
NamespaceUUIDCache& cache = NamespaceUUIDCache::get(opCtx);
cache.evictNamespace(fromCollection);
@@ -888,9 +801,6 @@ void OpObserverImpl::onApplyOps(OperationContext* opCtx,
// Only transactional 'applyOps' commands can be prepared.
constexpr bool prepare = false;
replLogApplyOps(opCtx, cmdNss, applyOpCmd, {}, kUninitializedStmtId, {}, prepare, OplogSlot());
-
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, applyOpCmd, nullptr);
}
void OpObserverImpl::onEmptyCapped(OperationContext* opCtx,
@@ -915,9 +825,6 @@ void OpObserverImpl::onEmptyCapped(OperationContext* opCtx,
false /* prepare */,
OplogSlot());
}
-
- AuthorizationManager::get(opCtx->getServiceContext())
- ->logOp(opCtx, "c", cmdNss, cmdObj, nullptr);
}
namespace {
@@ -1113,15 +1020,6 @@ void OpObserverImpl::onTransactionAbort(OperationContext* opCtx,
void OpObserverImpl::onReplicationRollback(OperationContext* opCtx,
const RollbackObserverInfo& rbInfo) {
-
- // Invalidate any in-memory auth data if necessary.
- const auto& rollbackNamespaces = rbInfo.rollbackNamespaces;
- if (rollbackNamespaces.count(AuthorizationManager::versionCollectionNamespace) == 1 ||
- rollbackNamespaces.count(AuthorizationManager::usersCollectionNamespace) == 1 ||
- rollbackNamespaces.count(AuthorizationManager::rolesCollectionNamespace) == 1) {
- AuthorizationManager::get(opCtx->getServiceContext())->invalidateUserCache(opCtx);
- }
-
// If there were ops rolled back that were part of operations on a session, then invalidate
// the session cache.
if (rbInfo.rollbackSessionIds.size() > 0) {
diff --git a/src/mongo/db/op_observer_impl_test.cpp b/src/mongo/db/op_observer_impl_test.cpp
index 08afeb96bd0..d73610ade4b 100644
--- a/src/mongo/db/op_observer_impl_test.cpp
+++ b/src/mongo/db/op_observer_impl_test.cpp
@@ -30,7 +30,6 @@
#include "mongo/platform/basic.h"
-#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/client.h"
#include "mongo/db/concurrency/locker_noop.h"
#include "mongo/db/db_raii.h"
@@ -377,45 +376,6 @@ TEST_F(OpObserverSessionCatalogRollbackTest,
}
}
-TEST_F(OpObserverTest, OnRollbackInvalidatesAuthCacheWhenAuthNamespaceRolledBack) {
- OpObserverImpl opObserver;
- auto opCtx = cc().makeOperationContext();
- auto authMgr = AuthorizationManager::get(getServiceContext());
- auto initCacheGen = authMgr->getCacheGeneration();
-
- // Verify that the rollback op observer invalidates the user cache for each auth namespace by
- // checking that the cache generation changes after a call to the rollback observer method.
- auto nss = AuthorizationManager::rolesCollectionNamespace;
- OpObserver::RollbackObserverInfo rbInfo;
- rbInfo.rollbackNamespaces = {AuthorizationManager::rolesCollectionNamespace};
- opObserver.onReplicationRollback(opCtx.get(), rbInfo);
- ASSERT_NE(initCacheGen, authMgr->getCacheGeneration());
-
- initCacheGen = authMgr->getCacheGeneration();
- rbInfo.rollbackNamespaces = {AuthorizationManager::usersCollectionNamespace};
- opObserver.onReplicationRollback(opCtx.get(), rbInfo);
- ASSERT_NE(initCacheGen, authMgr->getCacheGeneration());
-
- initCacheGen = authMgr->getCacheGeneration();
- rbInfo.rollbackNamespaces = {AuthorizationManager::versionCollectionNamespace};
- opObserver.onReplicationRollback(opCtx.get(), rbInfo);
- ASSERT_NE(initCacheGen, authMgr->getCacheGeneration());
-}
-
-TEST_F(OpObserverTest, OnRollbackDoesntInvalidateAuthCacheWhenNoAuthNamespaceRolledBack) {
- OpObserverImpl opObserver;
- auto opCtx = cc().makeOperationContext();
- auto authMgr = AuthorizationManager::get(getServiceContext());
- auto initCacheGen = authMgr->getCacheGeneration();
-
- // Verify that the rollback op observer doesn't invalidate the user cache.
- auto nss = AuthorizationManager::rolesCollectionNamespace;
- OpObserver::RollbackObserverInfo rbInfo;
- opObserver.onReplicationRollback(opCtx.get(), rbInfo);
- auto newCacheGen = authMgr->getCacheGeneration();
- ASSERT_EQ(newCacheGen, initCacheGen);
-}
-
TEST_F(OpObserverTest, MultipleAboutToDeleteAndOnDelete) {
auto uuid = UUID::gen();
OpObserverImpl opObserver;
diff --git a/src/mongo/db/op_observer_util.cpp b/src/mongo/db/op_observer_util.cpp
new file mode 100644
index 00000000000..02621a00b3a
--- /dev/null
+++ b/src/mongo/db/op_observer_util.cpp
@@ -0,0 +1,93 @@
+
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/op_observer_util.h"
+
+#include "mongo/db/index/index_descriptor.h"
+
+namespace mongo {
+
+/**
+ * Given a raw collMod command object and associated collection metadata, create and return the
+ * object for the 'o' field of a collMod oplog entry. For TTL index updates, we make sure the oplog
+ * entry always stores the index name, instead of a key pattern.
+ */
+BSONObj makeCollModCmdObj(const BSONObj& collModCmd,
+ const CollectionOptions& oldCollOptions,
+ boost::optional<TTLCollModInfo> ttlInfo) {
+ BSONObjBuilder cmdObjBuilder;
+ std::string ttlIndexFieldName = "index";
+
+ // Add all fields from the original collMod command.
+ for (auto elem : collModCmd) {
+ // We normalize all TTL collMod oplog entry objects to use the index name, even if the
+ // command used an index key pattern.
+ if (elem.fieldNameStringData() == ttlIndexFieldName && ttlInfo) {
+ BSONObjBuilder ttlIndexObjBuilder;
+ ttlIndexObjBuilder.append("name", ttlInfo->indexName);
+ ttlIndexObjBuilder.append("expireAfterSeconds",
+ durationCount<Seconds>(ttlInfo->expireAfterSeconds));
+
+ cmdObjBuilder.append(ttlIndexFieldName, ttlIndexObjBuilder.obj());
+ } else {
+ cmdObjBuilder.append(elem);
+ }
+ }
+
+ return cmdObjBuilder.obj();
+}
+
+BSONObj makeCreateCollCmdObj(const NamespaceString& collectionName,
+ const CollectionOptions& options,
+ const BSONObj& idIndex) {
+ BSONObjBuilder b;
+ b.append("create", collectionName.coll().toString());
+ {
+ // Don't store the UUID as part of the options, but instead only at the top level
+ CollectionOptions optionsToStore = options;
+ optionsToStore.uuid.reset();
+ b.appendElements(optionsToStore.toBSON());
+ }
+
+ // Include the full _id index spec in the oplog for index versions >= 2.
+ if (!idIndex.isEmpty()) {
+ auto versionElem = idIndex[IndexDescriptor::kIndexVersionFieldName];
+ invariant(versionElem.isNumber());
+ if (IndexDescriptor::IndexVersion::kV2 <=
+ static_cast<IndexDescriptor::IndexVersion>(versionElem.numberInt())) {
+ b.append("idIndex", idIndex);
+ }
+ }
+
+ return b.obj();
+}
+} // namespace mongo
diff --git a/src/mongo/db/op_observer_util.h b/src/mongo/db/op_observer_util.h
new file mode 100644
index 00000000000..16f3a4b1250
--- /dev/null
+++ b/src/mongo/db/op_observer_util.h
@@ -0,0 +1,46 @@
+
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/db/catalog/collection_options.h"
+#include "mongo/db/namespace_string.h"
+#include "mongo/db/op_observer.h"
+
+namespace mongo {
+
+BSONObj makeCreateCollCmdObj(const NamespaceString& collectionName,
+ const CollectionOptions& options,
+ const BSONObj& idIndex);
+
+BSONObj makeCollModCmdObj(const BSONObj& collModCmd,
+ const CollectionOptions& oldCollOptions,
+ boost::optional<TTLCollModInfo> ttlInfo);
+}