diff options
36 files changed, 305 insertions, 236 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 09f7d8e3cc9..0d23ae77bd4 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -199,6 +199,7 @@ env.Library( LIBDEPS=[ '$BUILD_DIR/mongo/base/secure_allocator', '$BUILD_DIR/mongo/bson/util/bson_extract', + '$BUILD_DIR/mongo/db/commands/authentication_commands', '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/db/curop', '$BUILD_DIR/mongo/db/global_settings', diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index a23905406da..bdcfba3a7b7 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -316,14 +316,14 @@ public: std::vector<BSONObj>* result) = 0; /** - * Returns a Status or UserHandle for the given userName. If the user cache already has a + * Returns a Status or UserHandle for the given userRequest. If the user cache already has a * user object for this user, it returns a handle from the cache, otherwise it reads the - * user document from disk or LDAP - this may block for a long time. + * user document from the AuthzManagerExternalState - this may block for a long time. * * The returned user may be invalid by the time the caller gets access to it. */ virtual StatusWith<UserHandle> acquireUser(OperationContext* opCtx, - const UserName& userName) = 0; + const UserRequest& userRequest) = 0; /** * Validate the ID associated with a known user while refreshing session cache. diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index 6b95652f6bb..0da6eeef244 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -51,6 +51,7 @@ #include "mongo/db/auth/sasl_options.h" #include "mongo/db/auth/user_document_parser.h" #include "mongo/db/auth/user_management_commands_parser.h" +#include "mongo/db/commands/authentication_commands.h" #include "mongo/db/curop.h" #include "mongo/db/global_settings.h" #include "mongo/db/mongod_options.h" @@ -68,7 +69,8 @@ namespace mongo { namespace { std::shared_ptr<UserHandle> createSystemUserHandle() { - auto user = std::make_shared<UserHandle>(User(UserName("__system", "local"))); + UserRequest request(UserName("__system", "local"), boost::none); + auto user = std::make_shared<UserHandle>(User(std::move(request))); ActionSet allActions; allActions.addAllActions(); @@ -284,29 +286,6 @@ bool appliesToAuthzData(StringData op, const NamespaceString& nss, const BSONObj } } -/** - * Returns true if roles for this user were provided by the client, and can be obtained from - * the connection. - */ -bool shouldUseRolesFromConnection(OperationContext* opCtx, const UserName& userName) { -#ifdef MONGO_CONFIG_SSL - if (!opCtx || !opCtx->getClient() || !opCtx->getClient()->session()) { - return false; - } - - if (!allowRolesFromX509Certificates) { - return false; - } - - auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session()); - return sslPeerInfo.subjectName.toString() == userName.getUser() && - userName.getDB() == "$external"_sd && !sslPeerInfo.roles.empty(); -#else - return false; -#endif -} - - std::unique_ptr<AuthorizationManager> authorizationManagerCreateImpl( ServiceContext* serviceContext) { return std::make_unique<AuthorizationManagerImpl>(serviceContext, @@ -527,26 +506,24 @@ MONGO_FAIL_POINT_DEFINE(authUserCacheSleep); } // namespace StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* opCtx, - const UserName& userName) try { + const UserRequest& request) try { + const auto& userName = request.name; + auto systemUser = internalSecurity.getUser(); if (userName == (*systemUser)->getName()) { + uassert(ErrorCodes::OperationFailed, + "Attempted to acquire system user with predefined roles", + request.roles == boost::none); return *systemUser; } - UserRequest request(userName, boost::none); - + UserRequest userRequest(request); #ifdef MONGO_CONFIG_SSL - // Clients connected via TLS may present an X.509 certificate which contains an authorization - // grant. If this is the case, the roles must be provided to the external state, for expansion - // into privileges. - if (shouldUseRolesFromConnection(opCtx, userName)) { - auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session()); - request.roles = std::set<RoleName>(); - - // In order to be hashable, the role names must be converted from unordered_set to a set. - std::copy(sslPeerInfo.roles.begin(), - sslPeerInfo.roles.end(), - std::inserter(*request.roles, request.roles->begin())); + // X.509 will give us our roles for initial acquire, but we have to lose them during + // reacquire (for now) so reparse those roles into the request if not already present. + if ((request.roles == boost::none) && request.mechanismData.empty() && + (userName.getDB() == "$external"_sd)) { + userRequest = getX509UserRequest(opCtx, std::move(userRequest)); } #endif @@ -571,7 +548,7 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o sleepsecs(1); } - auto cachedUser = _userCache.acquire(opCtx, request); + auto cachedUser = _userCache.acquire(opCtx, userRequest); userAcquisitionStatsHandle.recordTimerEnd(); invariant(cachedUser); @@ -592,7 +569,7 @@ StatusWith<UserHandle> AuthorizationManagerImpl::reacquireUser(OperationContext* // Make a good faith effort to acquire an up-to-date user object, since the one // we've cached is marked "out-of-date." - auto swUserHandle = acquireUser(opCtx, userName); + auto swUserHandle = acquireUser(opCtx, UserRequest(userName, boost::none)); if (!swUserHandle.isOK()) { return swUserHandle.getStatus(); } @@ -678,6 +655,18 @@ void AuthorizationManagerImpl::_pinnedUsersThreadRoutine() noexcept try { } } + // Find UserRequests for UserNames we need to pin if they exist in the cache. + std::map<UserName, UserRequest> pinNow; + _userCache.peekLatestCachedIf([&](const UserRequest& request, const User& user) { + if (std::any_of(usersToPin.begin(), usersToPin.end(), [&](const auto& userName) { + return user.getName() == userName; + })) { + pinNow.emplace(request.name, request); + } + // Don't need any output vector. + return false; + }); + for (const auto& userName : usersToPin) { if (std::any_of(pinnedUsers.begin(), pinnedUsers.end(), [&](const auto& user) { return user->getName() == userName; @@ -685,7 +674,22 @@ void AuthorizationManagerImpl::_pinnedUsersThreadRoutine() noexcept try { continue; } - auto swUser = acquireUser(opCtx.get(), userName); + auto request = ([&] { + if (auto it = pinNow.find(userName); it != pinNow.end()) { + return it->second; + } + return UserRequest(userName, boost::none); + })(); + + if (!request.mechanismData.empty()) { + LOGV2_DEBUG(7070000, + 2, + "Refusing to pin user with mechanism metadata", + "user"_attr = request.name); + continue; + } + + auto swUser = acquireUser(opCtx.get(), request); if (swUser.isOK()) { LOGV2_DEBUG(20232, 2, "Pinned user", "user"_attr = userName); diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h index 0cb99f041f9..db7fd95b70d 100644 --- a/src/mongo/db/auth/authorization_manager_impl.h +++ b/src/mongo/db/auth/authorization_manager_impl.h @@ -101,7 +101,8 @@ public: bool showBuiltinRoles, std::vector<BSONObj>* result) override; - StatusWith<UserHandle> acquireUser(OperationContext* opCtx, const UserName& userName) override; + StatusWith<UserHandle> acquireUser(OperationContext* opCtx, + const UserRequest& userRequest) override; StatusWith<UserHandle> reacquireUser(OperationContext* opCtx, const UserHandle& user) override; /** diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index f91f3f0a329..3363aead426 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -140,7 +140,7 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { << "admin"))), BSONObj())); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("v2read", "test")); + auto swu = authzManager->acquireUser(opCtx.get(), {{"v2read", "test"}, boost::none}); ASSERT_OK(swu.getStatus()); auto v2read = std::move(swu.getValue()); ASSERT_EQUALS(UserName("v2read", "test"), v2read->getName()); @@ -153,7 +153,7 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { ASSERT(testDBPrivilege.getActions().contains(ActionType::find)); // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure - swu = authzManager->acquireUser(opCtx.get(), UserName("v2cluster", "admin")); + swu = authzManager->acquireUser(opCtx.get(), {{"v2cluster", "admin"}, boost::none}); ASSERT_OK(swu.getStatus()); auto v2cluster = std::move(swu.getValue()); ASSERT_EQUALS(UserName("v2cluster", "admin"), v2cluster->getName()); @@ -169,24 +169,19 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { #ifdef MONGO_CONFIG_SSL TEST_F(AuthorizationManagerTest, testLocalX509Authorization) { - setX509PeerInfo(session, - SSLPeerInfo(buildX509Name(), - boost::none, - {RoleName("read", "test"), RoleName("readWrite", "test")})); + std::set<RoleName> roles({{"read", "test"}, {"readWrite", "test"}}); + UserRequest request(UserName("CN=mongodb.com", "$external"), roles); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external")); + auto swu = authzManager->acquireUser(opCtx.get(), request); ASSERT_OK(swu.getStatus()); auto x509User = std::move(swu.getValue()); ASSERT(x509User.isValid()); - stdx::unordered_set<RoleName> expectedRoles{RoleName("read", "test"), - RoleName("readWrite", "test")}; - RoleNameIterator roles = x509User->getRoles(); - stdx::unordered_set<RoleName> acquiredRoles; - while (roles.more()) { - acquiredRoles.insert(roles.next()); + std::set<RoleName> gotRoles; + for (auto it = x509User->getRoles(); it.more();) { + gotRoles.emplace(it.next()); } - ASSERT_TRUE(expectedRoles == acquiredRoles); + ASSERT_TRUE(roles == gotRoles); const User::ResourcePrivilegeMap& privileges = x509User->getPrivileges(); ASSERT_FALSE(privileges.empty()); @@ -202,14 +197,16 @@ TEST_F(AuthorizationManagerTest, testLocalX509AuthorizationInvalidUser) { {RoleName("read", "test"), RoleName("write", "test")})); ASSERT_NOT_OK( - authzManager->acquireUser(opCtx.get(), UserName("CN=10gen.com", "$external")).getStatus()); + authzManager->acquireUser(opCtx.get(), {{"CN=10gen.com", "$external"}, boost::none}) + .getStatus()); } TEST_F(AuthorizationManagerTest, testLocalX509AuthenticationNoAuthorization) { setX509PeerInfo(session, {}); - ASSERT_NOT_OK(authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external")) - .getStatus()); + ASSERT_NOT_OK( + authzManager->acquireUser(opCtx.get(), {{"CN=mongodb.com", "$external"}, boost::none}) + .getStatus()); } #endif @@ -240,7 +237,7 @@ TEST_F(AuthorizationManagerTest, testAcquireV2UserWithUnrecognizedActions) { << "insert")))), BSONObj())); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("myUser", "test")); + auto swu = authzManager->acquireUser(opCtx.get(), {{"myUser", "test"}, boost::none}); ASSERT_OK(swu.getStatus()); auto myUser = std::move(swu.getValue()); ASSERT_EQUALS(UserName("myUser", "test"), myUser->getName()); @@ -317,14 +314,12 @@ TEST_F(AuthorizationManagerTest, testRefreshExternalV2User) { std::vector<UserHandle> checkedOutUsers; checkedOutUsers.reserve(userDocs.size()); for (const auto& userDoc : userDocs) { - auto swUser = authzManager->acquireUser( - opCtx.get(), - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName))); + const UserName userName(userDoc.getStringField(kUserFieldName), + userDoc.getStringField(kDbFieldName)); + auto swUser = authzManager->acquireUser(opCtx.get(), {userName, boost::none}); ASSERT_OK(swUser.getStatus()); auto user = std::move(swUser.getValue()); - ASSERT_EQUALS( - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName)), - user->getName()); + ASSERT_EQUALS(userName, user->getName()); ASSERT(user.isValid()); RoleNameIterator cachedUserRoles = user->getRoles(); @@ -364,14 +359,12 @@ TEST_F(AuthorizationManagerTest, testRefreshExternalV2User) { // Retrieve all users from the cache and verify that only the external ones contain the newly // added role. for (const auto& userDoc : userDocs) { - auto swUser = authzManager->acquireUser( - opCtx.get(), - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName))); + const UserName userName(userDoc.getStringField(kUserFieldName), + userDoc.getStringField(kDbFieldName)); + auto swUser = authzManager->acquireUser(opCtx.get(), {userName, boost::none}); ASSERT_OK(swUser.getStatus()); auto user = std::move(swUser.getValue()); - ASSERT_EQUALS( - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName)), - user->getName()); + ASSERT_EQUALS(userName, user->getName()); ASSERT(user.isValid()); RoleNameIterator cachedUserRolesIt = user->getRoles(); diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 72d107e5253..4c63ebeb1e6 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -145,7 +145,7 @@ public: * for it in the process. */ virtual Status addAndAuthorizeUser(OperationContext* opCtx, - const UserName& userName, + const UserRequest& userRequest, boost::optional<Date_t> expirationTime) = 0; // Returns the authenticated user with the given name. Returns NULL diff --git a/src/mongo/db/auth/authorization_session_for_test.cpp b/src/mongo/db/auth/authorization_session_for_test.cpp index 620716c5e4f..e88dd65ebc4 100644 --- a/src/mongo/db/auth/authorization_session_for_test.cpp +++ b/src/mongo/db/auth/authorization_session_for_test.cpp @@ -48,7 +48,8 @@ void AuthorizationSessionForTest::assumePrivilegesForDB(Privilege privilege, Str void AuthorizationSessionForTest::assumePrivilegesForDB(PrivilegeVector privileges, StringData dbName) { - _authenticatedUser = UserHandle(User(UserName("authorizationSessionForTestUser", dbName))); + UserRequest request(UserName("authorizationSessionForTestUser"_sd, dbName), boost::none); + _authenticatedUser = UserHandle(User(request)); _authenticatedUser.value()->addPrivileges(privileges); _authenticationMode = AuthorizationSession::AuthenticationMode::kConnection; _updateInternalAuthorizationState(); diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index 981e3649bdd..a95df5f97ea 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -210,8 +210,10 @@ void AuthorizationSessionImpl::startContractTracking() { } Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, - const UserName& userName, + const UserRequest& userRequest, boost::optional<Date_t> expirationTime) try { + const auto& userName = userRequest.name; + // Check before we start to reveal as little as possible. Note that we do not need the lock // because only the Client thread can mutate _authenticatedUser. if (_authenticatedUser) { @@ -253,7 +255,7 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, } AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); - auto user = uassertStatusOK(authzManager->acquireUser(opCtx, userName)); + auto user = uassertStatusOK(authzManager->acquireUser(opCtx, userRequest)); auto restrictionStatus = user->validateRestrictions(opCtx); if (!restrictionStatus.isOK()) { diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h index fc8a903ac59..7aab611d2c2 100644 --- a/src/mongo/db/auth/authorization_session_impl.h +++ b/src/mongo/db/auth/authorization_session_impl.h @@ -77,7 +77,7 @@ public: void startContractTracking() override; Status addAndAuthorizeUser(OperationContext* opCtx, - const UserName& userName, + const UserRequest& userRequest, boost::optional<Date_t> expirationTime) override; User* lookupUser(const UserName& name) override; diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 0241901ee41..4f5b6a7c1f6 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -226,31 +226,42 @@ const ResourcePattern otherProfileCollResource( const ResourcePattern thirdProfileCollResource( ResourcePattern::forExactNamespace(NamespaceString("third.system.profile"))); +const UserName kUser1Test("user1"_sd, "test"_sd); +const UserRequest kUser1TestRequest(kUser1Test, boost::none); +const UserName kUser2Test("user2"_sd, "test"_sd); +const UserRequest kUser2TestRequest(kUser2Test, boost::none); + TEST_F(AuthorizationSessionTest, MultiAuthSameUserAllowed) { - ASSERT_OK(createUser({"user1", "test"}, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user1", "test"}, boost::none)); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user1", "test"}, boost::none)); + ASSERT_OK(createUser(kUser1Test, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser1TestRequest, boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser1TestRequest, boost::none)); authzSession->logoutAllDatabases(_client.get(), "Test finished"); } TEST_F(AuthorizationSessionTest, MultiAuthSameDBDisallowed) { - ASSERT_OK(createUser({"user1", "test"}, {})); - ASSERT_OK(createUser({"user2", "test"}, {})); + ASSERT_OK(createUser(kUser1Test, {})); + ASSERT_OK(createUser(kUser2Test, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user1", "test"}, boost::none)); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user2", "test"}, boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser1TestRequest, boost::none)); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser2TestRequest, boost::none)); authzSession->logoutAllDatabases(_client.get(), "Test finished"); } TEST_F(AuthorizationSessionTest, MultiAuthMultiDBDisallowed) { - ASSERT_OK(createUser({"user", "test1"}, {})); - ASSERT_OK(createUser({"user", "test2"}, {})); + ASSERT_OK(createUser(kUser1Test, {})); + ASSERT_OK(createUser(kUser2Test, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user", "test1"}, boost::none)); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user", "test2"}, boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser1TestRequest, boost::none)); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser2TestRequest, boost::none)); authzSession->logoutAllDatabases(_client.get(), "Test finished"); } +const UserName kSpencerTest("spencer"_sd, "test"_sd); +const UserRequest kSpencerTestRequest(kSpencerTest, boost::none); + +const UserName kAdminAdmin("admin"_sd, "admin"_sd); +const UserRequest kAdminAdminRequest(kAdminAdmin, boost::none); + TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { // Check that disabling auth checks works ASSERT_FALSE( @@ -265,12 +276,11 @@ TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { // Check that you can't authorize a user that doesn't exist. ASSERT_EQUALS( ErrorCodes::UserNotFound, - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); // Add a user with readWrite and dbAdmin on the test DB ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}, {"dbAdmin", "test"}})); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); @@ -282,8 +292,7 @@ TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { // Add an admin user with readWriteAnyDatabase ASSERT_OK(createUser({"admin", "admin"}, {{"readWriteAnyDatabase", "admin"}})); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("admin", "admin"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kAdminAdminRequest, boost::none)); ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( ResourcePattern::forExactNamespace(NamespaceString("anydb.somecollection")), @@ -335,10 +344,9 @@ TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { TEST_F(AuthorizationSessionTest, DuplicateRolesOK) { // Add a user with doubled-up readWrite and single dbAdmin on the test DB - ASSERT_OK(createUser({"spencer", "test"}, + ASSERT_OK(createUser(kSpencerTest, {{"readWrite", "test"}, {"dbAdmin", "test"}, {"readWrite", "test"}})); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); @@ -349,15 +357,24 @@ TEST_F(AuthorizationSessionTest, DuplicateRolesOK) { authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } +const UserName kRWTest("rw"_sd, "test"_sd); +const UserName kUserAdminTest("useradmin"_sd, "test"_sd); +const UserName kRWAnyTest("rwany"_sd, "test"_sd); +const UserName kUserAdminAnyTest("useradminany"_sd, "test"_sd); + +const UserRequest kRWTestRequest(kRWTest, boost::none); +const UserRequest kUserAdminTestRequest(kUserAdminTest, boost::none); +const UserRequest kRWAnyTestRequest(kRWAnyTest, boost::none); +const UserRequest kUserAdminAnyTestRequest(kUserAdminAnyTest, boost::none); + TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { - ASSERT_OK(createUser({"rw", "test"}, {{"readWrite", "test"}, {"dbAdmin", "test"}})); - ASSERT_OK(createUser({"useradmin", "test"}, {{"userAdmin", "test"}})); - ASSERT_OK(createUser({"rwany", "test"}, + ASSERT_OK(createUser(kRWTest, {{"readWrite", "test"}, {"dbAdmin", "test"}})); + ASSERT_OK(createUser(kUserAdminTest, {{"userAdmin", "test"}})); + ASSERT_OK(createUser(kRWAnyTest, {{"readWriteAnyDatabase", "admin"}, {"dbAdminAnyDatabase", "admin"}})); - ASSERT_OK(createUser({"useradminany", "test"}, {{"userAdminAnyDatabase", "admin"}})); + ASSERT_OK(createUser(kUserAdminAnyTest, {{"userAdminAnyDatabase", "admin"}})); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("rwany", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kRWAnyTestRequest, boost::none)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); @@ -373,8 +390,8 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); - ASSERT_OK(authzSession->addAndAuthorizeUser( - _opCtx.get(), UserName("useradminany", "test"), boost::none)); + ASSERT_OK( + authzSession->addAndAuthorizeUser(_opCtx.get(), kUserAdminAnyTestRequest, boost::none)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); ASSERT_TRUE( @@ -389,7 +406,7 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("rw", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kRWTestRequest, boost::none)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); @@ -405,8 +422,7 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); - ASSERT_OK(authzSession->addAndAuthorizeUser( - _opCtx.get(), UserName("useradmin", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUserAdminTestRequest, boost::none)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); ASSERT_FALSE( @@ -424,16 +440,15 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { TEST_F(AuthorizationSessionTest, InvalidateUser) { // Add a readWrite user - ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}})); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_OK(createUser(kSpencerTest, {{"readWrite", "test"}})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - User* user = authzSession->lookupUser(UserName("spencer", "test")); + User* user = authzSession->lookupUser(kSpencerTest); // Change the user to be read-only int ignored; @@ -442,7 +457,7 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { BSONObj(), BSONObj(), &ignored)); - ASSERT_OK(createUser({"spencer", "test"}, {{"read", "test"}})); + ASSERT_OK(createUser(kSpencerTest, {{"read", "test"}})); // Make sure that invalidating the user causes the session to reload its privileges. authzManager->invalidateUserByName(_opCtx.get(), user->getName()); @@ -452,7 +467,7 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - user = authzSession->lookupUser(UserName("spencer", "test")); + user = authzSession->lookupUser(kSpencerTest); // Delete the user. ASSERT_OK(managerState->remove(_opCtx.get(), @@ -467,22 +482,21 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->lookupUser(UserName("spencer", "test"))); + ASSERT_FALSE(authzSession->lookupUser(kSpencerTest)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) { // Add a readWrite user ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}})); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - User* user = authzSession->lookupUser(UserName("spencer", "test")); + User* user = authzSession->lookupUser(kSpencerTest); // Change the user to be read-only int ignored; @@ -492,7 +506,7 @@ TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) { BSONObj(), BSONObj(), &ignored)); - ASSERT_OK(createUser({"spencer", "test"}, {{"read", "test"}})); + ASSERT_OK(createUser(kSpencerTest, {{"read", "test"}})); // 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 @@ -546,8 +560,8 @@ TEST_F(AuthorizationSessionTest, AcquireUserObtainsAndValidatesAuthenticationRes std::make_unique<RestrictionEnvironment>( SockAddr::create(clientSource, 5555, AF_UNSPEC), SockAddr::create(serverAddress, 27017, AF_UNSPEC))); - ASSERT_OK(authzSession->addAndAuthorizeUser( - _opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_OK( + authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); }; @@ -556,13 +570,13 @@ TEST_F(AuthorizationSessionTest, AcquireUserObtainsAndValidatesAuthenticationRes std::make_unique<RestrictionEnvironment>( SockAddr::create(clientSource, 5555, AF_UNSPEC), SockAddr::create(serverAddress, 27017, AF_UNSPEC))); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser( - _opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_NOT_OK( + authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); }; // The empty RestrictionEnvironment will cause addAndAuthorizeUser to fail. ASSERT_NOT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); // A clientSource from the 192.168.0.0/24 block will succeed in connecting to a server // listening on 192.168.0.2. @@ -1117,28 +1131,26 @@ TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithNobody) { } TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsNotCoauthorizedWithAnybody) { - ASSERT_FALSE(authzSession->isCoauthorizedWith(UserName("spencer", "test"))); + ASSERT_FALSE(authzSession->isCoauthorizedWith(kSpencerTest)); } TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithAnybodyWhenAuthIsDisabled) { authzManager->setAuthEnabled(false); - ASSERT_TRUE(authzSession->isCoauthorizedWith(UserName("spencer", "test"))); + ASSERT_TRUE(authzSession->isCoauthorizedWith(kSpencerTest)); } TEST_F(AuthorizationSessionTest, AuthorizedSessionIsNotCoauthorizedNobody) { - UserName user("spencer", "test"); - ASSERT_OK(createUser(user, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user, boost::none)); + ASSERT_OK(createUser(kSpencerTest, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); ASSERT_FALSE(authzSession->isCoauthorizedWith(boost::none)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } TEST_F(AuthorizationSessionTest, AuthorizedSessionIsCoauthorizedNobodyWhenAuthIsDisabled) { - UserName user("spencer", "test"); authzManager->setAuthEnabled(false); - ASSERT_OK(createUser(user, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user, boost::none)); - ASSERT_TRUE(authzSession->isCoauthorizedWith(user)); + ASSERT_OK(createUser(kSpencerTest, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); + ASSERT_TRUE(authzSession->isCoauthorizedWith(kSpencerTest)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } @@ -1252,6 +1264,9 @@ TEST_F(AuthorizationSessionTest, CanUseUUIDNamespacesWithPrivilege) { authzSession->verifyContract(&ac); } +const UserName kGMarksAdmin("gmarks", "admin"); +const UserRequest kGMarksAdminRequest(kGMarksAdmin, boost::none); + TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { ASSERT_FALSE(authzSession->mayBypassWriteBlockingMode()); @@ -1267,8 +1282,7 @@ TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { << "db" << "test"))), BSONObj())); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); ASSERT_FALSE(authzSession->mayBypassWriteBlockingMode()); // Add a user with restore role on admin db and ensure we can bypass @@ -1285,8 +1299,7 @@ TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { BSONObj())); authzSession->logoutDatabase(_client.get(), "test", "End of test"); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("gmarks", "admin"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kGMarksAdminRequest, boost::none)); ASSERT_TRUE(authzSession->mayBypassWriteBlockingMode()); // Remove that user by logging out of the admin db and ensure we can't bypass anymore @@ -1308,8 +1321,7 @@ TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { BSONObj())); authzSession->logoutDatabase(_client.get(), "admin", ""); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("admin", "admin"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kAdminAdminRequest, boost::none)); ASSERT_TRUE(authzSession->mayBypassWriteBlockingMode()); // Remove non-privileged user by logging out of test db and ensure we can still bypass @@ -1325,15 +1337,14 @@ TEST_F(AuthorizationSessionTest, InvalidExpirationTime) { // Create and authorize valid user with invalid expiration. Date_t expirationTime = clockSource()->now() - Hours(1); ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}, {"dbAdmin", "test"}})); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser( - _opCtx.get(), UserName("spencer", "test"), expirationTime)); + ASSERT_NOT_OK( + authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, expirationTime)); } TEST_F(AuthorizationSessionTest, NoExpirationTime) { // Create and authorize valid user with no expiration. ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}, {"dbAdmin", "test"}})); - ASSERT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"), boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, boost::none)); assertActive(testFooCollResource, ActionType::insert); // Assert that moving the clock forward has no impact on a session without expiration time. @@ -1354,8 +1365,7 @@ TEST_F(AuthorizationSessionTest, ExpiredSessionWithReauth) { Date_t expirationTime = clockSource()->now() + Hours(1); ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}, {"dbAdmin", "test"}})); ASSERT_OK(createUser({"admin", "admin"}, {{"readWriteAnyDatabase", "admin"}})); - ASSERT_OK(authzSession->addAndAuthorizeUser( - _opCtx.get(), UserName("spencer", "test"), expirationTime)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, expirationTime)); // Assert that advancing the clock by 30 minutes does not trigger expiration. auto clock = clockSource(); @@ -1373,8 +1383,7 @@ TEST_F(AuthorizationSessionTest, ExpiredSessionWithReauth) { // Authorize the same user again to simulate re-login. expirationTime += Hours(2); - ASSERT_OK(authzSession->addAndAuthorizeUser( - _opCtx.get(), UserName("spencer", "test"), expirationTime)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest, expirationTime)); assertActive(testFooCollResource, ActionType::insert); // Expire the user again, this time by setting clock to the exact expiration time boundary. @@ -1383,8 +1392,7 @@ TEST_F(AuthorizationSessionTest, ExpiredSessionWithReauth) { assertExpired(testFooCollResource, ActionType::insert); // Assert that a different user cannot log in on the expired connection. - ASSERT_NOT_OK( - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("admin", "admin"), boost::none)); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kAdminAdminRequest, boost::none)); assertExpired(testFooCollResource, ActionType::insert); // Check that explicit logout from an expired connection works as expected. @@ -1402,8 +1410,10 @@ TEST_F(AuthorizationSessionTest, ExpirationWithSecurityTokenNOK) { constexpr auto authUserFieldName = auth::SecurityToken::kAuthenticatedUserFieldName; auto kOid = OID::gen(); auto body = BSON("ping" << 1 << "$tenant" << kOid); - UserName user("spencer", "test", TenantId(kOid)); - UserName adminUser("admin", "admin"); + const UserName user("spencer", "test", TenantId(kOid)); + const UserRequest userRequest(user, boost::none); + const UserName adminUser("admin", "admin"); + const UserRequest adminUserRequest(adminUser, boost::none); ASSERT_OK(createUser(user, {{"readWrite", "test"}, {"dbAdmin", "test"}})); ASSERT_OK(createUser(adminUser, {{"readWriteAnyDatabase", "admin"}})); @@ -1414,14 +1424,14 @@ TEST_F(AuthorizationSessionTest, ExpirationWithSecurityTokenNOK) { // Make sure that security token users can't be authorized with an expiration date. Date_t expirationTime = clockSource()->now() + Hours(1); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user, expirationTime)); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user, boost::none)); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), userRequest, expirationTime)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), userRequest, boost::none)); // Assert that the session is authenticated and authorized as expected. assertSecurityToken(testFooCollResource, ActionType::insert); // Assert that another user can't be authorized while the security token is auth'd. - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), adminUser, boost::none)); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), adminUserRequest, boost::none)); // Check that starting a new request without the security token decoration results in token user // logout. @@ -1431,7 +1441,7 @@ TEST_F(AuthorizationSessionTest, ExpirationWithSecurityTokenNOK) { // Assert that a connection-based user with an expiration policy can be authorized after token // logout. - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), adminUser, expirationTime)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), adminUserRequest, expirationTime)); assertActive(ResourcePattern::forExactNamespace(NamespaceString("anydb.somecollection")), ActionType::insert); diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp index 39d6da595f3..0b38920150f 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -313,7 +313,7 @@ StatusWith<User> AuthzManagerExternalStateLocal::getUserObject(OperationContext* const UserRequest& userReq) try { const UserName& userName = userReq.name; std::vector<RoleName> directRoles; - User user(userReq.name); + User user(userReq); auto rolesLock = _lockRoles(opCtx, userName.getTenant()); diff --git a/src/mongo/db/auth/authz_manager_external_state_s.cpp b/src/mongo/db/auth/authz_manager_external_state_s.cpp index 61705c9c9b9..4f415fd7247 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -118,7 +118,7 @@ StatusWith<User> AuthzManagerExternalStateMongos::getUserObject(OperationContext return status; } - User user(userReq.name); + User user(userReq); V2UserDocumentParser dp; dp.setTenantId(getActiveTenant(opCtx)); status = dp.initializeUserFromUserDocument(userDoc, &user); diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp index 5a50334a6ce..5e06de862f8 100644 --- a/src/mongo/db/auth/sasl_commands.cpp +++ b/src/mongo/db/auth/sasl_commands.cpp @@ -205,10 +205,10 @@ SaslReply doSaslStep(OperationContext* opCtx, } if (mechanism.isSuccess()) { - UserName userName(mechanism.getPrincipalName(), mechanism.getAuthenticationDatabase()); + auto request = mechanism.getUserRequest(); auto expirationTime = mechanism.getExpirationTime(); uassertStatusOK(AuthorizationSession::get(opCtx->getClient()) - ->addAndAuthorizeUser(opCtx, userName, expirationTime)); + ->addAndAuthorizeUser(opCtx, request, expirationTime)); if (!serverGlobalParams.quiet.load()) { auto attrs = makeLogAttributes(); diff --git a/src/mongo/db/auth/sasl_mechanism_registry.cpp b/src/mongo/db/auth/sasl_mechanism_registry.cpp index fbc1caa8511..e3136ddfbe4 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry.cpp +++ b/src/mongo/db/auth/sasl_mechanism_registry.cpp @@ -103,7 +103,6 @@ void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContex AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); UserHandle user; - const auto swUser = [&] { ScopedCallbackTimer timer([&](Microseconds elapsed) { LOGV2(6788603, @@ -111,7 +110,7 @@ void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContex "metric"_attr = "sasl_acquireUser", "micros"_attr = elapsed.count()); }); - return authManager->acquireUser(opCtx, userName); + return authManager->acquireUser(opCtx, UserRequest(userName, boost::none)); }(); if (!swUser.isOK()) { diff --git a/src/mongo/db/auth/sasl_mechanism_registry.h b/src/mongo/db/auth/sasl_mechanism_registry.h index f2011909368..2112ad06e7d 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry.h +++ b/src/mongo/db/auth/sasl_mechanism_registry.h @@ -220,6 +220,13 @@ public: virtual boost::optional<unsigned int> currentStep() const = 0; virtual boost::optional<unsigned int> totalSteps() const = 0; + /** + * Create a UserRequest to send to AuthorizationSession. + */ + virtual UserRequest getUserRequest() const { + return UserRequest(UserName(getPrincipalName(), getAuthenticationDatabase()), boost::none); + } + protected: /** * Mechanism provided step implementation. diff --git a/src/mongo/db/auth/sasl_mechanism_registry_test.cpp b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp index 38a65af5f10..a8942d99052 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry_test.cpp +++ b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp @@ -233,7 +233,8 @@ public: << "roles" << BSONArray()), BSONObj())); - internalSecurity.setUser(std::make_shared<UserHandle>(User(UserName("__system", "local")))); + UserRequest systemLocal(UserName("__system"_sd, "local"_sd), boost::none); + internalSecurity.setUser(std::make_shared<UserHandle>(User(systemLocal))); } BSONObj getMechsFor(const UserName user) { diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index b9e1df67504..cf3ed62176a 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -135,8 +135,7 @@ StatusWith<std::tuple<bool, std::string>> SASLPlainServerMechanism::stepImpl( "metric"_attr = "plain_acquireUser", "micros"_attr = elapsed.count()); }); - return authManager->acquireUser( - opCtx, UserName(ServerMechanismBase::_principalName, _authenticationDatabase)); + return authManager->acquireUser(opCtx, getUserRequest()); }(); if (!swUser.isOK()) { diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.cpp b/src/mongo/db/auth/sasl_scram_server_conversation.cpp index a8351aafbaf..eb1cbf2287d 100644 --- a/src/mongo/db/auth/sasl_scram_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scram_server_conversation.cpp @@ -213,7 +213,7 @@ StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_fir "metric"_attr = "acquireUser", "micros"_attr = elapsed.count()); }); - return authManager->acquireUser(opCtx, user); + return authManager->acquireUser(opCtx, UserRequest(user, boost::none)); }(); if (!swUser.isOK()) { diff --git a/src/mongo/db/auth/security_key_test.cpp b/src/mongo/db/auth/security_key_test.cpp index e64aded89f6..7a33cfbbad7 100644 --- a/src/mongo/db/auth/security_key_test.cpp +++ b/src/mongo/db/auth/security_key_test.cpp @@ -160,7 +160,8 @@ TEST(SecurityFile, Test) { } TEST(SecurityKey, Test) { - internalSecurity.setUser(std::make_shared<UserHandle>(User(UserName("__system", "local")))); + UserRequest systemLocal(UserName("__system"_sd, "local"_sd), boost::none); + internalSecurity.setUser(std::make_shared<UserHandle>(User(systemLocal))); for (const auto& testCase : testCases) { TestFile file(testCase.fileContents, testCase.mode != TestCase::FailureMode::Permissions); diff --git a/src/mongo/db/auth/security_token_authentication_guard.cpp b/src/mongo/db/auth/security_token_authentication_guard.cpp index a989a356089..04462ee0a3d 100644 --- a/src/mongo/db/auth/security_token_authentication_guard.cpp +++ b/src/mongo/db/auth/security_token_authentication_guard.cpp @@ -41,10 +41,10 @@ namespace auth { SecurityTokenAuthenticationGuard::SecurityTokenAuthenticationGuard( OperationContext* opCtx, const ValidatedTenancyScope& token) { if (token.hasAuthenticatedUser()) { - const auto& userName = token.authenticatedUser(); + UserRequest request(token.authenticatedUser(), boost::none); auto* client = opCtx->getClient(); uassertStatusOK( - AuthorizationSession::get(client)->addAndAuthorizeUser(opCtx, userName, boost::none)); + AuthorizationSession::get(client)->addAndAuthorizeUser(opCtx, request, boost::none)); _client = client; LOGV2_DEBUG(5838100, diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index a80e833c9e7..c50de72a546 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -66,8 +66,8 @@ SHA256Block computeDigest(const UserName& name) { } // namespace -User::User(const UserName& name) - : _name(name), _isInvalidated(false), _digest(computeDigest(_name)) {} +User::User(UserRequest request) + : _request(std::move(request)), _isInvalidated(false), _digest(computeDigest(_request.name)) {} template <> User::SCRAMCredentials<SHA1Block>& User::CredentialData::scram<SHA1Block>() { @@ -201,10 +201,10 @@ void User::reportForUsersInfo(BSONObjBuilder* builder, bool showCredentials, bool showPrivileges, bool showAuthenticationRestrictions) const { - builder->append(kIdFieldName, _name.getUnambiguousName()); + builder->append(kIdFieldName, getName().getUnambiguousName()); UUID::fromCDR(ConstDataRange(_id)).appendToBuilder(builder, kUserIdFieldName); - builder->append(kUserFieldName, _name.getUser()); - builder->append(kDbFieldName, _name.getDB()); + builder->append(kUserFieldName, getName().getUser()); + builder->append(kDbFieldName, getName().getDB()); BSONArrayBuilder mechanismNamesBuilder(builder->subarrayStart(kMechanismsFieldName)); for (const StringData& mechanism : _credentials.toMechanismsVector()) { diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index 52b9e4e38d9..f9ca505322b 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -47,6 +47,47 @@ namespace mongo { /** + * Represents the properties required to request a UserHandle. + * This type is hashable and may be used as a key describing requests + */ +struct UserRequest { + UserRequest(UserName name, boost::optional<std::set<RoleName>> roles) + : name(std::move(name)), roles(std::move(roles)) {} + + + template <typename H> + friend H AbslHashValue(H h, const UserRequest& key) { + auto state = H::combine(std::move(h), key.name, key.mechanismData); + if (key.roles) { + for (const auto& role : *key.roles) { + state = H::combine(std::move(state), role); + } + } + return state; + } + + auto equalityLens() const { + return std::tie(name, roles, mechanismData); + } + + bool operator==(const UserRequest& key) const { + return equalityLens() == key.equalityLens(); + } + + bool operator!=(const UserRequest& key) const { + return equalityLens() != key.equalityLens(); + } + + // The name of the requested user + UserName name; + // Any authorization grants which should override and be used in favor of roles acquisition. + boost::optional<std::set<RoleName>> roles; + + // Mechanism specific metadata which may be used during User acquisition. + std::string mechanismData; +}; + +/** * Represents a MongoDB user. Stores information about the user necessary for access control * checks and authentications, such as what privileges this user has, as well as what roles * the user belongs to. @@ -158,7 +199,7 @@ public: using ResourcePrivilegeMap = stdx::unordered_map<ResourcePattern, Privilege>; - explicit User(const UserName& name); + explicit User(UserRequest request); User(User&&) = default; User& operator=(User&&) = default; @@ -170,11 +211,15 @@ public: _id = std::move(id); } + const UserRequest& getUserRequest() const { + return _request; + } + /** * Returns the user name for this user. */ const UserName& getName() const { - return _name; + return _request.name; } /** @@ -326,8 +371,8 @@ private: // Unique ID (often UUID) for this user. May be empty for legacy users. UserId _id; - // The full user name (as specified by the administrator) - UserName _name; + // The original UserRequest which resolved into this user + UserRequest _request; // User was explicitly invalidated bool _isInvalidated; @@ -354,36 +399,6 @@ private: RestrictionDocuments _indirectRestrictions; }; -/** - * Represents the properties required to request a UserHandle. - * This type is hashable and may be used as a key describing requests - */ -struct UserRequest { - UserRequest(const UserName& name, boost::optional<std::set<RoleName>> roles) - : name(name), roles(std::move(roles)) {} - - - template <typename H> - friend H AbslHashValue(H h, const UserRequest& key) { - auto state = H::combine(std::move(h), key.name); - if (key.roles) { - for (const auto& role : *key.roles) { - state = H::combine(std::move(state), role); - } - } - return state; - } - - bool operator==(const UserRequest& key) const { - return name == key.name && roles == key.roles; - } - - // The name of the requested user - UserName name; - // Any authorization grants which should override and be used in favor of roles acquisition. - boost::optional<std::set<RoleName>> roles; -}; - using UserCache = ReadThroughCache<UserRequest, User>; using UserHandle = UserCache::ValueHandle; diff --git a/src/mongo/db/auth/user_document_parser_test.cpp b/src/mongo/db/auth/user_document_parser_test.cpp index 00ff394a222..9773927ad50 100644 --- a/src/mongo/db/auth/user_document_parser_test.cpp +++ b/src/mongo/db/auth/user_document_parser_test.cpp @@ -63,8 +63,8 @@ public: BSONObj sha1_creds, sha256_creds; void setUp() { - user.reset(new User(UserName("spencer", "test"))); - adminUser.reset(new User(UserName("admin", "admin"))); + user.reset(new User(UserRequest(UserName("spencer", "test"), boost::none))); + adminUser.reset(new User(UserRequest(UserName("admin", "admin"), boost::none))); sha1_creds = scram::Secrets<SHA1Block>::generateCredentials( "a", saslGlobalParams.scramSHA1IterationCount.load()); diff --git a/src/mongo/db/auth/validated_tenancy_scope_test.cpp b/src/mongo/db/auth/validated_tenancy_scope_test.cpp index 0f2049c893f..ee2859f70a7 100644 --- a/src/mongo/db/auth/validated_tenancy_scope_test.cpp +++ b/src/mongo/db/auth/validated_tenancy_scope_test.cpp @@ -48,7 +48,7 @@ public: * Synthesize a user with the useTenant privilege and add them to the authorization session. */ static void grantUseTenant(Client& client) { - User user(UserName("useTenant", "admin")); + User user(UserRequest(UserName("useTenant"_sd, "admin"_sd), boost::none)); user.setPrivileges( {Privilege(ResourcePattern::forClusterResource(), ActionType::useTenant)}); auto* as = dynamic_cast<AuthorizationSessionImpl*>(AuthorizationSession::get(client)); diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 6b2077cc3ce..66214acb7da 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -177,6 +177,7 @@ env.Library( '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/auth_options', '$BUILD_DIR/mongo/db/auth/authentication_session', + '$BUILD_DIR/mongo/db/auth/authorization_manager_global', '$BUILD_DIR/mongo/db/auth/cluster_auth_mode', '$BUILD_DIR/mongo/db/commands', '$BUILD_DIR/mongo/db/server_base', diff --git a/src/mongo/db/commands/authentication_commands.cpp b/src/mongo/db/commands/authentication_commands.cpp index 13b62c1bef8..661b1d03e9f 100644 --- a/src/mongo/db/commands/authentication_commands.cpp +++ b/src/mongo/db/commands/authentication_commands.cpp @@ -40,6 +40,7 @@ #include "mongo/config.h" #include "mongo/db/auth/auth_options_gen.h" #include "mongo/db/auth/authentication_session.h" +#include "mongo/db/auth/authorization_manager_global_parameters_gen.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/cluster_auth_mode.h" #include "mongo/db/commands.h" @@ -136,6 +137,33 @@ public: } cmdLogout; #ifdef MONGO_CONFIG_SSL +} // namespace + +UserRequest getX509UserRequest(OperationContext* opCtx, UserRequest request) { + std::shared_ptr<transport::Session> session; + if (opCtx && opCtx->getClient()) { + session = opCtx->getClient()->session(); + } + + if (!allowRolesFromX509Certificates || !session || request.roles) { + return request; + } + + auto& sslPeerInfo = SSLPeerInfo::forSession(session); + if (sslPeerInfo.roles.empty() || + (sslPeerInfo.subjectName.toString() != request.name.getUser())) { + return request; + } + + // In order to be hashable, the role names must be converted from unordered_set to a set. + request.roles = std::set<RoleName>(); + std::copy(sslPeerInfo.roles.begin(), + sslPeerInfo.roles.end(), + std::inserter(*request.roles, request.roles->begin())); + return request; +} + +namespace { constexpr auto kX509AuthenticationDisabledMessage = "x.509 authentication is disabled."_sd; /** @@ -159,7 +187,7 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) "No verified subject name available from client", !clientName.empty()); - auto user = [&] { + UserName userName = ([&] { if (session->getUserName().empty()) { auto user = UserName(clientName.toString(), session->getDatabase().toString()); session->updateUserName(user, true /* isMechX509 */); @@ -170,7 +198,7 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) session->getUserName() == clientName.toString()); return UserName(session->getUserName().toString(), session->getDatabase().toString()); } - }(); + })(); uassert(ErrorCodes::ProtocolError, "SSL support is required for the MONGODB-X509 mechanism.", @@ -186,7 +214,7 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) uassert(ErrorCodes::ProtocolError, "X.509 authentication must always use the $external database.", - user.getDB() == kExternalDB); + userName.getDB() == kExternalDB); auto isInternalClient = [&]() -> bool { return opCtx->getClient()->session()->getTags() & transport::Session::kInternalClient; @@ -194,11 +222,12 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) const auto clusterAuthMode = ClusterAuthMode::get(opCtx->getServiceContext()); + auto request = getX509UserRequest(opCtx, UserRequest(userName, boost::none)); auto authorizeExternalUser = [&] { uassert(ErrorCodes::BadValue, kX509AuthenticationDisabledMessage, !isX509AuthDisabled(opCtx->getServiceContext())); - uassertStatusOK(authorizationSession->addAndAuthorizeUser(opCtx, user, boost::none)); + uassertStatusOK(authorizationSession->addAndAuthorizeUser(opCtx, request, boost::none)); }; if (sslConfiguration->isClusterMember(clientName)) { diff --git a/src/mongo/db/commands/authentication_commands.h b/src/mongo/db/commands/authentication_commands.h index 2e82168e050..725f40afde0 100644 --- a/src/mongo/db/commands/authentication_commands.h +++ b/src/mongo/db/commands/authentication_commands.h @@ -32,6 +32,8 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/auth/user.h" +#include "mongo/db/auth/user_name.h" #include "mongo/db/service_context.h" namespace mongo { @@ -41,6 +43,7 @@ constexpr StringData kX509AuthMechanism = "MONGODB-X509"_sd; void disableX509Auth(ServiceContext* svcCtx); bool isX509AuthDisabled(ServiceContext* svcCtx); +UserRequest getX509UserRequest(OperationContext* opCtx, UserRequest request); void doSpeculativeAuthenticate(OperationContext* opCtx, BSONObj isMaster, BSONObjBuilder* result); diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 3cbff434678..8358ae25c05 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -139,7 +139,7 @@ Status getCurrentUserRoles(OperationContext* opCtx, AuthorizationManager* authzManager, const UserName& userName, stdx::unordered_set<RoleName>* roles) { - auto swUser = authzManager->acquireUser(opCtx, userName); + auto swUser = authzManager->acquireUser(opCtx, UserRequest(userName, boost::none)); if (!swUser.isOK()) { return swUser.getStatus(); } @@ -1385,7 +1385,8 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu } else { // Custom data is not required in the output, so it can be generated from a cached // user object. - auto swUserHandle = authzManager->acquireUser(opCtx, userName); + auto swUserHandle = + authzManager->acquireUser(opCtx, UserRequest(userName, boost::none)); if (swUserHandle.getStatus().code() == ErrorCodes::UserNotFound) { continue; } diff --git a/src/mongo/db/commands_test.cpp b/src/mongo/db/commands_test.cpp index 40dff290ee1..6a82edd45fe 100644 --- a/src/mongo/db/commands_test.cpp +++ b/src/mongo/db/commands_test.cpp @@ -476,10 +476,13 @@ protected: AuthorizationSession* _authzSession; }; +const UserName kVarunTest("varun", "test"); +const UserRequest kVarunTestRequest(kVarunTest, boost::none); + TEST_F(TypedCommandTest, runTyped) { { auto opCtx = _client->makeOperationContext(); - ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), {"varun", "test"}, boost::none)); + ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), kVarunTestRequest, boost::none)); _authzSession->startRequest(opCtx.get()); } @@ -492,7 +495,7 @@ TEST_F(TypedCommandTest, runTyped) { TEST_F(TypedCommandTest, runMinimal) { { auto opCtx = _client->makeOperationContext(); - ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), {"varun", "test"}, boost::none)); + ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), kVarunTestRequest, boost::none)); _authzSession->startRequest(opCtx.get()); } @@ -505,7 +508,7 @@ TEST_F(TypedCommandTest, runMinimal) { TEST_F(TypedCommandTest, runVoid) { { auto opCtx = _client->makeOperationContext(); - ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), {"varun", "test"}, boost::none)); + ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), kVarunTestRequest, boost::none)); _authzSession->startRequest(opCtx.get()); } @@ -518,7 +521,7 @@ TEST_F(TypedCommandTest, runVoid) { TEST_F(TypedCommandTest, runThrowStatus) { { auto opCtx = _client->makeOperationContext(); - ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), {"varun", "test"}, boost::none)); + ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), kVarunTestRequest, boost::none)); _authzSession->startRequest(opCtx.get()); } @@ -539,7 +542,7 @@ TEST_F(TypedCommandTest, runThrowStatus) { TEST_F(TypedCommandTest, runThrowDoCheckAuthorization) { { auto opCtx = _client->makeOperationContext(); - ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), {"varun", "test"}, boost::none)); + ASSERT_OK(_authzSession->addAndAuthorizeUser(opCtx.get(), kVarunTestRequest, boost::none)); _authzSession->startRequest(opCtx.get()); } @@ -578,7 +581,7 @@ TEST_F(TypedCommandTest, runThrowAuthzSessionExpired) { auto opCtx = _client->makeOperationContext(); auto expirationTime = clockSource()->now() + Hours{1}; ASSERT_OK( - _authzSession->addAndAuthorizeUser(opCtx.get(), {"varun", "test"}, expirationTime)); + _authzSession->addAndAuthorizeUser(opCtx.get(), kVarunTestRequest, expirationTime)); // Fast-forward time before starting a new request. clockSource()->advance(Hours(2)); diff --git a/src/mongo/db/session/SConscript b/src/mongo/db/session/SConscript index 9862c8a660b..a0021e3ac81 100644 --- a/src/mongo/db/session/SConscript +++ b/src/mongo/db/session/SConscript @@ -155,6 +155,7 @@ env.Library( '$BUILD_DIR/mongo/db/api_parameters', '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/authprivilege', + '$BUILD_DIR/mongo/db/auth/user', '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/rpc/client_metadata', 'logical_session_id_helpers', diff --git a/src/mongo/db/session/kill_sessions.cpp b/src/mongo/db/session/kill_sessions.cpp index 7626afa111b..99d8fd5122a 100644 --- a/src/mongo/db/session/kill_sessions.cpp +++ b/src/mongo/db/session/kill_sessions.cpp @@ -109,12 +109,8 @@ KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCt const KillAllSessionsUser& kasu) { KillAllSessionsByPatternItem item = makeKillAllSessionsByPattern(opCtx); - auto authMgr = AuthorizationManager::get(opCtx->getServiceContext()); - - UserName un(kasu.getUser(), kasu.getDb()); - - auto user = uassertStatusOK(authMgr->acquireUser(opCtx, un)); - item.pattern.setUid(user->getDigest()); + User user(UserRequest(UserName(kasu.getUser(), kasu.getDb()), boost::none)); + item.pattern.setUid(user.getDigest()); return item; } diff --git a/src/mongo/db/session/logical_session_id_test.cpp b/src/mongo/db/session/logical_session_id_test.cpp index 5b5f59fbb83..a338ca5ddae 100644 --- a/src/mongo/db/session/logical_session_id_test.cpp +++ b/src/mongo/db/session/logical_session_id_test.cpp @@ -111,7 +111,7 @@ public: << "db" << "test"))), BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), un, boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {un, boost::none}, boost::none)); return authzSession->lookupUser(un); } @@ -126,7 +126,7 @@ public: << "db" << "admin"))), BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), un, boost::none)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {un, boost::none}, boost::none)); return authzSession->lookupUser(un); } }; diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp index 6604e4a2756..b0da468f787 100644 --- a/src/mongo/embedded/embedded_auth_manager.cpp +++ b/src/mongo/embedded/embedded_auth_manager.cpp @@ -116,7 +116,7 @@ public: UASSERT_NOT_IMPLEMENTED; } - StatusWith<UserHandle> acquireUser(OperationContext*, const UserName&) override { + StatusWith<UserHandle> acquireUser(OperationContext*, const UserRequest&) override { UASSERT_NOT_IMPLEMENTED; } diff --git a/src/mongo/embedded/embedded_auth_session.cpp b/src/mongo/embedded/embedded_auth_session.cpp index 1925afdf14c..135f0d2c95b 100644 --- a/src/mongo/embedded/embedded_auth_session.cpp +++ b/src/mongo/embedded/embedded_auth_session.cpp @@ -71,7 +71,7 @@ public: void startContractTracking() override {} Status addAndAuthorizeUser(OperationContext*, - const UserName&, + const UserRequest&, boost::optional<Date_t>) override { UASSERT_NOT_IMPLEMENTED; } diff --git a/src/mongo/rpc/op_msg_test.cpp b/src/mongo/rpc/op_msg_test.cpp index 95da2298ec4..def7ff23975 100644 --- a/src/mongo/rpc/op_msg_test.cpp +++ b/src/mongo/rpc/op_msg_test.cpp @@ -61,7 +61,7 @@ public: * Synthesize a user with the useTenant privilege and add them to the authorization session. */ static void grantUseTenant(Client& client) { - User user(UserName("useTenant", "admin")); + User user(UserRequest(UserName("useTenant", "admin"), boost::none)); user.setPrivileges( {Privilege(ResourcePattern::forClusterResource(), ActionType::useTenant)}); auto* as = dynamic_cast<AuthorizationSessionImpl*>(AuthorizationSession::get(client)); diff --git a/src/mongo/util/net/network_interface_ssl_test.cpp b/src/mongo/util/net/network_interface_ssl_test.cpp index fadbb694257..cc4fae14287 100644 --- a/src/mongo/util/net/network_interface_ssl_test.cpp +++ b/src/mongo/util/net/network_interface_ssl_test.cpp @@ -60,7 +60,8 @@ public: NetworkInterfaceIntegrationFixture::setUp(); // Setup an internal user so that we can use it for external auth - auto user = std::make_shared<UserHandle>(User(UserName("__system", "local"))); + UserRequest systemLocal(UserName("__system"_sd, "local"_sd), boost::none); + auto user = std::make_shared<UserHandle>(User(systemLocal)); internalSecurity.setUser(user); |