diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2020-08-08 19:53:46 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-08-24 23:19:05 +0000 |
commit | fc279d5cbaa398d879513ae2d679408bfda69e40 (patch) | |
tree | 860ffd30730befdf4071284bfb8d6046682e6f3b /src | |
parent | 5112cd025d54dab920b16bf6ebc901e2d7b4c13e (diff) | |
download | mongo-fc279d5cbaa398d879513ae2d679408bfda69e40.tar.gz |
SERVER-50204 Refactor role acquisition using resolveRoles
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 57 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_impl.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_impl.h | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.h | 27 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.cpp | 425 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.h | 15 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.cpp | 97 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.h | 10 | ||||
-rw-r--r-- | src/mongo/db/auth/privilege.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/auth/privilege.h | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/role_graph.h | 5 | ||||
-rw-r--r-- | src/mongo/db/auth/role_graph_builtin_roles.cpp | 206 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 215 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands_common.cpp | 12 | ||||
-rw-r--r-- | src/mongo/embedded/embedded_auth_manager.cpp | 10 |
15 files changed, 578 insertions, 530 deletions
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 01790c106db..4769b16c25c 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -217,21 +217,54 @@ public: virtual Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) = 0; /** - * Delegates method call to the underlying AuthzManagerExternalState. + * Options for what data resolveRoles() should mine from the role tree. + * + * kRoles: Collect RoleNames in the "roles" field in each role document for subordinates. + * kPrivileges: Examine the "privileges" field in each role document and + * merge "actions" for identicate "resource" patterns. + * kRestrictions: Collect the "authenticationRestrictions" field in each role document. + * + * kDirectOnly: If specified, only the RoleNames explicitly supplied to resolveRoles() + * will be examined. + * If not specified, then resolveRoles() will continue examining all + * subordinate roles until the tree has been exhausted. + * + * kAll, kDirectRoles, kDirectPrivileges, kDirectRestrictions, and kDirectAll + * exist as convenience aliases for combinations of the above flags. */ - virtual Status getRoleDescription(OperationContext* opCtx, - const RoleName& roleName, - PrivilegeFormat privilegeFormat, - AuthenticationRestrictionsFormat, - BSONObj* result) = 0; + enum ResolveRoleOption : std::uint8_t { + + kRoles = 0x01, + kPrivileges = 0x02, + kRestrictions = 0x04, + kAll = kRoles | kPrivileges | kRestrictions, + + // Only collect from the first pass. + kDirectOnly = 0x10, + + kDirectRoles = kRoles | kDirectOnly, + kDirectPrivileges = kPrivileges | kDirectOnly, + kDirectRestrictions = kRestrictions | kDirectOnly, + kDirectAll = kAll | kDirectOnly, + }; /** - * Convenience wrapper for getRoleDescription() defaulting formats to kOmit. + * Return type for resolveRoles(). + * Each member will be populated ONLY IF their corresponding Option flag was specifed. + * Otherwise, they will be equal to boost::none. + */ + struct ResolvedRoleData { + boost::optional<stdx::unordered_set<RoleName>> roles; + boost::optional<PrivilegeVector> privileges; + boost::optional<RestrictionDocuments> restrictions; + }; + + /** + * Delegates method call to the underlying AuthzManagerExternalState. */ - Status getRoleDescription(OperationContext* ctx, const RoleName& roleName, BSONObj* result) { - return getRoleDescription( - ctx, roleName, PrivilegeFormat::kOmit, AuthenticationRestrictionsFormat::kOmit, result); - } + virtual StatusWith<ResolvedRoleData> resolveRoles(OperationContext* opCtx, + const std::vector<RoleName>& roleNames, + ResolveRoleOption option) = 0; /** * Delegates method call to the underlying AuthzManagerExternalState. @@ -250,7 +283,7 @@ public: PrivilegeFormat privilegeFormat, AuthenticationRestrictionsFormat, bool showBuiltinRoles, - std::vector<BSONObj>* result) = 0; + BSONArrayBuilder* result) = 0; /** * Returns a Status or UserHandle for the given userName. If the user cache already has a diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index 42439d4e791..329b7e4765f 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -421,12 +421,9 @@ Status AuthorizationManagerImpl::rolesExist(OperationContext* opCtx, return _externalState->rolesExist(opCtx, roleNames); } -Status AuthorizationManagerImpl::getRoleDescription(OperationContext* opCtx, - const RoleName& roleName, - PrivilegeFormat privileges, - AuthenticationRestrictionsFormat restrictions, - BSONObj* result) { - return _externalState->getRoleDescription(opCtx, roleName, privileges, restrictions, result); +StatusWith<AuthorizationManager::ResolvedRoleData> AuthorizationManagerImpl::resolveRoles( + OperationContext* opCtx, const std::vector<RoleName>& roleNames, ResolveRoleOption option) { + return _externalState->resolveRoles(opCtx, roleNames, option); } Status AuthorizationManagerImpl::getRolesDescription(OperationContext* opCtx, @@ -444,7 +441,7 @@ Status AuthorizationManagerImpl::getRoleDescriptionsForDB( PrivilegeFormat privileges, AuthenticationRestrictionsFormat restrictions, bool showBuiltinRoles, - std::vector<BSONObj>* result) { + BSONArrayBuilder* result) { return _externalState->getRoleDescriptionsForDB( opCtx, dbname, privileges, restrictions, showBuiltinRoles, result); } diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h index 933bbc02e32..894a18c042c 100644 --- a/src/mongo/db/auth/authorization_manager_impl.h +++ b/src/mongo/db/auth/authorization_manager_impl.h @@ -30,6 +30,7 @@ #pragma once #include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/privilege.h" #include "mongo/platform/atomic_word.h" #include "mongo/platform/mutex.h" #include "mongo/stdx/condition_variable.h" @@ -74,11 +75,9 @@ public: Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) override; - Status getRoleDescription(OperationContext* opCtx, - const RoleName& roleName, - PrivilegeFormat privilegeFormat, - AuthenticationRestrictionsFormat, - BSONObj* result) override; + StatusWith<ResolvedRoleData> resolveRoles(OperationContext* opCtx, + const std::vector<RoleName>& roleNames, + ResolveRoleOption option) override; Status getRolesDescription(OperationContext* opCtx, const std::vector<RoleName>& roleName, @@ -91,7 +90,7 @@ public: PrivilegeFormat privilegeFormat, AuthenticationRestrictionsFormat, bool showBuiltinRoles, - std::vector<BSONObj>* result) override; + BSONArrayBuilder* result) override; StatusWith<UserHandle> acquireUser(OperationContext* opCtx, const UserName& userName) override; StatusWith<UserHandle> acquireUserForSessionRefresh(OperationContext* opCtx, diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index 867ea493a5f..f27d577a90f 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -37,6 +37,7 @@ #include "mongo/base/status.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authorization_manager_impl.h" +#include "mongo/db/auth/privilege.h" #include "mongo/db/auth/privilege_format.h" #include "mongo/db/auth/role_name.h" #include "mongo/db/auth/user.h" @@ -104,25 +105,15 @@ public: */ virtual Status rolesExist(OperationContext* opCtx, const std::vector<RoleName>& roleNames) = 0; + using ResolveRoleOption = AuthorizationManager::ResolveRoleOption; + using ResolvedRoleData = AuthorizationManager::ResolvedRoleData; + /** - * 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 - * (indirect roles). If "showPrivileges" is kShowPrivileges, then the description documents - * will also include a full list of the role's privileges. If "showPrivileges" is - * kShowAsUserFragment, then the description returned will take the form of a partial user - * document, describing a hypothetical user which possesses the provided and implicit roles, - * and all inherited privileges. In the event that some of this information is inconsistent, - * the document will contain a "warnings" array, with std::string messages describing - * inconsistencies. - * - * If the role does not exist, returns ErrorCodes::RoleNotFound. + * Collects (in)direct roles, privileges, and restrictions for a set of start roles. */ - virtual Status getRoleDescription(OperationContext* opCtx, - const RoleName& roleName, - PrivilegeFormat showPrivileges, - AuthenticationRestrictionsFormat, - BSONObj* result) = 0; + virtual StatusWith<ResolvedRoleData> resolveRoles(OperationContext* opCtx, + const std::vector<RoleName>& roleNames, + ResolveRoleOption option) = 0; /** * Writes into "result" a document describing the named role is and returns Status::OK(). If @@ -160,7 +151,7 @@ public: PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat, bool showBuiltinRoles, - std::vector<BSONObj>* result) = 0; + BSONArrayBuilder* result) = 0; /** * Returns true if there exists at least one privilege document in the system. 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 e530074b5a4..3d06c09c713 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -38,6 +38,7 @@ #include "mongo/bson/mutable/document.h" #include "mongo/bson/mutable/element.h" #include "mongo/bson/util/bson_extract.h" +#include "mongo/db/auth/address_restriction.h" #include "mongo/db/auth/auth_options_gen.h" #include "mongo/db/auth/privilege_parser.h" #include "mongo/db/auth/user_document_parser.h" @@ -50,6 +51,7 @@ namespace mongo { using std::vector; +using ResolveRoleOption = AuthzManagerExternalStateLocal::ResolveRoleOption; Status AuthzManagerExternalStateLocal::initialize(OperationContext* opCtx) { Status status = _initializeRoleGraph(opCtx); @@ -144,6 +146,101 @@ void addAuthenticationRestrictionObjectsToArrayElement( fassert(40560, restrictionsElement.appendArray("", r->toBSON())); } } + +void serializeResolvedRoles(BSONObjBuilder* user, + const AuthzManagerExternalState::ResolvedRoleData& data, + const BSONObj& roleDoc) { + BSONArrayBuilder rolesBuilder(user->subarrayStart("inheritedRoles")); + for (const auto& roleName : data.roles.get()) { + roleName.serializeToBSON(&rolesBuilder); + } + rolesBuilder.doneFast(); + + if (data.privileges) { + BSONArrayBuilder privsBuilder(user->subarrayStart("inheritedPrivileges")); + for (const auto& privilege : data.privileges.get()) { + privsBuilder.append(privilege.toBSON()); + } + privsBuilder.doneFast(); + } + + if (data.restrictions) { + BSONArrayBuilder arBuilder(user->subarrayStart("inheritedAuthenticationRestrictions")); + if (auto ar = roleDoc["authenticationRestrictions"]; + (ar.type() == Array) && (ar.Obj().nFields() > 0)) { + arBuilder.append(ar); + } + if (auto ar = data.restrictions->toBSON(); ar.nFields() > 0) { + arBuilder.append(ar); + } + arBuilder.doneFast(); + } +} + +/** + * Make sure the roleDoc as retreived from storage matches expectations for options. + */ +constexpr auto kRolesFieldName = "roles"_sd; +constexpr auto kPrivilegesFieldName = "privileges"_sd; +constexpr auto kAuthenticationRestrictionFieldName = "authenticationRestrictions"_sd; + +std::vector<RoleName> filterAndMapRole(BSONObjBuilder* builder, + BSONObj role, + ResolveRoleOption option) { + std::vector<RoleName> subRoles; + bool sawRestrictions = false; + + for (const auto& elem : role) { + if (elem.fieldNameStringData() == kRolesFieldName) { + uassert( + ErrorCodes::BadValue, "Invalid roles field, expected array", elem.type() == Array); + for (const auto& roleName : elem.Obj()) { + subRoles.push_back(RoleName::parseFromBSON(roleName)); + } + if ((option & ResolveRoleOption::kRoles) == 0) { + continue; + } + } + + if ((elem.fieldNameStringData() == kPrivilegesFieldName) && + ((option & ResolveRoleOption::kPrivileges) == 0)) { + continue; + } + + if (elem.fieldNameStringData() == kAuthenticationRestrictionFieldName) { + sawRestrictions = true; + if (option & ResolveRoleOption::kRestrictions) { + BSONArrayBuilder arBuilder( + builder->subarrayStart(kAuthenticationRestrictionFieldName)); + arBuilder.append(elem); + arBuilder.doneFast(); + } + continue; + } + + builder->append(elem); + } + + if (!sawRestrictions && (option & ResolveRoleOption::kRestrictions)) { + builder->append(kAuthenticationRestrictionFieldName, BSONArray()); + } + + return subRoles; +} + +ResolveRoleOption makeResolveRoleOption(PrivilegeFormat showPrivileges, + AuthenticationRestrictionsFormat showRestrictions) { + auto option = ResolveRoleOption::kRoles; + if (showPrivileges != PrivilegeFormat::kOmit) { + option = static_cast<ResolveRoleOption>(option | ResolveRoleOption::kPrivileges); + } + if (showRestrictions != AuthenticationRestrictionsFormat::kOmit) { + option = static_cast<ResolveRoleOption>(option | ResolveRoleOption::kRestrictions); + } + + return option; +} + } // namespace bool AuthzManagerExternalStateLocal::_checkHasAnyPrivilegeDocuments(OperationContext* opCtx) { @@ -312,142 +409,164 @@ Status AuthzManagerExternalStateLocal::rolesExist(OperationContext* opCtx, return Status::OK(); } -Status AuthzManagerExternalStateLocal::getRoleDescription( - OperationContext* opCtx, - const RoleName& roleName, - PrivilegeFormat showPrivileges, - AuthenticationRestrictionsFormat showRestrictions, - BSONObj* result) { - if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) { - mutablebson::Document resultDoc; - mutablebson::Element rolesElement = resultDoc.makeElementArray("roles"); - fassert(40273, resultDoc.root().pushBack(rolesElement)); - addRoleNameObjectsToArrayElement( - rolesElement, makeRoleNameIteratorForContainer(std::vector<RoleName>{roleName})); - resolveUserRoles(&resultDoc, {roleName}); - *result = resultDoc.getObject(); - return Status::OK(); - } - stdx::lock_guard<Latch> lk(_roleGraphMutex); - return _getRoleDescription_inlock(roleName, showPrivileges, showRestrictions, result); -} - -Status AuthzManagerExternalStateLocal::getRolesDescription( - OperationContext* opCtx, - const std::vector<RoleName>& roles, - PrivilegeFormat showPrivileges, - AuthenticationRestrictionsFormat showRestrictions, - BSONObj* result) { - if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) { - mutablebson::Document resultDoc; - mutablebson::Element rolesElement = resultDoc.makeElementArray("roles"); - fassert(40274, resultDoc.root().pushBack(rolesElement)); - addRoleNameObjectsToArrayElement(rolesElement, makeRoleNameIteratorForContainer(roles)); - resolveUserRoles(&resultDoc, roles); - *result = resultDoc.getObject(); - return Status::OK(); - } - - stdx::lock_guard<Latch> lk(_roleGraphMutex); - BSONArrayBuilder resultBuilder; - for (const RoleName& role : roles) { - BSONObj roleDoc; - Status status = - _getRoleDescription_inlock(role, showPrivileges, showRestrictions, &roleDoc); - if (!status.isOK()) { - if (status.code() == ErrorCodes::RoleNotFound) { +using ResolvedRoleData = AuthzManagerExternalState::ResolvedRoleData; +StatusWith<ResolvedRoleData> AuthzManagerExternalStateLocal::resolveRoles( + OperationContext* opCtx, const std::vector<RoleName>& roleNames, ResolveRoleOption option) try { + using RoleNameSet = typename decltype(ResolvedRoleData::roles)::value_type; + const bool processRoles = option & ResolveRoleOption::kRoles; + const bool processPrivs = option & ResolveRoleOption::kPrivileges; + const bool processRests = option & ResolveRoleOption::kRestrictions; + const bool walkIndirect = (option & ResolveRoleOption::kDirectOnly) == 0; + + RoleNameSet inheritedRoles; + PrivilegeVector inheritedPrivileges; + RestrictionDocuments::sequence_type inheritedRestrictions; + + RoleNameSet frontier(roleNames.cbegin(), roleNames.cend()); + RoleNameSet visited; + while (!frontier.empty()) { + RoleNameSet nextFrontier; + for (const auto& role : frontier) { + visited.insert(role); + + if (RoleGraph::isBuiltinRole(role)) { + if (processPrivs) { + RoleGraph::addPrivilegesForBuiltinRole(role, &inheritedPrivileges); + } continue; } - return status; - } - resultBuilder << roleDoc; - } - *result = resultBuilder.arr(); - return Status::OK(); -} - -Status AuthzManagerExternalStateLocal::_getRoleDescription_inlock( - const RoleName& roleName, - PrivilegeFormat showPrivileges, - AuthenticationRestrictionsFormat showRestrictions, - BSONObj* result) { - if (!_roleGraph.roleExists(roleName)) - return Status(ErrorCodes::RoleNotFound, "No role named " + roleName.toString()); - - mutablebson::Document resultDoc; - fassert(17162, - resultDoc.root().appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, - roleName.getRole())); - fassert( - 17163, - resultDoc.root().appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB())); - fassert(17267, resultDoc.root().appendBool("isBuiltin", _roleGraph.isBuiltinRole(roleName))); - auto warningsElement = resultDoc.makeElementArray("warnings"); + BSONObj roleDoc; + auto status = findOne( + opCtx, AuthorizationManager::rolesCollectionNamespace, role.toBSON(), &roleDoc); + if (!status.isOK()) { + if (status.code() == ErrorCodes::NoMatchingDocument) { + return {ErrorCodes::RoleNotFound, + str::stream() << "Role '" << role.getFullName() << "' does not exist"}; + } + return status; + } - auto rolesElement = resultDoc.makeElementArray("roles"); - fassert(17164, resultDoc.root().pushBack(rolesElement)); - addRoleNameObjectsToArrayElement(rolesElement, _roleGraph.getDirectSubordinates(roleName)); + BSONElement elem; + if ((processRoles || walkIndirect) && (elem = roleDoc["roles"])) { + if (elem.type() != Array) { + return {ErrorCodes::BadValue, + str::stream() + << "Invalid 'roles' field in role document '" << role.getFullName() + << "', expected an array but found " << typeName(elem.type())}; + } + for (const auto& subroleElem : elem.Obj()) { + auto subrole = RoleName::parseFromBSON(subroleElem); + if (visited.count(subrole) || nextFrontier.count(subrole)) { + continue; + } + if (walkIndirect) { + nextFrontier.insert(subrole); + } + if (processRoles) { + inheritedRoles.insert(std::move(subrole)); + } + } + } - auto inheritedRolesElement = resultDoc.makeElementArray("inheritedRoles"); - fassert(17165, resultDoc.root().pushBack(inheritedRolesElement)); + if (processPrivs && (elem = roleDoc["privileges"])) { + if (elem.type() != Array) { + return {ErrorCodes::BadValue, + str::stream() << "Invalid 'privileges' field in role document '" + << role.getFullName() << "'"}; + } + for (const auto& privElem : elem.Obj()) { + auto priv = Privilege::fromBSON(privElem); + Privilege::addPrivilegeToPrivilegeVector(&inheritedPrivileges, priv); + } + } - auto privilegesElement = resultDoc.makeElementArray("privileges"); - if (showPrivileges == PrivilegeFormat::kShowSeparate) { - fassert(17166, resultDoc.root().pushBack(privilegesElement)); + if (processRests && (elem = roleDoc["authenticationRestrictions"])) { + if (elem.type() != Array) { + return {ErrorCodes::BadValue, + str::stream() + << "Invalid 'authenticationRestrictions' field in role document '" + << role.getFullName() << "'"}; + } + inheritedRestrictions.push_back( + uassertStatusOK(parseAuthenticationRestriction(BSONArray(elem.Obj())))); + } + } + frontier = std::move(nextFrontier); } - if (showRestrictions == AuthenticationRestrictionsFormat::kShow) { - auto authenticationRestrictionsElement = - resultDoc.makeElementArray("authenticationRestrictions"); - fassert(40559, resultDoc.root().pushBack(authenticationRestrictionsElement)); - - const auto& restrictions = _roleGraph.getDirectAuthenticationRestrictions(roleName); - if (restrictions.get()) { - fassert(40561, - authenticationRestrictionsElement.appendArray("", restrictions->toBSON())); - } + ResolvedRoleData ret; + if (processRoles) { + ret.roles = std::move(inheritedRoles); + } + if (processPrivs) { + ret.privileges = std::move(inheritedPrivileges); + } + if (processRests) { + ret.restrictions = RestrictionDocuments(std::move(inheritedRestrictions)); } - if (_roleGraphState == roleGraphStateConsistent) { - addRoleNameObjectsToArrayElement(inheritedRolesElement, - _roleGraph.getIndirectSubordinates(roleName)); + return ret; +} catch (const AssertionException& ex) { + return ex.toStatus(); +} - if (showPrivileges == PrivilegeFormat::kShowSeparate) { - auto inheritedPrivilegesElement = resultDoc.makeElementArray("inheritedPrivileges"); - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); +Status AuthzManagerExternalStateLocal::getRolesDescription( + OperationContext* opCtx, + const std::vector<RoleName>& roleNames, + PrivilegeFormat showPrivileges, + AuthenticationRestrictionsFormat showRestrictions, + BSONObj* result) { + auto option = makeResolveRoleOption(showPrivileges, showRestrictions); - addPrivilegeObjectsOrWarningsToArrayElement( - inheritedPrivilegesElement, warningsElement, _roleGraph.getAllPrivileges(roleName)); + if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) { + BSONObjBuilder fragment; - fassert(17323, resultDoc.root().pushBack(inheritedPrivilegesElement)); + BSONArrayBuilder rolesBuilder(fragment.subarrayStart("roles")); + for (const auto& roleName : roleNames) { + roleName.serializeToBSON(&rolesBuilder); } + rolesBuilder.doneFast(); - if (showRestrictions == AuthenticationRestrictionsFormat::kShow) { - auto inheritedAuthenticationRestrictionsElement = - resultDoc.makeElementArray("inheritedAuthenticationRestrictions"); - fassert(40563, resultDoc.root().pushBack(inheritedAuthenticationRestrictionsElement)); + auto swData = resolveRoles(opCtx, roleNames, option); + if (!swData.isOK()) { + return swData.getStatus(); + } + auto data = std::move(swData.getValue()); + data.roles->insert(roleNames.cbegin(), roleNames.cend()); + serializeResolvedRoles(&fragment, data, BSONObj()); + *result = fragment.obj(); + return Status::OK(); + } - for (const auto& restrictions : _roleGraph.getAllAuthenticationRestrictions(roleName)) { - fassert(40562, - inheritedAuthenticationRestrictionsElement.appendArray( - "", restrictions->toBSON())); + BSONArrayBuilder rolesBuilder; + for (const RoleName& role : roleNames) { + try { + BSONObj roleDoc; + auto status = findOne( + opCtx, AuthorizationManager::rolesCollectionNamespace, role.toBSON(), &roleDoc); + if (!status.isOK()) { + if (status.code() == ErrorCodes::NoMatchingDocument) { + continue; + } + uassertStatusOK(status); // throws } + + BSONObjBuilder roleBuilder(rolesBuilder.subobjStart()); + auto subRoles = filterAndMapRole(&roleBuilder, roleDoc, option); + auto data = uassertStatusOK(resolveRoles(opCtx, subRoles, option)); + data.roles->insert(subRoles.cbegin(), subRoles.cend()); + serializeResolvedRoles(&roleBuilder, data, roleDoc); + + roleBuilder.doneFast(); + } catch (const AssertionException& ex) { + return {ex.code(), + str::stream() << "Failed fetching role '" << role.getFullName() + << "': " << ex.reason()}; } - } else if (showPrivileges == PrivilegeFormat::kShowSeparate) { - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); - fassert(40557, - warningsElement.appendString("", - "Role graph state inconsistent; only direct " - "privileges and restrictions available.")); } - if (warningsElement.hasChildren()) { - fassert(17167, resultDoc.root().pushBack(warningsElement)); - } - *result = resultDoc.getObject(); + *result = rolesBuilder.arr(); return Status::OK(); } @@ -457,26 +576,70 @@ Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB( PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat showRestrictions, bool showBuiltinRoles, - vector<BSONObj>* result) { + BSONArrayBuilder* result) { + auto option = makeResolveRoleOption(showPrivileges, showRestrictions); + if (showPrivileges == PrivilegeFormat::kShowAsUserFragment) { - return Status(ErrorCodes::IllegalOperation, - "Cannot get user fragment for all roles in a database"); + return {ErrorCodes::IllegalOperation, + "Cannot get user fragment for all roles in a database"}; } - stdx::lock_guard<Latch> lk(_roleGraphMutex); - for (RoleNameIterator it = _roleGraph.getRolesForDatabase(dbname); it.more(); it.next()) { - if (!showBuiltinRoles && _roleGraph.isBuiltinRole(it.get())) { - continue; - } - BSONObj roleDoc; - Status status = - _getRoleDescription_inlock(it.get(), showPrivileges, showRestrictions, &roleDoc); - if (!status.isOK()) { - return status; + if (showBuiltinRoles) { + for (const auto& roleName : RoleGraph::getBuiltinRoleNamesForDB(dbname)) { + BSONObjBuilder roleBuilder(result->subobjStart()); + + roleBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole()); + roleBuilder.append(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB()); + roleBuilder.append("isBuiltin", true); + + roleBuilder.append("roles", BSONArray()); + roleBuilder.append("inheritedRoles", BSONArray()); + + if (showPrivileges == PrivilegeFormat::kShowSeparate) { + BSONArrayBuilder privsBuilder(roleBuilder.subarrayStart("privileges")); + PrivilegeVector privs; + RoleGraph::addPrivilegesForBuiltinRole(roleName, &privs); + for (const auto& privilege : privs) { + privsBuilder.append(privilege.toBSON()); + } + privsBuilder.doneFast(); + + // Builtin roles have identival privs/inheritedPrivs + BSONArrayBuilder ipBuilder(roleBuilder.subarrayStart("inheritedPrivileges")); + for (const auto& privilege : privs) { + ipBuilder.append(privilege.toBSON()); + } + ipBuilder.doneFast(); + } + + if (showRestrictions == AuthenticationRestrictionsFormat::kShow) { + roleBuilder.append("authenticationRestrictions", BSONArray()); + roleBuilder.append("inheritedAuthenticationRestrictions", BSONArray()); + } + + roleBuilder.doneFast(); } - result->push_back(roleDoc); } - return Status::OK(); + + return query(opCtx, + AuthorizationManager::rolesCollectionNamespace, + BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname), + BSONObj(), + [&](const BSONObj& roleDoc) { + try { + BSONObjBuilder roleBuilder(result->subobjStart()); + + auto subRoles = filterAndMapRole(&roleBuilder, roleDoc, option); + roleBuilder.append("isBuiltin", false); + auto data = uassertStatusOK(resolveRoles(opCtx, subRoles, option)); + data.roles->insert(subRoles.cbegin(), subRoles.cend()); + serializeResolvedRoles(&roleBuilder, data, roleDoc); + roleBuilder.doneFast(); + return Status::OK(); + } catch (const AssertionException& ex) { + return ex.toStatus(); + } + }); } namespace { 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 0832d9ea4fa..3e434c305b5 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.h +++ b/src/mongo/db/auth/authz_manager_external_state_local.h @@ -63,11 +63,9 @@ public: 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, - AuthenticationRestrictionsFormat, - BSONObj* result) override; + StatusWith<ResolvedRoleData> resolveRoles(OperationContext* opCtx, + const std::vector<RoleName>& roleNames, + ResolveRoleOption option) override; Status getRolesDescription(OperationContext* opCtx, const std::vector<RoleName>& roles, PrivilegeFormat showPrivileges, @@ -78,7 +76,7 @@ public: PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat, bool showBuiltinRoles, - std::vector<BSONObj>* result) override; + BSONArrayBuilder* result) override; bool hasAnyPrivilegeDocuments(OperationContext* opCtx) final { return _hasAnyPrivilegeDocuments.load(); @@ -158,11 +156,6 @@ private: */ Status _getUserDocument(OperationContext* opCtx, const UserName& userName, BSONObj* result); - Status _getRoleDescription_inlock(const RoleName& roleName, - PrivilegeFormat showPrivileges, - AuthenticationRestrictionsFormat showRestrictions, - BSONObj* result); - /** * Returns true if the auth DB contains any users or roles. */ 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 d43be5722ce..0d521a66b36 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -243,75 +243,25 @@ Status AuthzManagerExternalStateMongos::rolesExist(OperationContext* opCtx, return ex.toStatus(); } -Status AuthzManagerExternalStateMongos::getRoleDescription( - OperationContext* opCtx, - const RoleName& roleName, - PrivilegeFormat showPrivileges, - AuthenticationRestrictionsFormat showRestrictions, - BSONObj* result) { - BSONObjBuilder rolesInfoCmd; - rolesInfoCmd.append( - "rolesInfo", - BSON_ARRAY(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME - << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME - << roleName.getDB()))); - addShowToBuilder(&rolesInfoCmd, showPrivileges, showRestrictions); - - BSONObjBuilder builder; - const bool ok = Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand( - opCtx, "admin", rolesInfoCmd.obj(), &builder); - BSONObj cmdResult = builder.obj(); - if (!ok) { - return getStatusFromCommandResult(cmdResult); - } - - std::vector<BSONElement> foundRoles = cmdResult[rolesFieldName(showPrivileges)].Array(); - if (foundRoles.size() == 0) { - return Status(ErrorCodes::RoleNotFound, "Role \"" + roleName.toString() + "\" not found"); - } - - if (foundRoles.size() > 1) { - return Status(ErrorCodes::RoleDataInconsistent, - str::stream() << "Found multiple roles on the \"" << roleName.getDB() - << "\" database with name \"" << roleName.getRole() << "\""); - } - *result = foundRoles[0].Obj().getOwned(); - return Status::OK(); +using ResolvedRoleData = AuthzManagerExternalState::ResolvedRoleData; +StatusWith<ResolvedRoleData> AuthzManagerExternalStateMongos::resolveRoles( + OperationContext* opCtx, const std::vector<RoleName>& roleNames, ResolveRoleOption option) { + // mongos never calls into resolveRoles(). + // That's done exclusively by mongod's User acquisition and user management commands. + dassert(false); + return {ErrorCodes::NotImplemented, "AuthzManagerExternalStateMongos::resolveRoles"}; } + Status AuthzManagerExternalStateMongos::getRolesDescription( OperationContext* opCtx, const std::vector<RoleName>& roles, PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat showRestrictions, BSONObj* result) { - BSONArrayBuilder rolesInfoCmdArray; - - for (const RoleName& roleName : roles) { - rolesInfoCmdArray << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME - << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME - << roleName.getDB()); - } - - BSONObjBuilder rolesInfoCmd; - rolesInfoCmd.append("rolesInfo", rolesInfoCmdArray.arr()); - addShowToBuilder(&rolesInfoCmd, showPrivileges, showRestrictions); - - BSONObjBuilder builder; - const bool ok = Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand( - opCtx, "admin", rolesInfoCmd.obj(), &builder); - BSONObj cmdResult = builder.obj(); - if (!ok) { - return getStatusFromCommandResult(cmdResult); - } - - std::vector<BSONElement> foundRoles = cmdResult[rolesFieldName(showPrivileges)].Array(); - if (foundRoles.size() == 0) { - return Status(ErrorCodes::RoleNotFound, "Roles not found"); - } - - *result = foundRoles[0].Obj().getOwned(); - - return Status::OK(); + // mongos never calls into resolveRoles(). + // That's done exclusively by mongod's User acquisition and user management commands. + dassert(false); + return {ErrorCodes::NotImplemented, "AuthzManagerExternalStateMongos::resolveRoles"}; } Status AuthzManagerExternalStateMongos::getRoleDescriptionsForDB( OperationContext* opCtx, @@ -319,24 +269,11 @@ Status AuthzManagerExternalStateMongos::getRoleDescriptionsForDB( PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat showRestrictions, bool showBuiltinRoles, - std::vector<BSONObj>* result) { - BSONObjBuilder rolesInfoCmd; - rolesInfoCmd << "rolesInfo" << 1 << "showBuiltinRoles" << showBuiltinRoles; - addShowToBuilder(&rolesInfoCmd, showPrivileges, showRestrictions); - - BSONObjBuilder builder; - const bool ok = Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand( - opCtx, dbname.toString(), rolesInfoCmd.obj(), &builder); - BSONObj cmdResult = builder.obj(); - if (!ok) { - return getStatusFromCommandResult(cmdResult); - } - - for (BSONObjIterator it(cmdResult[rolesFieldName(showPrivileges)].Obj()); it.more(); - it.next()) { - result->push_back((*it).Obj().getOwned()); - } - return Status::OK(); + BSONArrayBuilder* result) { + // mongos never calls into resolveRoles(). + // That's done exclusively by mongod's User acquisition and user management commands. + dassert(false); + return {ErrorCodes::NotImplemented, "AuthzManagerExternalStateMongos::resolveRoles"}; } bool AuthzManagerExternalStateMongos::hasAnyPrivilegeDocuments(OperationContext* opCtx) { 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 4d310c6aa47..00616c2ab26 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.h +++ b/src/mongo/db/auth/authz_manager_external_state_s.h @@ -59,11 +59,9 @@ public: Status getUserDescription(OperationContext* opCtx, const UserRequest& user, BSONObj* result) final; - Status getRoleDescription(OperationContext* opCtx, - const RoleName& roleName, - PrivilegeFormat showPrivileges, - AuthenticationRestrictionsFormat, - BSONObj* result) final; + StatusWith<ResolvedRoleData> resolveRoles(OperationContext* opCtx, + const std::vector<RoleName>& roleNames, + ResolveRoleOption option) override; Status getRolesDescription(OperationContext* opCtx, const std::vector<RoleName>& roles, PrivilegeFormat showPrivileges, @@ -74,7 +72,7 @@ public: PrivilegeFormat showPrivileges, AuthenticationRestrictionsFormat, bool showBuiltinRoles, - std::vector<BSONObj>* result) final; + BSONArrayBuilder* result) final; bool hasAnyPrivilegeDocuments(OperationContext* opCtx) final; }; diff --git a/src/mongo/db/auth/privilege.cpp b/src/mongo/db/auth/privilege.cpp index 033ec7cbb08..032124b1a52 100644 --- a/src/mongo/db/auth/privilege.cpp +++ b/src/mongo/db/auth/privilege.cpp @@ -84,6 +84,12 @@ BSONObj Privilege::toBSON() const { return pp.toBSON(); } +Privilege Privilege::fromBSON(const BSONElement elem) { + uassert( + ErrorCodes::BadValue, "Privilege documents must be of type object", elem.type() == Object); + return fromBSON(elem.Obj()); +} + Privilege Privilege::fromBSON(BSONObj obj) { ParsedPrivilege pp; std::string errmsg; diff --git a/src/mongo/db/auth/privilege.h b/src/mongo/db/auth/privilege.h index f610e8adf94..633b6fce74c 100644 --- a/src/mongo/db/auth/privilege.h +++ b/src/mongo/db/auth/privilege.h @@ -87,6 +87,7 @@ public: // Checks if the given actions are present in the Privilege. bool includesActions(const ActionSet& actions) const; + static Privilege fromBSON(const BSONElement obj); static Privilege fromBSON(BSONObj obj); BSONObj toBSON() const; diff --git a/src/mongo/db/auth/role_graph.h b/src/mongo/db/auth/role_graph.h index 52343322b68..4d68d47e3ea 100644 --- a/src/mongo/db/auth/role_graph.h +++ b/src/mongo/db/auth/role_graph.h @@ -73,6 +73,11 @@ public: */ static bool addPrivilegesForBuiltinRole(const RoleName& role, PrivilegeVector* privileges); + /** + * Ennumerate all builtin RoleNames for the given database. + */ + static stdx::unordered_set<RoleName> getBuiltinRoleNamesForDB(StringData dbname); + // Swaps the contents of this RoleGraph with those of "other" void swap(RoleGraph& other); diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp index 9182cd4a997..e677dd68e32 100644 --- a/src/mongo/db/auth/role_graph_builtin_roles.cpp +++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp @@ -664,54 +664,96 @@ void addInternalRolePrivileges(PrivilegeVector* privileges) { RoleGraph::generateUniversalPrivileges(privileges); } +class BuiltinRoleDefinition { +public: + BuiltinRoleDefinition() = delete; + + using AddPrivilegesFn = void (*)(PrivilegeVector*); + BuiltinRoleDefinition(bool adminOnly, AddPrivilegesFn fn) + : _adminOnly(adminOnly), _addPrivileges(fn) {} + + using AddPrivilegesWithDBFn = void (*)(PrivilegeVector*, StringData); + BuiltinRoleDefinition(bool adminOnly, AddPrivilegesWithDBFn fn) + : _adminOnly(adminOnly), _addPrivilegesWithDB(fn) {} + + bool adminOnly() const { + return _adminOnly; + } + + void operator()(PrivilegeVector* result, StringData dbname) const { + if (_addPrivileges) { + dassert(!_addPrivilegesWithDB); + _addPrivileges(result); + } else { + dassert(_addPrivilegesWithDB); + _addPrivilegesWithDB(result, dbname); + } + } + +private: + bool _adminOnly; + AddPrivilegesFn _addPrivileges = nullptr; + AddPrivilegesWithDBFn _addPrivilegesWithDB = nullptr; +}; + +const std::map<StringData, BuiltinRoleDefinition> kBuiltinRoles({ + // All DBs. + {BUILTIN_ROLE_READ, {false, addReadOnlyDbPrivileges}}, + {BUILTIN_ROLE_READ_WRITE, {false, addReadWriteDbPrivileges}}, + {BUILTIN_ROLE_USER_ADMIN, {false, addUserAdminDbPrivileges}}, + {BUILTIN_ROLE_DB_ADMIN, {false, addDbAdminDbPrivileges}}, + {BUILTIN_ROLE_DB_OWNER, {false, addDbOwnerPrivileges}}, + {BUILTIN_ROLE_ENABLE_SHARDING, {false, addEnableShardingPrivileges}}, + // Admin Only. + {BUILTIN_ROLE_READ_ANY_DB, {true, addReadOnlyAnyDbPrivileges}}, + {BUILTIN_ROLE_READ_WRITE_ANY_DB, {true, addReadWriteAnyDbPrivileges}}, + {BUILTIN_ROLE_USER_ADMIN_ANY_DB, {true, addUserAdminAnyDbPrivileges}}, + {BUILTIN_ROLE_DB_ADMIN_ANY_DB, {true, addDbAdminAnyDbPrivileges}}, + {BUILTIN_ROLE_CLUSTER_MONITOR, {true, addClusterMonitorPrivileges}}, + {BUILTIN_ROLE_HOST_MANAGEMENT, {true, addHostManagerPrivileges}}, + {BUILTIN_ROLE_CLUSTER_MANAGEMENT, {true, addClusterManagerPrivileges}}, + {BUILTIN_ROLE_CLUSTER_ADMIN, {true, addClusterAdminPrivileges}}, + {BUILTIN_ROLE_QUERYABLE_BACKUP, {true, addQueryableBackupPrivileges}}, + {BUILTIN_ROLE_BACKUP, {true, addBackupPrivileges}}, + {BUILTIN_ROLE_RESTORE, {true, addRestorePrivileges}}, + {BUILTIN_ROLE_ROOT, {true, addRootRolePrivileges}}, + {BUILTIN_ROLE_INTERNAL, {true, addInternalRolePrivileges}}, +}); + } // namespace +stdx::unordered_set<RoleName> RoleGraph::getBuiltinRoleNamesForDB(StringData dbname) { + const bool isAdmin = dbname == ADMIN_DBNAME; + + stdx::unordered_set<RoleName> roleNames; + for (const auto& [role, def] : kBuiltinRoles) { + if (isAdmin || !def.adminOnly()) { + roleNames.insert(RoleName(role, dbname)); + } + } + return roleNames; +} + bool RoleGraph::addPrivilegesForBuiltinRole(const RoleName& roleName, PrivilegeVector* result) { - const bool isAdminDB = (roleName.getDB() == ADMIN_DBNAME); - - if (roleName.getRole() == BUILTIN_ROLE_READ) { - addReadOnlyDbPrivileges(result, roleName.getDB()); - } else if (roleName.getRole() == BUILTIN_ROLE_READ_WRITE) { - addReadWriteDbPrivileges(result, roleName.getDB()); - } else if (roleName.getRole() == BUILTIN_ROLE_USER_ADMIN) { - addUserAdminDbPrivileges(result, roleName.getDB()); - } else if (roleName.getRole() == BUILTIN_ROLE_DB_ADMIN) { - addDbAdminDbPrivileges(result, roleName.getDB()); - } else if (roleName.getRole() == BUILTIN_ROLE_DB_OWNER) { - addDbOwnerPrivileges(result, roleName.getDB()); - } else if (roleName.getRole() == BUILTIN_ROLE_ENABLE_SHARDING) { - addEnableShardingPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_READ_ANY_DB) { - addReadOnlyAnyDbPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_READ_WRITE_ANY_DB) { - addReadWriteAnyDbPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_USER_ADMIN_ANY_DB) { - addUserAdminAnyDbPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_DB_ADMIN_ANY_DB) { - addDbAdminAnyDbPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_MONITOR) { - addClusterMonitorPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_HOST_MANAGEMENT) { - addHostManagerPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_MANAGEMENT) { - addClusterManagerPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_CLUSTER_ADMIN) { - addClusterAdminPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_QUERYABLE_BACKUP) { - addQueryableBackupPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_BACKUP) { - addBackupPrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_RESTORE) { - addRestorePrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_ROOT) { - addRootRolePrivileges(result); - } else if (isAdminDB && roleName.getRole() == BUILTIN_ROLE_INTERNAL) { - addInternalRolePrivileges(result); - } else { + auto role = roleName.getRole(); + auto dbname = roleName.getDB(); + + if (!NamespaceString::validDBName(dbname, NamespaceString::DollarInDbNameBehavior::Allow) || + dbname == "$external") { + return false; + } + + auto it = kBuiltinRoles.find(role); + if (it == kBuiltinRoles.end()) { return false; } + const auto& def = it->second; - // One of the roles has matched, otherwise we would have returned already. + if (def.adminOnly() && (dbname != ADMIN_DBNAME)) { + return false; + } + + def(result, dbname); return true; } @@ -722,78 +764,28 @@ void RoleGraph::generateUniversalPrivileges(PrivilegeVector* privileges) { } bool RoleGraph::isBuiltinRole(const RoleName& role) { - if (!NamespaceString::validDBName(role.getDB(), - NamespaceString::DollarInDbNameBehavior::Allow) || - role.getDB() == "$external") { + auto dbname = role.getDB(); + if (!NamespaceString::validDBName(dbname, NamespaceString::DollarInDbNameBehavior::Allow) || + dbname == "$external") { return false; } - bool isAdminDB = role.getDB() == ADMIN_DBNAME; - - if (role.getRole() == BUILTIN_ROLE_READ) { - return true; - } else if (role.getRole() == BUILTIN_ROLE_READ_WRITE) { - return true; - } else if (role.getRole() == BUILTIN_ROLE_USER_ADMIN) { - return true; - } else if (role.getRole() == BUILTIN_ROLE_DB_ADMIN) { - return true; - } else if (role.getRole() == BUILTIN_ROLE_DB_OWNER) { - return true; - } else if (role.getRole() == BUILTIN_ROLE_ENABLE_SHARDING) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_READ_ANY_DB) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_READ_WRITE_ANY_DB) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_USER_ADMIN_ANY_DB) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_DB_ADMIN_ANY_DB) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_MONITOR) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_HOST_MANAGEMENT) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_MANAGEMENT) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_CLUSTER_ADMIN) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_BACKUP) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_RESTORE) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_ROOT) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_INTERNAL) { - return true; - } else if (isAdminDB && role.getRole() == BUILTIN_ROLE_QUERYABLE_BACKUP) { - return true; + const auto it = kBuiltinRoles.find(role.getRole()); + if (it == kBuiltinRoles.end()) { + return false; } - return false; + + return !it->second.adminOnly() || (dbname == ADMIN_DBNAME); } void RoleGraph::_createBuiltinRolesForDBIfNeeded(StringData dbname) { - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_OWNER, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_ENABLE_SHARDING, dbname)); - - if (dbname == "admin") { - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN_ANY_DB, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MONITOR, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_HOST_MANAGEMENT, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MANAGEMENT, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_ADMIN, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_BACKUP, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_RESTORE, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_ROOT, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_INTERNAL, dbname)); - _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_QUERYABLE_BACKUP, dbname)); + const bool isAdmin = dbname == ADMIN_DBNAME; + + for (const auto& [rolename, def] : kBuiltinRoles) { + if (def.adminOnly() && !isAdmin) { + continue; + } + _createBuiltinRoleIfNeeded(RoleName(rolename, dbname)); } } diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 47f5bc48ee7..15b40b7ddc6 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -88,45 +88,21 @@ Status useDefaultCode(const Status& status, ErrorCodes::Error defaultCode) { return Status(defaultCode, status.reason()); } -BSONArray roleSetToBSONArray(const stdx::unordered_set<RoleName>& roles) { - BSONArrayBuilder rolesArrayBuilder; - for (stdx::unordered_set<RoleName>::const_iterator it = roles.begin(); it != roles.end(); - ++it) { - const RoleName& role = *it; - rolesArrayBuilder.append(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME - << role.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME - << role.getDB())); - } - return rolesArrayBuilder.arr(); -} - -BSONArray rolesVectorToBSONArray(const std::vector<RoleName>& roles) { - BSONArrayBuilder rolesArrayBuilder; - for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { - const RoleName& role = *it; - rolesArrayBuilder.append(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME - << role.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME - << role.getDB())); +template <typename Container> +BSONArray containerToBSONArray(const Container& container) { + BSONArrayBuilder arrayBuilder; + for (const auto& item : container) { + arrayBuilder.append(item.toBSON()); } - return rolesArrayBuilder.arr(); + return arrayBuilder.arr(); } Status privilegeVectorToBSONArray(const PrivilegeVector& privileges, BSONArray* result) { - BSONArrayBuilder arrBuilder; - for (PrivilegeVector::const_iterator it = privileges.begin(); it != privileges.end(); ++it) { - const Privilege& privilege = *it; - - ParsedPrivilege parsedPrivilege; - std::string errmsg; - if (!ParsedPrivilege::privilegeToParsedPrivilege(privilege, &parsedPrivilege, &errmsg)) { - return Status(ErrorCodes::FailedToParse, errmsg); - } - if (!parsedPrivilege.isValid(&errmsg)) { - return Status(ErrorCodes::FailedToParse, errmsg); - } - arrBuilder.append(parsedPrivilege.toBSON()); - } - *result = arrBuilder.arr(); + // privileges may come in with non-unique ResourcePatterns. + // Make a local copy so that ActionSets are merged. + PrivilegeVector uniquePrivileges; + Privilege::addPrivilegesToPrivilegeVector(&uniquePrivileges, privileges); + *result = containerToBSONArray(uniquePrivileges); return Status::OK(); } @@ -158,47 +134,42 @@ Status getCurrentUserRoles(OperationContext* opCtx, */ Status checkOkayToGrantRolesToRole(OperationContext* opCtx, const RoleName& role, - const std::vector<RoleName> rolesToAdd, + const std::vector<RoleName>& rolesToAdd, AuthorizationManager* authzManager) { - for (std::vector<RoleName>::const_iterator it = rolesToAdd.begin(); it != rolesToAdd.end(); - ++it) { - const RoleName& roleToAdd = *it; + for (const auto& roleToAdd : rolesToAdd) { if (roleToAdd == role) { - return Status(ErrorCodes::InvalidRoleModification, - str::stream() - << "Cannot grant role " << role.getFullName() << " to itself."); + return {ErrorCodes::InvalidRoleModification, + str::stream() << "Cannot grant role " << role.getFullName() << " to itself."}; } if (role.getDB() != "admin" && roleToAdd.getDB() != role.getDB()) { - return Status(ErrorCodes::InvalidRoleModification, - str::stream() - << "Roles on the \'" << role.getDB() - << "\' database cannot be granted roles from other databases"); + return {ErrorCodes::InvalidRoleModification, + str::stream() << "Roles on the \'" << role.getDB() + << "\' database cannot be granted roles from other databases"}; } + } - BSONObj roleToAddDoc; - Status status = authzManager->getRoleDescription(opCtx, roleToAdd, &roleToAddDoc); - if (status == ErrorCodes::RoleNotFound) { - return Status(ErrorCodes::RoleNotFound, - "Cannot grant nonexistent role " + roleToAdd.toString()); - } - if (!status.isOK()) { - return status; - } - std::vector<RoleName> indirectRoles; - status = auth::parseRoleNamesFromBSONArray( - BSONArray(roleToAddDoc["inheritedRoles"].Obj()), role.getDB(), &indirectRoles); - if (!status.isOK()) { - return status; - } + auto status = authzManager->rolesExist(opCtx, rolesToAdd); + if (!status.isOK()) { + return {status.code(), + str::stream() << "Cannot grant roles to '" << role.toString() + << "': " << status.reason()}; + } - if (sequenceContains(indirectRoles, role)) { - return Status(ErrorCodes::InvalidRoleModification, - str::stream() << "Granting " << roleToAdd.getFullName() << " to " - << role.getFullName() - << " would introduce a cycle in the role graph."); - } + auto swData = authzManager->resolveRoles( + opCtx, rolesToAdd, AuthorizationManager::ResolveRoleOption::kRoles); + if (!swData.isOK()) { + return {swData.getStatus().code(), + str::stream() << "Cannot grant roles to '" << role.toString() + << "': " << swData.getStatus().reason()}; + } + + if (sequenceContains(swData.getValue().roles.get(), role)) { + return {ErrorCodes::InvalidRoleModification, + str::stream() << "Granting roles to " << role.getFullName() + << " would introduce a cycle in the role graph"}; } + return Status::OK(); } @@ -1093,7 +1064,7 @@ void CmdUMCTyped<GrantRolesToUserCommand, void>::Invocation::typedRun(OperationC } audit::logGrantRolesToUser(client, userName, resolvedRoleNames); - auto newRolesBSONArray = roleSetToBSONArray(userRoles); + auto newRolesBSONArray = containerToBSONArray(userRoles); auto status = updatePrivilegeDocument( opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); @@ -1128,7 +1099,7 @@ void CmdUMCTyped<RevokeRolesFromUserCommand, void>::Invocation::typedRun(Operati } audit::logRevokeRolesFromUser(client, userName, resolvedUserRoles); - BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles); + BSONArray newRolesBSONArray = containerToBSONArray(userRoles); auto status = updatePrivilegeDocument( opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); @@ -1323,7 +1294,7 @@ void CmdUMCTyped<CreateRoleCommand, void>::Invocation::typedRun(OperationContext roleObjBuilder.append("privileges", privileges); auto resolvedRoleNames = auth::resolveRoleNames(cmd.getRoles(), dbname); - roleObjBuilder.append("roles", rolesVectorToBSONArray(resolvedRoleNames)); + roleObjBuilder.append("roles", containerToBSONArray(resolvedRoleNames)); boost::optional<BSONArray> bsonAuthRestrictions; if (auto ar = cmd.getAuthenticationRestrictions(); ar && !ar->empty()) { @@ -1372,7 +1343,7 @@ void CmdUMCTyped<UpdateRoleCommand, void>::Invocation::typedRun(OperationContext boost::optional<std::vector<RoleName>> optRoles; if (auto roles = cmd.getRoles()) { optRoles = auth::resolveRoleNames(roles.get(), dbname); - updateSetBuilder.append("roles", rolesVectorToBSONArray(*optRoles)); + updateSetBuilder.append("roles", containerToBSONArray(*optRoles)); } BSONArray authRest; @@ -1444,17 +1415,10 @@ void CmdUMCTyped<GrantPrivilegesToRoleCommand, void>::Invocation::typedRun( uassertStatusOK(checkOkayToGrantPrivilegesToRole(roleName, cmd.getPrivileges())); - BSONObj roleDoc; - uassertStatusOK(authzManager->getRoleDescription(opCtx, - roleName, - PrivilegeFormat::kShowSeparate, - AuthenticationRestrictionsFormat::kOmit, - &roleDoc)); - - PrivilegeVector privileges; - uassertStatusOK( - auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), &privileges)); - + // Add additional privileges to existing set. + auto data = uassertStatusOK(authzManager->resolveRoles( + opCtx, {roleName}, AuthorizationManager::ResolveRoleOption::kDirectPrivileges)); + auto privileges = std::move(data.privileges.get()); for (const auto& priv : cmd.getPrivileges()) { Privilege::addPrivilegeToPrivilegeVector(&privileges, priv); } @@ -1499,17 +1463,9 @@ void CmdUMCTyped<RevokePrivilegesFromRoleCommand, void>::Invocation::typedRun( auto* authzManager = AuthorizationManager::get(serviceContext); auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - BSONObj roleDoc; - uassertStatusOK(authzManager->getRoleDescription(opCtx, - roleName, - PrivilegeFormat::kShowSeparate, - AuthenticationRestrictionsFormat::kOmit, - &roleDoc)); - - PrivilegeVector privileges; - uassertStatusOK( - auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), &privileges)); - + auto data = uassertStatusOK(authzManager->resolveRoles( + opCtx, {roleName}, AuthorizationManager::ResolveRoleOption::kDirectPrivileges)); + auto privileges = std::move(data.privileges.get()); for (const auto& rmPriv : cmd.getPrivileges()) { for (auto it = privileges.begin(); it != privileges.end(); ++it) { if (it->getResourcePattern() == rmPriv.getResourcePattern()) { @@ -1563,28 +1519,19 @@ void CmdUMCTyped<GrantRolesToRoleCommand, void>::Invocation::typedRun(OperationC auto* authzManager = AuthorizationManager::get(serviceContext); auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - // Role existence has to be checked after acquiring the update lock - BSONObj roleDoc; - uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &roleDoc)); - // Check for cycles uassertStatusOK(checkOkayToGrantRolesToRole(opCtx, roleName, rolesToAdd, authzManager)); // Add new roles to existing roles - std::vector<RoleName> directRoles; - uassertStatusOK(auth::parseRoleNamesFromBSONArray( - BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &directRoles)); - for (const auto& roleToAdd : rolesToAdd) { - if (!sequenceContains(directRoles, roleToAdd)) { - // Don't double-add role - directRoles.push_back(roleToAdd); - } - } + auto data = uassertStatusOK(authzManager->resolveRoles( + opCtx, {roleName}, AuthorizationManager::ResolveRoleOption::kDirectRoles)); + auto directRoles = std::move(data.roles.get()); + directRoles.insert(rolesToAdd.cbegin(), rolesToAdd.cend()); audit::logGrantRolesToRole(client, roleName, rolesToAdd); auto status = updateRoleDocument( - opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(directRoles)))); + opCtx, roleName, BSON("$set" << BSON("roles" << containerToBSONArray(directRoles)))); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(opCtx); uassertStatusOK(status); @@ -1612,23 +1559,18 @@ void CmdUMCTyped<RevokeRolesFromRoleCommand, void>::Invocation::typedRun(Operati auto* authzManager = AuthorizationManager::get(serviceContext); auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - BSONObj roleDoc; - uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &roleDoc)); - - std::vector<RoleName> roles; - uassertStatusOK(auth::parseRoleNamesFromBSONArray( - BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &roles)); - + // Remove roles from existing set. + auto data = uassertStatusOK(authzManager->resolveRoles( + opCtx, {roleName}, AuthorizationManager::ResolveRoleOption::kDirectRoles)); + auto roles = std::move(data.roles.get()); for (const auto& roleToRemove : rolesToRemove) { - if (auto it = std::find(roles.begin(), roles.end(), roleToRemove); it != roles.end()) { - roles.erase(it); - } + roles.erase(roleToRemove); } audit::logRevokeRolesFromRole(client, roleName, rolesToRemove); auto status = updateRoleDocument( - opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(roles)))); + opCtx, roleName, BSON("$set" << BSON("roles" << containerToBSONArray(roles)))); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(opCtx); uassertStatusOK(status); @@ -1850,39 +1792,32 @@ public: const BSONObj& cmdObj, BSONObjBuilder& result) override { auth::RolesInfoArgs args; - Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args); - uassertStatusOK(status); + uassertStatusOK(auth::parseRolesInfoCommand(cmdObj, dbname, &args)); AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager)); if (args.allForDB) { - std::vector<BSONObj> rolesDocs; - status = authzManager->getRoleDescriptionsForDB(opCtx, - dbname, - args.privilegeFormat, - args.authenticationRestrictionsFormat, - args.showBuiltinRoles, - &rolesDocs); - uassertStatusOK(status); - if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) { uasserted(ErrorCodes::IllegalOperation, "Cannot get user fragment for all roles in a database"); } - BSONArrayBuilder rolesArrayBuilder; - for (size_t i = 0; i < rolesDocs.size(); ++i) { - rolesArrayBuilder.append(rolesDocs[i]); - } - result.append("roles", rolesArrayBuilder.arr()); - } else { - BSONObj roleDetails; - status = authzManager->getRolesDescription(opCtx, - args.roleNames, + + BSONArrayBuilder rolesBuilder(result.subarrayStart("roles")); + uassertStatusOK( + authzManager->getRoleDescriptionsForDB(opCtx, + dbname, args.privilegeFormat, args.authenticationRestrictionsFormat, - &roleDetails); - uassertStatusOK(status); + args.showBuiltinRoles, + &rolesBuilder)); + } else { + BSONObj roleDetails; + uassertStatusOK(authzManager->getRolesDescription(opCtx, + args.roleNames, + args.privilegeFormat, + args.authenticationRestrictionsFormat, + &roleDetails)); if (args.privilegeFormat == PrivilegeFormat::kShowAsUserFragment) { result.append("userFragment", roleDetails); diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp index 83df92fae4c..d10742585a1 100644 --- a/src/mongo/db/commands/user_management_commands_common.cpp +++ b/src/mongo/db/commands/user_management_commands_common.cpp @@ -53,12 +53,12 @@ namespace auth { std::vector<RoleName> resolveRoleNames(const std::vector<RoleNameOrString>& possibleRoles, StringData dbname) { - std::vector<RoleName> roles; - std::transform(possibleRoles.cbegin(), - possibleRoles.cend(), - std::back_inserter(roles), - [dbname](const auto& possibleRole) { return possibleRole.getRoleName(dbname); }); - return roles; + // De-duplicate as we resolve names by using a set. + stdx::unordered_set<RoleName> roles; + for (const auto& possibleRole : possibleRoles) { + roles.insert(possibleRole.getRoleName(dbname)); + } + return std::vector<RoleName>(roles.cbegin(), roles.cend()); } Status checkAuthorizedToGrantRoles(AuthorizationSession* authzSession, diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp index aef2b61d650..e62be95bb4c 100644 --- a/src/mongo/embedded/embedded_auth_manager.cpp +++ b/src/mongo/embedded/embedded_auth_manager.cpp @@ -78,11 +78,9 @@ public: UASSERT_NOT_IMPLEMENTED; } - Status getRoleDescription(OperationContext*, - const RoleName&, - PrivilegeFormat, - AuthenticationRestrictionsFormat, - BSONObj*) override { + StatusWith<ResolvedRoleData> resolveRoles(OperationContext*, + const std::vector<RoleName>&, + ResolveRoleOption) override { UASSERT_NOT_IMPLEMENTED; } @@ -99,7 +97,7 @@ public: PrivilegeFormat, AuthenticationRestrictionsFormat, bool, - std::vector<BSONObj>*) override { + BSONArrayBuilder*) override { UASSERT_NOT_IMPLEMENTED; } |