diff options
author | Isabella Siu <isabella.siu@10gen.com> | 2018-12-21 10:54:32 -0500 |
---|---|---|
committer | Isabella Siu <isabella.siu@10gen.com> | 2019-01-08 13:12:37 -0500 |
commit | 580c64d93dab07f3cd3d42d6c9aefc4b9cbc40a0 (patch) | |
tree | e2d1a166382fb15a0672b1b1791eb52b2775850f /src | |
parent | d155173622a2160471610749eff59f7a15003489 (diff) | |
download | mongo-580c64d93dab07f3cd3d42d6c9aefc4b9cbc40a0.tar.gz |
SERVER-38587 Extract authorization logic from OpObserverImpl to new AuthOpObserver
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/SConscript | 16 | ||||
-rw-r--r-- | src/mongo/db/auth/SConscript | 30 | ||||
-rw-r--r-- | src/mongo/db/auth/auth_op_observer.cpp | 221 | ||||
-rw-r--r-- | src/mongo/db/auth/auth_op_observer.h | 153 | ||||
-rw-r--r-- | src/mongo/db/auth/auth_op_observer_test.cpp | 166 | ||||
-rw-r--r-- | src/mongo/db/db.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl.cpp | 106 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl_test.cpp | 40 | ||||
-rw-r--r-- | src/mongo/db/op_observer_util.cpp | 93 | ||||
-rw-r--r-- | src/mongo/db/op_observer_util.h | 46 |
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); +} |