diff options
15 files changed, 180 insertions, 46 deletions
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 27fc4c57bb3..01790c106db 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -214,6 +214,11 @@ public: /** * Delegates method call to the underlying AuthzManagerExternalState. */ + virtual Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) = 0; + + /** + * Delegates method call to the underlying AuthzManagerExternalState. + */ virtual Status getRoleDescription(OperationContext* opCtx, const RoleName& roleName, PrivilegeFormat privilegeFormat, diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index a2ba1112eec..42439d4e791 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -416,6 +416,11 @@ Status AuthorizationManagerImpl::getUserDescription(OperationContext* opCtx, return _externalState->getUserDescription(opCtx, UserRequest(userName, boost::none), result); } +Status AuthorizationManagerImpl::rolesExist(OperationContext* opCtx, + const std::vector<RoleName>& roleNames) { + return _externalState->rolesExist(opCtx, roleNames); +} + Status AuthorizationManagerImpl::getRoleDescription(OperationContext* opCtx, const RoleName& roleName, PrivilegeFormat privileges, diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h index ba2d214d12a..933bbc02e32 100644 --- a/src/mongo/db/auth/authorization_manager_impl.h +++ b/src/mongo/db/auth/authorization_manager_impl.h @@ -72,6 +72,8 @@ public: const UserName& userName, BSONObj* result) override; + Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) override; + Status getRoleDescription(OperationContext* opCtx, const RoleName& roleName, PrivilegeFormat privilegeFormat, diff --git a/src/mongo/db/auth/authz_manager_external_state.cpp b/src/mongo/db/auth/authz_manager_external_state.cpp index 8384fa2b924..5d2eb103f95 100644 --- a/src/mongo/db/auth/authz_manager_external_state.cpp +++ b/src/mongo/db/auth/authz_manager_external_state.cpp @@ -46,4 +46,21 @@ std::unique_ptr<AuthzManagerExternalState> AuthzManagerExternalState::create() { AuthzManagerExternalState::AuthzManagerExternalState() = default; AuthzManagerExternalState::~AuthzManagerExternalState() = default; +Status AuthzManagerExternalState::makeRoleNotFoundStatus( + const stdx::unordered_set<RoleName>& unknownRoles) { + dassert(unknownRoles.size()); + + char delim = ':'; + StringBuilder sb; + sb << "Could not find role"; + if (unknownRoles.size() > 1) { + sb << 's'; + } + for (const auto& unknownRole : unknownRoles) { + sb << delim << ' ' << unknownRole.toString(); + delim = ','; + } + return {ErrorCodes::RoleNotFound, sb.str()}; +} + } // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index 7dde1480d96..867ea493a5f 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -100,6 +100,11 @@ public: BSONObj* result) = 0; /** + * Checks to see if the named roles exist. + */ + virtual Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) = 0; + + /** * Writes into "result" a document describing the named role and returns Status::OK(). If * showPrivileges is kOmit or kShowPrivileges, the description includes the roles which the * named role is a member of, including those memberships held implicitly through other roles @@ -173,6 +178,11 @@ public: protected: AuthzManagerExternalState(); // This class should never be instantiated directly. + + /** + * Construct a Status about one or more unknown roles. + */ + static Status makeRoleNotFoundStatus(const stdx::unordered_set<RoleName>&); }; } // namespace mongo 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 2d143fee899..a5b66fe2622 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_d.cpp @@ -88,6 +88,13 @@ Status AuthzManagerExternalStateMongod::findOne(OperationContext* opCtx, << query); } +bool AuthzManagerExternalStateMongod::hasOne(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query) { + AutoGetCollectionForReadCommand ctx(opCtx, collectionName); + return !Helpers::findOne(opCtx, ctx.getCollection(), query, false).isNull(); +} + namespace { std::unique_ptr<AuthzManagerExternalState> authzManagerExternalStateCreateImpl() { 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 289a7567236..dfb636adba0 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.h +++ b/src/mongo/db/auth/authz_manager_external_state_d.h @@ -51,17 +51,20 @@ public: virtual ~AuthzManagerExternalStateMongod(); std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( - AuthorizationManager* authzManager) override; + AuthorizationManager* authzManager) final; - virtual Status findOne(OperationContext* opCtx, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result); - virtual Status query(OperationContext* opCtx, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& projection, - const std::function<void(const BSONObj&)>& resultProcessor); + Status findOne(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result) final; + bool hasOne(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query) final; + Status query(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& projection, + const std::function<void(const BSONObj&)>& resultProcessor) final; }; } // namespace mongo 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 89ad8fbe7a5..e530074b5a4 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -293,6 +293,25 @@ Status AuthzManagerExternalStateLocal::_getUserDocument(OperationContext* opCtx, return status; } +Status AuthzManagerExternalStateLocal::rolesExist(OperationContext* opCtx, + const std::vector<RoleName>& roleNames) { + // Perform DB queries for user-defined roles (skipping builtin roles). + stdx::unordered_set<RoleName> unknownRoles; + for (const auto& roleName : roleNames) { + if (!RoleGraph::isBuiltinRole(roleName) && + !hasOne(opCtx, AuthorizationManager::rolesCollectionNamespace, roleName.toBSON())) { + unknownRoles.insert(roleName); + } + } + + // If anything remains, raise it as an unknown role error. + if (!unknownRoles.empty()) { + return makeRoleNotFoundStatus(unknownRoles); + } + + return Status::OK(); +} + Status AuthzManagerExternalStateLocal::getRoleDescription( OperationContext* opCtx, const RoleName& roleName, diff --git a/src/mongo/db/auth/authz_manager_external_state_local.h b/src/mongo/db/auth/authz_manager_external_state_local.h index bd01781fc97..0832d9ea4fa 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.h +++ b/src/mongo/db/auth/authz_manager_external_state_local.h @@ -62,6 +62,7 @@ public: Status getUserDescription(OperationContext* opCtx, const UserRequest& user, BSONObj* result) override; + Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) override; Status getRoleDescription(OperationContext* opCtx, const RoleName& roleName, PrivilegeFormat showPrivileges, @@ -102,6 +103,13 @@ public: BSONObj* result) = 0; /** + * Checks for the existance of a document matching "query" in "collectionName". + */ + virtual bool hasOne(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query) = 0; + + /** * Finds all documents matching "query" in "collectionName". For each document returned, * calls the function resultProcessor on it. */ diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.cpp b/src/mongo/db/auth/authz_manager_external_state_mock.cpp index 1e2c22934b3..57aee1183ea 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_mock.cpp @@ -127,6 +127,14 @@ Status AuthzManagerExternalStateMock::findOne(OperationContext* opCtx, return Status::OK(); } + +bool AuthzManagerExternalStateMock::hasOne(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query) { + BSONObjCollection::iterator iter; + return _findOneIter(opCtx, collectionName, query, &iter).isOK(); +} + Status AuthzManagerExternalStateMock::query( OperationContext* opCtx, const NamespaceString& collectionName, diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.h b/src/mongo/db/auth/authz_manager_external_state_mock.h index 670c73c924f..cd0a5793c30 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.h +++ b/src/mongo/db/auth/authz_manager_external_state_mock.h @@ -61,16 +61,21 @@ public: std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( AuthorizationManager* authzManager) override; - virtual Status findOne(OperationContext* opCtx, - const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result); - - virtual Status query(OperationContext* opCtx, - const NamespaceString& collectionName, - const BSONObj& query, - const BSONObj& projection, // Currently unused in mock - const std::function<void(const BSONObj&)>& resultProcessor); + Status findOne(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result) override; + + bool hasOne(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query) override; + + + Status query(OperationContext* opCtx, + const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& projection, // Currently unused in mock + const std::function<void(const BSONObj&)>& resultProcessor) override; /** * Inserts the given user object into the "admin" database. 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 95e906a8acc..d43be5722ce 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -192,6 +192,57 @@ Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* opC } } +Status AuthzManagerExternalStateMongos::rolesExist(OperationContext* opCtx, + const std::vector<RoleName>& roleNames) try { + // Marshall role names into a set before querying so that we don't get a false-negative + // from repeated roles only providing one result at the end. + stdx::unordered_set<RoleName> roleNameSet(roleNames.cbegin(), roleNames.cend()); + + BSONObjBuilder rolesInfoCmd; + + { + BSONArrayBuilder rolesArray(rolesInfoCmd.subarrayStart("rolesInfo")); + for (const auto& roleName : roleNameSet) { + roleName.serializeToBSON(&rolesArray); + } + rolesArray.doneFast(); + } + + BSONObjBuilder resultBuilder; + if (!Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand( + opCtx, "admin", rolesInfoCmd.obj(), &resultBuilder)) { + return {ErrorCodes::OperationFailed, "Failed running rolesInfo command on mongod"}; + } + + auto result = resultBuilder.obj(); + auto cmdStatus = getStatusFromCommandResult(result); + if (!cmdStatus.isOK()) { + return {cmdStatus.code(), + str::stream() << "Failed running rolesInfo command on mongod: " + << cmdStatus.reason()}; + } + + auto roles = result["roles"]; + if (roles.type() != Array) { + return {ErrorCodes::OperationFailed, + "Received invalid response from rolesInfo command on mongod"}; + } + + if (static_cast<std::size_t>(roles.Obj().nFields()) != roleNameSet.size()) { + // One or more missing roles, cross out the ones that do exist, and return error. + for (const auto& roleObj : roles.Obj()) { + auto roleName = RoleName::parseFromBSON(roleObj); + roleNameSet.erase(roleName); + } + + return makeRoleNotFoundStatus(roleNameSet); + } + + return Status::OK(); +} catch (const AssertionException& ex) { + return ex.toStatus(); +} + Status AuthzManagerExternalStateMongos::getRoleDescription( OperationContext* opCtx, const RoleName& roleName, diff --git a/src/mongo/db/auth/authz_manager_external_state_s.h b/src/mongo/db/auth/authz_manager_external_state_s.h index 939a2ca1cb2..4d310c6aa47 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.h +++ b/src/mongo/db/auth/authz_manager_external_state_s.h @@ -49,33 +49,34 @@ class AuthzManagerExternalStateMongos : public AuthzManagerExternalState { public: AuthzManagerExternalStateMongos(); - ~AuthzManagerExternalStateMongos() override; + ~AuthzManagerExternalStateMongos() final; - Status initialize(OperationContext* opCtx) override; + Status initialize(OperationContext* opCtx) final; std::unique_ptr<AuthzSessionExternalState> makeAuthzSessionExternalState( - AuthorizationManager* authzManager) override; + AuthorizationManager* authzManager) final; Status getStoredAuthorizationVersion(OperationContext* opCtx, int* outVersion) override; + Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) final; Status getUserDescription(OperationContext* opCtx, const UserRequest& user, - BSONObj* result) override; + BSONObj* result) final; Status getRoleDescription(OperationContext* opCtx, const RoleName& roleName, PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat, - BSONObj* result) override; + BSONObj* result) final; Status getRolesDescription(OperationContext* opCtx, const std::vector<RoleName>& roles, PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat, - BSONObj* result) override; + BSONObj* result) final; Status getRoleDescriptionsForDB(OperationContext* opCtx, StringData dbname, PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat, bool showBuiltinRoles, - std::vector<BSONObj>* result) override; + std::vector<BSONObj>* result) final; - bool hasAnyPrivilegeDocuments(OperationContext* opCtx) override; + bool hasAnyPrivilegeDocuments(OperationContext* opCtx) final; }; } // namespace mongo diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index fe1841aa8f9..47f5bc48ee7 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -882,11 +882,7 @@ void CmdUMCTyped<CreateUserCommand, void>::Invocation::typedRun(OperationContext uassertStatusOK(V2UserDocumentParser().checkValidUserDocument(userObj)); // Role existence has to be checked after acquiring the update lock - for (const auto& role : cmd.getRoles()) { - BSONObj ignored; - uassertStatusOK( - authzManager->getRoleDescription(opCtx, role.getRoleName(dbname), &ignored)); - } + uassertStatusOK(authzManager->rolesExist(opCtx, resolvedRoles)); // Audit this event. auto optCustomData = cmd.getCustomData(); @@ -988,11 +984,8 @@ void CmdUMCTyped<UpdateUserCommand, void>::Invocation::typedRun(OperationContext // Role existence has to be checked after acquiring the update lock if (auto roles = cmd.getRoles()) { - for (const auto& role : roles.get()) { - BSONObj ignored; - uassertStatusOK( - authzManager->getRoleDescription(opCtx, role.getRoleName(dbname), &ignored)); - } + auto resolvedRoles = auth::resolveRoleNames(roles.get(), dbname); + uassertStatusOK(authzManager->rolesExist(opCtx, resolvedRoles)); } // Audit this event. @@ -1094,9 +1087,8 @@ void CmdUMCTyped<GrantRolesToUserCommand, void>::Invocation::typedRun(OperationC uassertStatusOK(getCurrentUserRoles(opCtx, authzManager, userName, &userRoles)); auto resolvedRoleNames = auth::resolveRoleNames(cmd.getRoles(), dbname); + uassertStatusOK(authzManager->rolesExist(opCtx, resolvedRoleNames)); for (const auto& role : resolvedRoleNames) { - BSONObj roleDoc; - uassertStatusOK(authzManager->getRoleDescription(opCtx, role, &roleDoc)); userRoles.insert(role); } @@ -1130,9 +1122,8 @@ void CmdUMCTyped<RevokeRolesFromUserCommand, void>::Invocation::typedRun(Operati uassertStatusOK(getCurrentUserRoles(opCtx, authzManager, userName, &userRoles)); auto resolvedUserRoles = auth::resolveRoleNames(cmd.getRoles(), dbname); + uassertStatusOK(authzManager->rolesExist(opCtx, resolvedUserRoles)); for (const auto& role : resolvedUserRoles) { - BSONObj roleDoc; - uassertStatusOK(authzManager->getRoleDescription(opCtx, role, &roleDoc)); userRoles.erase(role); } @@ -1400,8 +1391,7 @@ void CmdUMCTyped<UpdateRoleCommand, void>::Invocation::typedRun(OperationContext auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); // Role existence has to be checked after acquiring the update lock - BSONObj ignored; - uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &ignored)); + uassertStatusOK(authzManager->rolesExist(opCtx, {roleName})); if (optRoles) { uassertStatusOK(checkOkayToGrantRolesToRole(opCtx, roleName, *optRoles, authzManager)); @@ -1660,8 +1650,7 @@ void CmdUMCTyped<DropRoleCommand, void>::Invocation::typedRun(OperationContext* auto* authzManager = AuthorizationManager::get(serviceContext); auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - BSONObj roleDoc; - uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &roleDoc)); + uassertStatusOK(authzManager->rolesExist(opCtx, {roleName})); // From here on, we always want to invalidate the user cache before returning. auto invalidateGuard = makeGuard([&] { diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp index 671fab3c41d..aef2b61d650 100644 --- a/src/mongo/embedded/embedded_auth_manager.cpp +++ b/src/mongo/embedded/embedded_auth_manager.cpp @@ -74,6 +74,10 @@ public: UASSERT_NOT_IMPLEMENTED; } + Status rolesExist(OperationContext*, const std::vector<RoleName>&) override { + UASSERT_NOT_IMPLEMENTED; + } + Status getRoleDescription(OperationContext*, const RoleName&, PrivilegeFormat, |