diff options
author | Jonathan Reams <jbreams@mongodb.com> | 2018-08-23 17:39:45 -0400 |
---|---|---|
committer | Jonathan Reams <jbreams@mongodb.com> | 2018-10-01 15:43:49 -0400 |
commit | d3c4ed928630e646bfc07af0ecc2201432ab22d7 (patch) | |
tree | 50377673d30e3e56a30b5fb4a7849196fd3bb9b6 /src | |
parent | b757d87fc622d44b16013e2722832a655d7ca052 (diff) | |
download | mongo-d3c4ed928630e646bfc07af0ecc2201432ab22d7.tar.gz |
SERVER-31552 Allow users to be pinned in memory
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/auth/SConscript | 5 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 8 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_impl.cpp | 351 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_impl.h | 37 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_test.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_test.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.h | 28 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_d.cpp | 29 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_d.h | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/user_cache_invalidator_job.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/commands/sleep_command.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 248 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/repl/oplog.cpp | 5 | ||||
-rw-r--r-- | src/mongo/embedded/embedded_auth_manager.cpp | 10 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_user_management_commands.cpp | 26 | ||||
-rw-r--r-- | src/mongo/s/server.cpp | 2 |
17 files changed, 593 insertions, 227 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index a8f89e223b1..dd21cdc2811 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -49,7 +49,7 @@ env.Library( 'authorization_session.cpp', 'auth_decorations.cpp', 'user_name.cpp', - 'role_name.cpp' + 'role_name.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', @@ -314,6 +314,9 @@ env.Library( '$BUILD_DIR/mongo/db/repl/repl_coordinator_interface', '$BUILD_DIR/mongo/db/server_parameters', ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/db/concurrency/lock_manager', + ], ) env.Library( diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 2c19797aad3..ad2c4b629c1 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -264,12 +264,12 @@ public: /** * Marks the given user as invalid and removes it from the user cache. */ - virtual void invalidateUserByName(const UserName& user) = 0; + virtual void invalidateUserByName(OperationContext* opCtx, const UserName& user) = 0; /** * Invalidates all users who's source is "dbname" and removes them from the user cache. */ - virtual void invalidateUsersFromDB(StringData dbname) = 0; + virtual void invalidateUsersFromDB(OperationContext* opCtx, StringData dbname) = 0; /** * Initializes the authorization manager. Depending on what version the authorization @@ -281,7 +281,7 @@ public: /** * Invalidates all of the contents of the user cache. */ - virtual void invalidateUserCache() = 0; + virtual void invalidateUserCache(OperationContext* opCtx) = 0; /** * Parses privDoc and fully initializes the user object (credentials, roles, and privileges) @@ -311,6 +311,8 @@ public: }; virtual std::vector<CachedUserInfo> getUserCacheInfo() const = 0; + + virtual void setInUserManagementCommand(OperationContext* opCtx, bool val) = 0; }; } // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index be6c356495b..c13e3594301 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -54,6 +54,7 @@ #include "mongo/db/auth/sasl_options.h" #include "mongo/db/auth/user.h" #include "mongo/db/auth/user_document_parser.h" +#include "mongo/db/auth/user_management_commands_parser.h" #include "mongo/db/auth/user_name.h" #include "mongo/db/auth/user_name_hash.h" #include "mongo/db/global_settings.h" @@ -112,6 +113,112 @@ MONGO_INITIALIZER_GENERAL(SetupInternalSecurityUser, } MONGO_EXPORT_STARTUP_SERVER_PARAMETER(authorizationManagerCacheSize, int, 100); + +class PinnedUserSetParameter final : public ServerParameter { +public: + PinnedUserSetParameter() + : ServerParameter( + ServerParameterSet::getGlobal(), "authorizationManagerPinnedUsers", true, true) {} + + void append(OperationContext* opCtx, BSONObjBuilder& b, const std::string& name) override { + BSONArrayBuilder sub(b.subarrayStart(name)); + for (const auto& username : _userNames) { + BSONObjBuilder nameObj(sub.subobjStart()); + nameObj << AuthorizationManager::USER_NAME_FIELD_NAME << username.getUser() + << AuthorizationManager::USER_DB_FIELD_NAME << username.getDB(); + } + } + + Status set(const BSONElement& newValueElement) override { + if (newValueElement.type() == String) { + return setFromString(newValueElement.valuestrsafe()); + } else if (newValueElement.type() == Array) { + auto array = static_cast<BSONArray>(newValueElement.embeddedObject()); + std::vector<UserName> out; + auto status = auth::parseUserNamesFromBSONArray(array, "", &out); + if (!status.isOK()) + return status; + + status = _checkForSystemUser(out); + if (!status.isOK()) { + return status; + } + + stdx::unique_lock<stdx::mutex> lk(_mutex); + std::swap(_userNames, out); + auto authzManager = _authzManager; + if (!authzManager) { + return Status::OK(); + } + + lk.unlock(); + + authzManager->invalidateUserCache(Client::getCurrent()->getOperationContext()); + return Status::OK(); + } else { + return {ErrorCodes::BadValue, + "authorizationManagerPinnedUsers must be either a string or a BSON array"}; + } + } + + Status setFromString(const std::string& str) override { + std::vector<std::string> strList; + splitStringDelim(str, &strList, ','); + + std::vector<UserName> out; + for (const auto& nameStr : strList) { + auto swUserName = UserName::parse(nameStr); + if (!swUserName.isOK()) { + return swUserName.getStatus(); + } + out.push_back(std::move(swUserName.getValue())); + } + + auto status = _checkForSystemUser(out); + if (!status.isOK()) { + return status; + } + + auto authzManager = _authzManager; + if (!authzManager) { + return Status::OK(); + } + + stdx::unique_lock<stdx::mutex> lk(_mutex); + std::swap(out, _userNames); + lk.unlock(); + + authzManager->invalidateUserCache(Client::getCurrent()->getOperationContext()); + return Status::OK(); + } + + void setAuthzManager(AuthorizationManager* authzManager) { + stdx::lock_guard<stdx::mutex> lk(_mutex); + _authzManager = authzManager; + } + + std::vector<UserName> getUserNames() { + stdx::lock_guard<stdx::mutex> lk(_mutex); + return _userNames; + } + +private: + Status _checkForSystemUser(const std::vector<UserName>& names) { + if (std::any_of(names.begin(), names.end(), [&](const UserName& userName) { + return (userName == internalSecurity.user->getName()); + })) { + return {ErrorCodes::BadValue, + "Cannot set __system as a pinned user, it is always pinned"}; + } + return Status::OK(); + } + stdx::mutex _mutex; + std::vector<UserName> _userNames; + AuthorizationManager* _authzManager; +} authorizationManagerPinnedUsers; + +const auto inUserManagementCommandsFlag = OperationContext::declareDecoration<bool>(); + } // namespace @@ -144,29 +251,19 @@ MONGO_REGISTER_SHIM(AuthorizationManager::create)()->std::unique_ptr<Authorizati * The cached data has an associated counter, called the cache generation. If the cache * generation changes while a guard is in fetch phase, the fetched data should not be stored * into the cache, because some invalidation event occurred during the fetch phase. - * - * NOTE: It is not safe to enter fetch phase while holding a database lock. Fetch phase - * operations are allowed to acquire database locks themselves, so entering fetch while holding - * a database lock may lead to deadlock. */ class AuthorizationManagerImpl::CacheGuard { MONGO_DISALLOW_COPYING(CacheGuard); public: - enum FetchSynchronization { fetchSynchronizationAutomatic, fetchSynchronizationManual }; - /** * Constructs a cache guard, locking the mutex that synchronizes user cache accesses. */ - CacheGuard(AuthorizationManagerImpl* authzManager, - const FetchSynchronization sync = fetchSynchronizationAutomatic) + explicit CacheGuard(OperationContext* opCtx, AuthorizationManagerImpl* authzManager) : _isThisGuardInFetchPhase(false), _authzManager(authzManager), - _lock(authzManager->_cacheMutex) { - if (fetchSynchronizationAutomatic == sync) { - synchronizeWithFetchPhase(); - } - } + _stateLock(_authzManager->_externalState->lock(opCtx)), + _lock(authzManager->_cacheWriteMutex) {} /** * Releases the mutex that synchronizes user cache access, if held, and notifies @@ -186,7 +283,7 @@ public: /** * Returns true of the authzManager reports that it is in fetch phase. */ - bool otherUpdateInFetchPhase() { + bool otherUpdateInFetchPhase() const { return _authzManager->_isFetchPhaseBusy; } @@ -203,18 +300,26 @@ public: * cache generation. */ void beginFetchPhase() { + beginFetchPhaseNoYield(); + _lock.unlock(); + } + + /** + * Sets up the fetch phase without releasing _authzManager->_cacheMutex + */ + void beginFetchPhaseNoYield() { fassert(17191, !_authzManager->_isFetchPhaseBusy); _isThisGuardInFetchPhase = true; _authzManager->_isFetchPhaseBusy = true; _startGeneration = _authzManager->_fetchGeneration; - _lock.unlock(); } /** * Exits the fetch phase, reacquiring the _authzManager->_cacheMutex. */ void endFetchPhase() { - _lock.lock(); + if (!_lock.owns_lock()) + _lock.lock(); // We do not clear _authzManager->_isFetchPhaseBusy or notify waiters until // ~CacheGuard(), for two reasons. First, there's no value to notifying the waiters // before you're ready to release the mutex, because they'll just go to sleep on the @@ -237,17 +342,10 @@ public: } private: - void synchronizeWithFetchPhase() { - while (otherUpdateInFetchPhase()) - wait(); - fassert(17192, !_authzManager->_isFetchPhaseBusy); - _isThisGuardInFetchPhase = true; - _authzManager->_isFetchPhaseBusy = true; - } - OID _startGeneration; bool _isThisGuardInFetchPhase; AuthorizationManagerImpl* _authzManager; + std::unique_ptr<AuthzManagerExternalState::StateLock> _stateLock; stdx::unique_lock<stdx::mutex> _lock; }; @@ -262,9 +360,8 @@ AuthorizationManagerImpl::AuthorizationManagerImpl( _externalState(std::move(externalState)), _version(schemaVersionInvalid), _userCache(authorizationManagerCacheSize, UserCacheInvalidator()), - _isFetchPhaseBusy(false) { - _updateCacheGeneration_inlock(); -} + _fetchGeneration(OID::gen()), + _isFetchPhaseBusy(false) {} AuthorizationManagerImpl::~AuthorizationManagerImpl() {} @@ -283,7 +380,7 @@ bool AuthorizationManagerImpl::shouldValidateAuthSchemaOnStartup() { } Status AuthorizationManagerImpl::getAuthorizationVersion(OperationContext* opCtx, int* version) { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + CacheGuard guard(opCtx, this); int newVersion = _version; if (schemaVersionInvalid == newVersion) { while (guard.otherUpdateInFetchPhase()) @@ -307,7 +404,7 @@ Status AuthorizationManagerImpl::getAuthorizationVersion(OperationContext* opCtx } OID AuthorizationManagerImpl::getCacheGeneration() { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + stdx::lock_guard<stdx::mutex> lk(_cacheWriteMutex); return _fetchGeneration; } @@ -327,6 +424,7 @@ bool AuthorizationManagerImpl::hasAnyPrivilegeDocuments(OperationContext* opCtx) } lk.unlock(); + auto stateLk = _externalState->lock(opCtx); bool privDocsExist = _externalState->hasAnyPrivilegeDocuments(opCtx); lk.lock(); @@ -377,6 +475,7 @@ Status AuthorizationManagerImpl::_initializeUserFromPrivilegeDocument(User* user Status AuthorizationManagerImpl::getUserDescription(OperationContext* opCtx, const UserName& userName, BSONObj* result) { + auto lk = _externalState->lock(opCtx); return _externalState->getUserDescription(opCtx, userName, result); } @@ -385,6 +484,7 @@ Status AuthorizationManagerImpl::getRoleDescription(OperationContext* opCtx, PrivilegeFormat privileges, AuthenticationRestrictionsFormat restrictions, BSONObj* result) { + auto lk = _externalState->lock(opCtx); return _externalState->getRoleDescription(opCtx, roleName, privileges, restrictions, result); } @@ -393,6 +493,7 @@ Status AuthorizationManagerImpl::getRolesDescription(OperationContext* opCtx, PrivilegeFormat privileges, AuthenticationRestrictionsFormat restrictions, BSONObj* result) { + auto lk = _externalState->lock(opCtx); return _externalState->getRolesDescription(opCtx, roleName, privileges, restrictions, result); } @@ -404,11 +505,13 @@ Status AuthorizationManagerImpl::getRoleDescriptionsForDB( AuthenticationRestrictionsFormat restrictions, bool showBuiltinRoles, vector<BSONObj>* result) { + auto lk = _externalState->lock(opCtx); return _externalState->getRoleDescriptionsForDB( opCtx, dbname, privileges, restrictions, showBuiltinRoles, result); } void AuthorizationManagerImpl::UserCacheInvalidator::operator()(User* user) { + LOG(1) << "Invalidating user " << user->getName().toString(); user->_invalidate(); } @@ -418,22 +521,65 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o return internalSecurity.user; } - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - boost::optional<std::shared_ptr<User>> cachedUser; + boost::optional<UserHandle> cachedUser = _userCache.get(userName); + auto returnUser = [&](boost::optional<UserHandle> cachedUser) { + auto ret = *cachedUser; + fassert(16914, ret.get()); + fassert(17003, ret->isValid()); + + LOG(1) << "Returning user " << userName << " from cache"; + return ret; + }; + + // The userCache is thread-safe, so if we can get a user out of the cache we don't need to + // take any locks! + if (cachedUser) { + return returnUser(cachedUser); + } + + // Otherwise make sure we have the locks we need and check whether and wait on another + // thread is fetching into the cache + CacheGuard guard(opCtx, this); + + auto pinnedIt = + std::find_if(_pinnedUsers.begin(), _pinnedUsers.end(), [&](const auto& userHandle) { + return (userHandle->getName() == userName); + }); + if (pinnedIt != _pinnedUsers.end()) { + return *pinnedIt; + } + while ((boost::none == (cachedUser = _userCache.get(userName))) && guard.otherUpdateInFetchPhase()) { guard.wait(); } if (cachedUser != boost::none) { - auto ret = *cachedUser; - fassert(16914, ret.get()); - fassert(17003, ret->isValid()); - return ret; + return returnUser(cachedUser); } - int authzVersion = _version; guard.beginFetchPhase(); + // If there's still no user in the cache, then we need to go to disk. Take the slow path. + LOG(1) << "Getting user " << userName << " from disk"; + auto ret = _acquireUserSlowPath(guard, opCtx, userName); + + guard.endFetchPhase(); + if (!ret.isOK()) { + return ret.getStatus(); + } + + auto user = std::move(ret.getValue()); + if (user->isValid()) { + _updateCacheGeneration_inlock(guard); + } + + return user; +} + +StatusWith<UserHandle> AuthorizationManagerImpl::_acquireUserSlowPath(CacheGuard& guard, + OperationContext* opCtx, + const UserName& userName) { + int authzVersion = _version; // Number of times to retry a user document that fetches due to transient // AuthSchemaIncompatible errors. These errors should only ever occur during and shortly @@ -475,9 +621,12 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o authzVersion = schemaVersionInvalid; } + if (!status.isOK()) return status; + // All this does is re-acquire the _cacheWriteMutex if we don't hold it already - a caller + // may also call endFetchPhase() after this returns. guard.endFetchPhase(); if (guard.isSameCacheGeneration()) { @@ -493,6 +642,72 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o } } +void AuthorizationManagerImpl::_recachePinnedUsers(CacheGuard& guard, OperationContext* opCtx) { + const auto findPinnedUser = [&](const auto& userName) { + return std::find_if(_pinnedUsers.begin(), _pinnedUsers.end(), [&](const auto& userHandle) { + return (userHandle->getName() == userName); + }); + }; + + // Get the list of users to pin + const auto usersToPin = authorizationManagerPinnedUsers.getUserNames(); + if (usersToPin.empty()) { + // If there are pinned users, clear them all out so they fall out of the cache + if (!_pinnedUsers.empty()) { + _pinnedUsers.clear(); + } + return; + } + + // Remove any users that shouldn't be pinned anymore or that are invalid. + std::vector<UserHandle> newPinnedUsers; + std::copy_if(_pinnedUsers.begin(), + _pinnedUsers.end(), + std::back_inserter(newPinnedUsers), + [&](const UserHandle& user) { + if (!user->isValid()) + return false; + return std::any_of( + usersToPin.begin(), usersToPin.end(), [&](const UserName& userName) { + return (user->getName() == userName); + }); + }); + + + while (guard.otherUpdateInFetchPhase()) { + guard.wait(); + } + + // Begin the fetch phase but don't yield any locks. We want re-pinning of users to block other + // operations until all the users are refreshed. + guard.beginFetchPhaseNoYield(); + + bool cacheUpdated = false; + for (const auto& userName : usersToPin) { + if (findPinnedUser(userName) != _pinnedUsers.end()) { + continue; + } + + cacheUpdated = true; + auto swUser = _acquireUserSlowPath(guard, opCtx, userName); + if (swUser.isOK()) { + newPinnedUsers.emplace(newPinnedUsers.end(), std::move(swUser.getValue())); + } else { + const auto& status = swUser.getStatus(); + // If the user is not found, then it might just not exist yet. Skip this user for now. + if (status != ErrorCodes::UserNotFound) { + warning() << "Unable to fetch pinned user " << userName.toString() << ": " + << status; + } + } + } + + if (cacheUpdated) + _updateCacheGeneration_inlock(guard); + + _pinnedUsers = std::move(newPinnedUsers); +} + Status AuthorizationManagerImpl::_fetchUserV2(OperationContext* opCtx, const UserName& userName, std::unique_ptr<User>* out) { @@ -513,26 +728,33 @@ Status AuthorizationManagerImpl::_fetchUserV2(OperationContext* opCtx, return Status::OK(); } -void AuthorizationManagerImpl::invalidateUserByName(const UserName& userName) { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - _updateCacheGeneration_inlock(); +void AuthorizationManagerImpl::invalidateUserByName(OperationContext* opCtx, + const UserName& userName) { + CacheGuard guard(opCtx, this); + _updateCacheGeneration_inlock(guard); _userCache.invalidate(userName); + + _recachePinnedUsers(guard, opCtx); } -void AuthorizationManagerImpl::invalidateUsersFromDB(StringData dbname) { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - _updateCacheGeneration_inlock(); +void AuthorizationManagerImpl::invalidateUsersFromDB(OperationContext* opCtx, StringData dbname) { + CacheGuard guard(opCtx, this); + _updateCacheGeneration_inlock(guard); _userCache.invalidateIf( [&](const UserName& user, const User*) { return user.getDB() == dbname; }); + + _recachePinnedUsers(guard, opCtx); } -void AuthorizationManagerImpl::invalidateUserCache() { - CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - _invalidateUserCache_inlock(); +void AuthorizationManagerImpl::invalidateUserCache(OperationContext* opCtx) { + CacheGuard guard(opCtx, this); + _invalidateUserCache_inlock(guard); + + _recachePinnedUsers(guard, opCtx); } -void AuthorizationManagerImpl::_invalidateUserCache_inlock() { - _updateCacheGeneration_inlock(); +void AuthorizationManagerImpl::_invalidateUserCache_inlock(const CacheGuard& guard) { + _updateCacheGeneration_inlock(guard); _userCache.invalidateIf([](const UserName& a, const User*) { return true; }); // Reread the schema version before acquiring the next user. @@ -540,11 +762,12 @@ void AuthorizationManagerImpl::_invalidateUserCache_inlock() { } Status AuthorizationManagerImpl::initialize(OperationContext* opCtx) { - invalidateUserCache(); Status status = _externalState->initialize(opCtx); if (!status.isOK()) return status; + authorizationManagerPinnedUsers.setAuthzManager(this); + invalidateUserCache(opCtx); return Status::OK(); } @@ -616,17 +839,26 @@ StatusWith<UserName> extractUserNameFromIdString(StringData idstr) { } // namespace -void AuthorizationManagerImpl::_updateCacheGeneration_inlock() { +void AuthorizationManagerImpl::_updateCacheGeneration_inlock(const CacheGuard&) { _fetchGeneration = OID::gen(); } -void AuthorizationManagerImpl::_invalidateRelevantCacheData(const char* op, +void AuthorizationManagerImpl::_invalidateRelevantCacheData(OperationContext* opCtx, + const char* op, const NamespaceString& ns, const BSONObj& o, const BSONObj* o2) { + // When we're doing a user management command we lock the admin DB for the duration + // of the command and invalidate the cache at the end of the command, so we don't need + // to invalidate it based on calls to logOp(). + if (inUserManagementCommandsFlag(opCtx)) { + LOG(1) << "Skipping cache invalidation in opObserver because of active user command"; + return; + } + if (ns == AuthorizationManager::rolesCollectionNamespace || ns == AuthorizationManager::versionCollectionNamespace) { - invalidateUserCache(); + invalidateUserCache(opCtx); return; } @@ -643,12 +875,12 @@ void AuthorizationManagerImpl::_invalidateRelevantCacheData(const char* op, warning() << "Invalidating user cache based on user being updated failed, will " "invalidate the entire cache instead: " << userName.getStatus(); - invalidateUserCache(); + invalidateUserCache(opCtx); return; } - invalidateUserByName(userName.getValue()); + invalidateUserByName(opCtx, userName.getValue()); } else { - invalidateUserCache(); + invalidateUserCache(opCtx); } } @@ -658,8 +890,13 @@ void AuthorizationManagerImpl::logOp(OperationContext* opCtx, const BSONObj& o, const BSONObj* o2) { if (appliesToAuthzData(op, nss, o)) { + // AuthzManagerExternalState's logOp method registers a RecoveryUnit::Change + // and to do so we need to have begun a UnitOfWork + WriteUnitOfWork wuow(opCtx); _externalState->logOp(opCtx, op, nss, o, o2); - _invalidateRelevantCacheData(op, nss, o, o2); + wuow.commit(); + + _invalidateRelevantCacheData(opCtx, op, nss, o, o2); } } @@ -676,4 +913,8 @@ std::vector<AuthorizationManager::CachedUserInfo> AuthorizationManagerImpl::getU return ret; } +void AuthorizationManagerImpl::setInUserManagementCommand(OperationContext* opCtx, bool val) { + inUserManagementCommandsFlag(opCtx) = val; +} + } // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h index c29f3790d62..d17f65cb70f 100644 --- a/src/mongo/db/auth/authorization_manager_impl.h +++ b/src/mongo/db/auth/authorization_manager_impl.h @@ -118,13 +118,13 @@ public: StatusWith<UserHandle> acquireUser(OperationContext* opCtx, const UserName& userName) override; - void invalidateUserByName(const UserName& user) override; + void invalidateUserByName(OperationContext* opCtx, const UserName& user) override; - void invalidateUsersFromDB(StringData dbname) override; + void invalidateUsersFromDB(OperationContext* opCtx, StringData dbname) override; Status initialize(OperationContext* opCtx) override; - void invalidateUserCache() override; + void invalidateUserCache(OperationContext* opCtx) override; Status _initializeUserFromPrivilegeDocument(User* user, const BSONObj& privDoc) override; @@ -136,6 +136,8 @@ public: std::vector<CachedUserInfo> getUserCacheInfo() const override; + void setInUserManagementCommand(OperationContext* opCtx, bool val) override; + private: /** * Type used to guard accesses and updates to the user cache. @@ -147,14 +149,15 @@ private: * Invalidates all User objects in the cache and removes them from the cache. * Should only be called when already holding _cacheMutex. */ - void _invalidateUserCache_inlock(); + void _invalidateUserCache_inlock(const CacheGuard&); /** * Given the objects describing an oplog entry that affects authorization data, invalidates * the portion of the user cache that is affected by that operation. Should only be called * with oplog entries that have been pre-verified to actually affect authorization data. */ - void _invalidateRelevantCacheData(const char* op, + void _invalidateRelevantCacheData(OperationContext* opCtx, + const char* op, const NamespaceString& ns, const BSONObj& o, const BSONObj* o2); @@ -162,7 +165,14 @@ private: /** * Updates _cacheGeneration to a new OID */ - void _updateCacheGeneration_inlock(); + void _updateCacheGeneration_inlock(const CacheGuard&); + + + void _recachePinnedUsers(CacheGuard& guard, OperationContext* opCtx); + + StatusWith<UserHandle> _acquireUserSlowPath(CacheGuard& guard, + OperationContext* opCtx, + const UserName& userName); /** * Fetches user information from a v2-schema user document for the named user, @@ -219,6 +229,13 @@ private: }; InvalidatingLRUCache<UserName, User, UserCacheInvalidator> _userCache; + std::vector<UserHandle> _pinnedUsers; + + /** + * Protects _cacheGeneration, _version and _isFetchPhaseBusy. Manipulated + * via CacheGuard. + */ + stdx::mutex _cacheWriteMutex; /** * Current generation of cached data. Updated every time part of the cache gets @@ -235,15 +252,11 @@ private: bool _isFetchPhaseBusy; /** - * Protects _userCache, _cacheGeneration, _version and _isFetchPhaseBusy. Manipulated - * via CacheGuard. - */ - stdx::mutex _cacheMutex; - - /** * Condition used to signal that it is OK for another CacheGuard to enter a fetch phase. * Manipulated via CacheGuard. */ stdx::condition_variable _fetchPhaseIsReady; + + AtomicBool _inUserManagementCommand{false}; }; } // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index d782ec3567b..48eee3f23e0 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -78,7 +78,7 @@ class AuthorizationManagerTest : public ServiceContextTest { public: virtual ~AuthorizationManagerTest() { if (authzManager) - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx.get()); } AuthorizationManagerTest() { @@ -265,11 +265,6 @@ private: class AuthorizationManagerWithExplicitUserPrivilegesTest : public ::mongo::unittest::Test { public: - virtual ~AuthorizationManagerWithExplicitUserPrivilegesTest() { - if (authzManager) - authzManager->invalidateUserCache(); - } - virtual void setUp() { auto localExternalState = stdx::make_unique<AuthzManagerExternalStateMockWithExplicitUserPrivileges>(); diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index cad01f09e9b..35eb7e5c56d 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -453,7 +453,7 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { BSONObj())); // Make sure that invalidating the user causes the session to reload its privileges. - authzManager->invalidateUserByName(user->getName()); + authzManager->invalidateUserByName(_opCtx.get(), user->getName()); authzSession->startRequest(_opCtx.get()); // Refreshes cached data for invalid users ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); @@ -472,7 +472,7 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { &ignored) .transitional_ignore(); // Make sure that invalidating the user causes the session to reload its privileges. - authzManager->invalidateUserByName(user->getName()); + authzManager->invalidateUserByName(_opCtx.get(), user->getName()); authzSession->startRequest(_opCtx.get()); // Refreshes cached data for invalid users ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); @@ -533,7 +533,7 @@ TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) { // Even though the user's privileges have been reduced, since we've configured user // document lookup to fail, the authz session should continue to use its known out-of-date // privilege data. - authzManager->invalidateUserByName(user->getName()); + authzManager->invalidateUserByName(_opCtx.get(), user->getName()); authzSession->startRequest(_opCtx.get()); // Refreshes cached data for invalid users ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index 3fb750a192d..8a044eb2038 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -165,7 +165,35 @@ public: const NamespaceString& ns, const BSONObj& o, const BSONObj* o2) {} + /** + * Represents a lock_guard on the storage for this implementation of the external state. + */ + class StateLock { + StateLock(StateLock&) = delete; + StateLock& operator=(StateLock&) = delete; + + public: + StateLock() = default; + virtual ~StateLock() = default; + }; + + /** + * Returns a Lock on the external state for the given operation context. + * + * By default this returns an empty/noop StateLock. + */ + virtual std::unique_ptr<StateLock> lock(OperationContext* opCtx) { + return std::make_unique<StateLock>(); + }; + /** + * Returns true if you must acquire a StateLock before fetching a user description. + * + * By default this returns false since only mongod actually needs to do locking at this level. + */ + virtual bool needsLockForUserName(OperationContext* opCtx, const UserName& user) { + return false; + } protected: AuthzManagerExternalState(); // This class should never be instantiated directly. diff --git a/src/mongo/db/auth/authz_manager_external_state_d.cpp b/src/mongo/db/auth/authz_manager_external_state_d.cpp index 11e350ee253..2c5b4a307bb 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_d.cpp @@ -59,6 +59,35 @@ AuthzManagerExternalStateMongod::makeAuthzSessionExternalState(AuthorizationMana return stdx::make_unique<AuthzSessionExternalStateMongod>(authzManager); } +class AuthzLock : public AuthzManagerExternalState::StateLock { +public: + explicit AuthzLock(OperationContext* opCtx) + : _lock(opCtx, + AuthorizationManager::usersCollectionNamespace.db(), + MODE_S, + opCtx->getDeadline()) {} + + static bool isLocked(OperationContext* opCtx); + +private: + Lock::DBLock _lock; +}; + +bool AuthzLock::isLocked(OperationContext* opCtx) { + return opCtx->lockState()->isDbLockedForMode( + AuthorizationManager::usersCollectionNamespace.db(), MODE_S); +} + +std::unique_ptr<AuthzManagerExternalState::StateLock> AuthzManagerExternalStateMongod::lock( + OperationContext* opCtx) { + return std::make_unique<AuthzLock>(opCtx); +} + +bool AuthzManagerExternalStateMongod::needsLockForUserName(OperationContext* opCtx, + const UserName& name) { + return (shouldUseRolesFromConnection(opCtx, name) == false); +} + Status AuthzManagerExternalStateMongod::query( OperationContext* opCtx, const NamespaceString& collectionName, diff --git a/src/mongo/db/auth/authz_manager_external_state_d.h b/src/mongo/db/auth/authz_manager_external_state_d.h index a620528e46f..c946d5a52cc 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.h +++ b/src/mongo/db/auth/authz_manager_external_state_d.h @@ -52,6 +52,10 @@ public: std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( AuthorizationManager* authzManager) override; + std::unique_ptr<StateLock> lock(OperationContext* opCtx) final; + + bool needsLockForUserName(OperationContext* opCtx, const UserName& name) final; + virtual Status findOne(OperationContext* opCtx, const NamespaceString& collectionName, const BSONObj& query, diff --git a/src/mongo/db/auth/user_cache_invalidator_job.cpp b/src/mongo/db/auth/user_cache_invalidator_job.cpp index 33b247af580..18dc03d3b8f 100644 --- a/src/mongo/db/auth/user_cache_invalidator_job.cpp +++ b/src/mongo/db/auth/user_cache_invalidator_job.cpp @@ -172,14 +172,23 @@ void UserCacheInvalidator::run() { << currentGeneration.getStatus(); } // When in doubt, invalidate the cache - _authzManager->invalidateUserCache(); + try { + _authzManager->invalidateUserCache(opCtx.get()); + } catch (const DBException& e) { + warning() << "Error invalidating user cache: " << e.toStatus(); + } continue; } if (currentGeneration.getValue() != _previousCacheGeneration) { log() << "User cache generation changed from " << _previousCacheGeneration << " to " << currentGeneration.getValue() << "; invalidating user cache"; - _authzManager->invalidateUserCache(); + try { + _authzManager->invalidateUserCache(opCtx.get()); + } catch (const DBException& e) { + warning() << "Error invalidating user cache: " << e.toStatus(); + } + _previousCacheGeneration = currentGeneration.getValue(); } } diff --git a/src/mongo/db/commands/sleep_command.cpp b/src/mongo/db/commands/sleep_command.cpp index ecf04ba6519..366232dbcb4 100644 --- a/src/mongo/db/commands/sleep_command.cpp +++ b/src/mongo/db/commands/sleep_command.cpp @@ -68,14 +68,24 @@ public: const BSONObj& cmdObj, std::vector<Privilege>* out) const {} - void _sleepInReadLock(mongo::OperationContext* opCtx, long long millis) { - Lock::GlobalRead lk(opCtx); - opCtx->sleepFor(Milliseconds(millis)); - } - - void _sleepInWriteLock(mongo::OperationContext* opCtx, long long millis) { - Lock::GlobalWrite lk(opCtx); - opCtx->sleepFor(Milliseconds(millis)); + void _sleepInLock(mongo::OperationContext* opCtx, + long long millis, + LockMode mode, + const StringData& ns) { + if (ns.empty()) { + Lock::GlobalLock lk(opCtx, mode, opCtx->getDeadline(), Lock::InterruptBehavior::kThrow); + opCtx->sleepFor(Milliseconds(millis)); + } else if (nsIsDbOnly(ns)) { + uassert(50961, "lockTarget is not a valid namespace", NamespaceString::validDBName(ns)); + Lock::DBLock lk(opCtx, ns, mode, opCtx->getDeadline()); + opCtx->sleepFor(Milliseconds(millis)); + } else { + uassert(50962, + "lockTarget is not a valid namespace", + NamespaceString::validCollectionComponent(ns)); + Lock::CollectionLock lk(opCtx->lockState(), ns, mode, opCtx->getDeadline()); + opCtx->sleepFor(Milliseconds(millis)); + } } CmdSleep() : BasicCommand("sleep") {} @@ -99,12 +109,17 @@ public: millis = 10 * 1000; } + StringData lockTarget; + if (cmdObj["lockTarget"]) { + lockTarget = cmdObj["lockTarget"].checkAndGetStringData(); + } + if (!cmdObj["lock"]) { // Legacy implementation if (cmdObj.getBoolField("w")) { - _sleepInWriteLock(opCtx, millis); + _sleepInLock(opCtx, millis, MODE_X, lockTarget); } else { - _sleepInReadLock(opCtx, millis); + _sleepInLock(opCtx, millis, MODE_S, lockTarget); } } else { uassert(34346, "Only one of 'w' and 'lock' may be set.", !cmdObj["w"]); @@ -113,10 +128,10 @@ public: if (lock == "none") { opCtx->sleepFor(Milliseconds(millis)); } else if (lock == "w") { - _sleepInWriteLock(opCtx, millis); + _sleepInLock(opCtx, millis, MODE_X, lockTarget); } else { uassert(34347, "'lock' must be one of 'r', 'w', 'none'.", lock == "r"); - _sleepInReadLock(opCtx, millis); + _sleepInLock(opCtx, millis, MODE_S, lockTarget); } } diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 84fff5c50a8..7f1d12cc6de 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -59,6 +59,7 @@ #include "mongo/db/client.h" #include "mongo/db/commands.h" #include "mongo/db/commands/run_aggregate.h" +#include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/jsobj.h" #include "mongo/db/operation_context.h" @@ -87,9 +88,6 @@ using std::vector; namespace { -// Used to obtain mutex that guards modifications to persistent authorization data -const auto getAuthzDataMutex = ServiceContext::declareDecoration<stdx::mutex>(); - Status useDefaultCode(const Status& status, ErrorCodes::Error defaultCode) { if (status.code() != ErrorCodes::UnknownError) return status; @@ -147,7 +145,6 @@ Status getCurrentUserRoles(OperationContext* opCtx, AuthorizationManager* authzManager, const UserName& userName, stdx::unordered_set<RoleName>* roles) { - authzManager->invalidateUserByName(userName); // Need to make sure cache entry is up to date auto swUser = authzManager->acquireUser(opCtx, userName); if (!swUser.isOK()) { return swUser.getStatus(); @@ -565,14 +562,45 @@ Status writeAuthSchemaVersionIfNeeded(OperationContext* opCtx, return status; } +class AuthzLockGuard { + AuthzLockGuard(AuthzLockGuard&) = delete; + AuthzLockGuard& operator=(AuthzLockGuard&) = delete; + +public: + AuthzLockGuard(OperationContext* opCtx, LockMode mode) + : _opCtx(opCtx), + _lock(_opCtx, + AuthorizationManager::usersCollectionNamespace.db(), + mode, + _opCtx->getDeadline()) { + auto authzMgr = AuthorizationManager::get(_opCtx->getServiceContext()); + authzMgr->setInUserManagementCommand(_opCtx, true); + } + + ~AuthzLockGuard() { + auto authzMgr = AuthorizationManager::get(_opCtx->getServiceContext()); + authzMgr->setInUserManagementCommand(_opCtx, false); + } + + AuthzLockGuard(AuthzLockGuard&&) = default; + +private: + OperationContext* _opCtx; + Lock::DBLock _lock; +}; + /** * Returns Status::OK() if the current Auth schema version is at least the auth schema version * for the MongoDB 3.0 SCRAM auth mode. * Returns an error otherwise. */ -Status requireWritableAuthSchema28SCRAM(OperationContext* opCtx, - AuthorizationManager* authzManager) { +StatusWith<AuthzLockGuard> requireWritableAuthSchema28SCRAM(OperationContext* opCtx, + AuthorizationManager* authzManager) { int foundSchemaVersion; + // We take a MODE_X lock during writes because we want to be sure that we can read any pinned + // user documents back out of the database after writing them during the user management + // commands, and to ensure only one user management command is running at a time. + AuthzLockGuard lk(opCtx, MODE_X); Status status = authzManager->getAuthorizationVersion(opCtx, &foundSchemaVersion); if (!status.isOK()) { return status; @@ -587,7 +615,12 @@ Status requireWritableAuthSchema28SCRAM(OperationContext* opCtx, << " but found " << foundSchemaVersion); } - return writeAuthSchemaVersionIfNeeded(opCtx, authzManager, foundSchemaVersion); + status = writeAuthSchemaVersionIfNeeded(opCtx, authzManager, foundSchemaVersion); + if (!status.isOK()) { + return status; + } + + return std::move(lk); } /** @@ -602,9 +635,10 @@ Status requireWritableAuthSchema28SCRAM(OperationContext* opCtx, * If records are added thinking we're at one schema level, then the default is changed, * then the auth database would wind up in an inconsistent state. */ -Status requireReadableAuthSchema26Upgrade(OperationContext* opCtx, - AuthorizationManager* authzManager) { +StatusWith<AuthzLockGuard> requireReadableAuthSchema26Upgrade(OperationContext* opCtx, + AuthorizationManager* authzManager) { int foundSchemaVersion; + AuthzLockGuard lk(opCtx, MODE_IS); Status status = authzManager->getAuthorizationVersion(opCtx, &foundSchemaVersion); if (!status.isOK()) { return status; @@ -618,7 +652,8 @@ Status requireReadableAuthSchema26Upgrade(OperationContext* opCtx, << " but found " << foundSchemaVersion); } - return Status::OK(); + + return std::move(lk); } Status buildCredentials(BSONObjBuilder* builder, const auth::CreateOrUpdateUserArgs& args) { @@ -802,6 +837,9 @@ public: ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + int authzVersion; status = authzManager->getAuthorizationVersion(opCtx, &authzVersion); uassertStatusOK(status); @@ -827,11 +865,6 @@ public: status = parser.checkValidUserDocument(userObj); uassertStatusOK(status); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); - // Role existence has to be checked after acquiring the update lock for (size_t i = 0; i < args.roles.size(); ++i) { BSONObj ignored; @@ -846,6 +879,7 @@ public: args.roles, args.authenticationRestrictions); status = insertPrivilegeDocument(opCtx, userObj); + authzManager->invalidateUserByName(opCtx, args.userName); uassertStatusOK(status); return true; } @@ -950,11 +984,9 @@ public: } ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); // Role existence has to be checked after acquiring the update lock if (args.hasRoles) { @@ -975,7 +1007,7 @@ public: status = updatePrivilegeDocument( opCtx, args.userName, queryBuilder.done(), updateDocumentBuilder.done()); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(args.userName); + authzManager->invalidateUserByName(opCtx, args.userName); uassertStatusOK(status); return true; } @@ -1020,10 +1052,9 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); audit::logDropUser(Client::getCurrent(), userName); @@ -1035,7 +1066,7 @@ public: << userName.getDB()), &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(userName); + authzManager->invalidateUserByName(opCtx, userName); uassertStatusOK(status); if (nMatched == 0) { @@ -1081,11 +1112,9 @@ public: Status status = auth::parseAndValidateDropAllUsersFromDatabaseCommand(cmdObj, dbname); uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); audit::logDropAllUsersFromDatabase(Client::getCurrent(), dbname); @@ -1093,7 +1122,7 @@ public: status = removePrivilegeDocuments( opCtx, BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname), &numRemoved); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUsersFromDB(dbname); + authzManager->invalidateUsersFromDB(opCtx, dbname); uassertStatusOK(status); result.append("n", numRemoved); @@ -1139,11 +1168,9 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); UserName userName(userNameString, dbname); stdx::unordered_set<RoleName> userRoles; @@ -1164,7 +1191,7 @@ public: status = updatePrivilegeDocument( opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(userName); + authzManager->invalidateUserByName(opCtx, userName); uassertStatusOK(status); return true; } @@ -1208,11 +1235,9 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); UserName userName(userNameString, dbname); stdx::unordered_set<RoleName> userRoles; @@ -1233,7 +1258,7 @@ public: status = updatePrivilegeDocument( opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(userName); + authzManager->invalidateUserByName(opCtx, userName); uassertStatusOK(status); return true; } @@ -1270,8 +1295,8 @@ public: Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args); uassertStatusOK(status); - status = requireReadableAuthSchema26Upgrade(opCtx, getGlobalAuthorizationManager()); - uassertStatusOK(status); + AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); + auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager)); if ((args.target != auth::UsersInfoArgs::Target::kExplicitUsers || args.filter) && (args.showPrivileges || @@ -1289,8 +1314,7 @@ public: // user. for (size_t i = 0; i < args.userNames.size(); ++i) { BSONObj userDetails; - status = getGlobalAuthorizationManager()->getUserDescription( - opCtx, args.userNames[i], &userDetails); + status = authzManager->getUserDescription(opCtx, args.userNames[i], &userDetails); if (status.code() == ErrorCodes::UserNotFound) { continue; } @@ -1475,11 +1499,9 @@ public: } ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); // Role existence has to be checked after acquiring the update lock status = checkOkayToGrantRolesToRole(opCtx, args.roleName, args.roles, authzManager); @@ -1564,11 +1586,9 @@ public: } ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); // Role existence has to be checked after acquiring the update lock BSONObj ignored; @@ -1603,7 +1623,7 @@ public: status = updateRoleDocument(opCtx, args.roleName, updateDocumentBuilder.obj()); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); uassertStatusOK(status); return true; } @@ -1647,11 +1667,9 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); if (RoleGraph::isBuiltinRole(roleName)) { uasserted(ErrorCodes::InvalidRoleModification, @@ -1699,7 +1717,7 @@ public: status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done()); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); uassertStatusOK(status); return true; } @@ -1743,11 +1761,8 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); if (RoleGraph::isBuiltinRole(roleName)) { uasserted(ErrorCodes::InvalidRoleModification, @@ -1800,7 +1815,7 @@ public: updateObj.writeTo(&updateBSONBuilder); status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done()); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); uassertStatusOK(status); return true; } @@ -1851,11 +1866,9 @@ public: } ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); // Role existence has to be checked after acquiring the update lock BSONObj roleDoc; @@ -1882,7 +1895,7 @@ public: status = updateRoleDocument( opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(directRoles)))); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); uassertStatusOK(status); return true; } @@ -1926,11 +1939,9 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); RoleName roleName(roleNameString, dbname); if (RoleGraph::isBuiltinRole(roleName)) { @@ -1961,7 +1972,7 @@ public: status = updateRoleDocument( opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(roles)))); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); uassertStatusOK(status); return true; } @@ -2006,11 +2017,9 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); if (RoleGraph::isBuiltinRole(roleName)) { uasserted(ErrorCodes::InvalidRoleModification, @@ -2022,6 +2031,18 @@ public: status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); uassertStatusOK(status); + // From here on, we always want to invalidate the user cache before returning. + auto invalidateGuard = MakeGuard([&] { + try { + authzManager->invalidateUserCache(opCtx); + } catch (const DBException& e) { + // Since this may be called after a uassert, we want to catch any uasserts + // that come out of invalidating the user cache and explicitly append it to + // the command response. + CommandHelpers::appendCommandStatusNoThrow(result, e.toStatus()); + } + }); + // Remove this role from all users long long nMatched; status = updateAuthzDocuments( @@ -2038,8 +2059,6 @@ public: false, true, &nMatched); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); if (!status.isOK()) { uassertStatusOK(useDefaultCode(status, ErrorCodes::UserModificationFailed) .withContext(str::stream() << "Failed to remove role " @@ -2062,8 +2081,6 @@ public: false, true, &nMatched); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); if (!status.isOK()) { uassertStatusOK( useDefaultCode(status, ErrorCodes::RoleModificationFailed) @@ -2080,8 +2097,6 @@ public: << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB()), &nMatched); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); if (!status.isOK()) { uassertStatusOK(status.withContext( str::stream() << "Removed role " << roleName.getFullName() @@ -2138,11 +2153,20 @@ public: uassertStatusOK(status); ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + // From here on, we always want to invalidate the user cache before returning. + auto invalidateGuard = MakeGuard([&] { + try { + authzManager->invalidateUserCache(opCtx); + } catch (const DBException& e) { + // Since this may be called after a uassert, we want to catch any uasserts + // that come out of invalidating the user cache and explicitly append it to + // the command response. + CommandHelpers::appendCommandStatusNoThrow(result, e.toStatus()); + } + }); // Remove these roles from all users long long nMatched; @@ -2155,8 +2179,6 @@ public: false, true, &nMatched); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); if (!status.isOK()) { uassertStatusOK(useDefaultCode(status, ErrorCodes::UserModificationFailed) .withContext(str::stream() << "Failed to remove roles from \"" @@ -2176,8 +2198,6 @@ public: false, true, &nMatched); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); if (!status.isOK()) { uassertStatusOK(useDefaultCode(status, ErrorCodes::RoleModificationFailed) .withContext(str::stream() << "Failed to remove roles from \"" @@ -2189,8 +2209,6 @@ public: // Finally, remove the actual role documents status = removeRoleDocuments( opCtx, BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname), &nMatched); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(); if (!status.isOK()) { uassertStatusOK(status.withContext( str::stream() << "Removed roles from \"" << dbname @@ -2259,18 +2277,17 @@ public: Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args); uassertStatusOK(status); - status = requireReadableAuthSchema26Upgrade(opCtx, getGlobalAuthorizationManager()); - uassertStatusOK(status); + AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); + auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager)); if (args.allForDB) { std::vector<BSONObj> rolesDocs; - status = getGlobalAuthorizationManager()->getRoleDescriptionsForDB( - opCtx, - dbname, - args.privilegeFormat, - args.authenticationRestrictionsFormat, - args.showBuiltinRoles, - &rolesDocs); + status = authzManager->getRoleDescriptionsForDB(opCtx, + dbname, + args.privilegeFormat, + args.authenticationRestrictionsFormat, + args.showBuiltinRoles, + &rolesDocs); uassertStatusOK(status); if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) { @@ -2284,12 +2301,11 @@ public: result.append("roles", rolesArrayBuilder.arr()); } else { BSONObj roleDetails; - status = getGlobalAuthorizationManager()->getRolesDescription( - opCtx, - args.roleNames, - args.privilegeFormat, - args.authenticationRestrictionsFormat, - &roleDetails); + status = authzManager->getRolesDescription(opCtx, + args.roleNames, + args.privilegeFormat, + args.authenticationRestrictionsFormat, + &roleDetails); uassertStatusOK(status); if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) { @@ -2335,7 +2351,8 @@ public: const BSONObj& cmdObj, BSONObjBuilder& result) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); - authzManager->invalidateUserCache(); + auto lk = requireReadableAuthSchema26Upgrade(opCtx, authzManager); + authzManager->invalidateUserCache(opCtx); return true; } @@ -2750,11 +2767,20 @@ public: } ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext)); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - status = requireWritableAuthSchema28SCRAM(opCtx, authzManager); - uassertStatusOK(status); + + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + // From here on, we always want to invalidate the user cache before returning. + auto invalidateGuard = MakeGuard([&] { + try { + authzManager->invalidateUserCache(opCtx); + } catch (const DBException& e) { + // Since this may be called after a uassert, we want to catch any uasserts + // that come out of invalidating the user cache and explicitly append it to + // the command response. + CommandHelpers::appendCommandStatusNoThrow(result, e.toStatus()); + } + }); if (!args.usersCollName.empty()) { Status status = diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index bbf00c3b02c..95be3fa2e4f 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -1120,7 +1120,7 @@ void OpObserverImpl::onReplicationRollback(OperationContext* opCtx, if (rollbackNamespaces.count(AuthorizationManager::versionCollectionNamespace) == 1 || rollbackNamespaces.count(AuthorizationManager::usersCollectionNamespace) == 1 || rollbackNamespaces.count(AuthorizationManager::rolesCollectionNamespace) == 1) { - AuthorizationManager::get(opCtx->getServiceContext())->invalidateUserCache(); + AuthorizationManager::get(opCtx->getServiceContext())->invalidateUserCache(opCtx); } // If there were ops rolled back that were part of operations on a session, then invalidate diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index 499312b3b14..cacb87ade6a 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -1668,11 +1668,8 @@ Status applyCommand_inlock(OperationContext* opCtx, } } - // AuthorizationManager's logOp method registers a RecoveryUnit::Change - // and to do so we need to have begun a UnitOfWork - WriteUnitOfWork wuow(opCtx); + getGlobalAuthorizationManager()->logOp(opCtx, opType, nss, o, nullptr); - wuow.commit(); return Status::OK(); } diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp index fc32fe4e103..522ee32531b 100644 --- a/src/mongo/embedded/embedded_auth_manager.cpp +++ b/src/mongo/embedded/embedded_auth_manager.cpp @@ -101,11 +101,11 @@ public: UASSERT_NOT_IMPLEMENTED; } - void invalidateUserByName(const UserName& user) override { + void invalidateUserByName(OperationContext*, const UserName& user) override { UASSERT_NOT_IMPLEMENTED; } - void invalidateUsersFromDB(const StringData dbname) override { + void invalidateUsersFromDB(OperationContext*, const StringData dbname) override { UASSERT_NOT_IMPLEMENTED; } @@ -113,7 +113,7 @@ public: UASSERT_NOT_IMPLEMENTED; } - void invalidateUserCache() override { + void invalidateUserCache(OperationContext*) override { UASSERT_NOT_IMPLEMENTED; } @@ -132,6 +132,10 @@ public: UASSERT_NOT_IMPLEMENTED; } + void setInUserManagementCommand(OperationContext*, bool) override { + UASSERT_NOT_IMPLEMENTED; + } + private: bool _shouldValidate = false; }; diff --git a/src/mongo/s/commands/cluster_user_management_commands.cpp b/src/mongo/s/commands/cluster_user_management_commands.cpp index 0cd46d1a192..7e398fd9ac1 100644 --- a/src/mongo/s/commands/cluster_user_management_commands.cpp +++ b/src/mongo/s/commands/cluster_user_management_commands.cpp @@ -139,7 +139,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserByName(args.userName); + authzManager->invalidateUserByName(opCtx, args.userName); return ok; } @@ -190,7 +190,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserByName(userName); + authzManager->invalidateUserByName(opCtx, userName); return ok; } @@ -232,7 +232,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUsersFromDB(dbname); + authzManager->invalidateUsersFromDB(opCtx, dbname); return ok; } @@ -281,7 +281,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserByName(UserName(userNameString, dbname)); + authzManager->invalidateUserByName(opCtx, UserName(userNameString, dbname)); return ok; } @@ -330,7 +330,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserByName(UserName(userNameString, dbname)); + authzManager->invalidateUserByName(opCtx, UserName(userNameString, dbname)); return ok; } @@ -440,7 +440,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return ok; } @@ -483,7 +483,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return ok; } @@ -525,7 +525,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return ok; } @@ -567,7 +567,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return ok; } @@ -609,7 +609,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return ok; } @@ -654,7 +654,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return ok; } @@ -701,7 +701,7 @@ public: AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return ok; } @@ -773,7 +773,7 @@ public: BSONObjBuilder& result) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); invariant(authzManager); - authzManager->invalidateUserCache(); + authzManager->invalidateUserCache(opCtx); return true; } diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp index 0ac5e2c17e1..80ea5479d6b 100644 --- a/src/mongo/s/server.cpp +++ b/src/mongo/s/server.cpp @@ -408,7 +408,7 @@ ExitCode runMongosServer(ServiceContext* serviceContext) { startMongoSFTDC(); - Status status = AuthorizationManager::get(serviceContext)->initialize(NULL); + Status status = AuthorizationManager::get(serviceContext)->initialize(opCtx.get()); if (!status.isOK()) { error() << "Initializing authorization data failed: " << status; return EXIT_SHARDING_ERROR; |