From 69d1897d0974aafc5f41b851ce61f62ab879c805 Mon Sep 17 00:00:00 2001 From: Markus Hentsch Date: Mon, 15 Jul 2024 11:09:55 +0200 Subject: [PATCH] Implement the Domain Manager Persona for Keystone Introduces domain-scoped policies for the 'manager' role to permit domain-wide management capabilities in regards to users, groups, projects and role assignments. Defines a new base policy rule to restrict the roles assignable by domain managers. Closes-Bug: #2045974 Change-Id: I62742ed7d906c92d1132251080758bb54d0fc8e1 --- doc/source/admin/service-api-protection.rst | 99 +++++++++++++++---- keystone/api/roles.py | 5 +- keystone/common/policies/base.py | 16 +++ keystone/common/policies/grant.py | 21 +++- keystone/common/policies/group.py | 33 ++++++- keystone/common/policies/project.py | 24 ++--- keystone/common/policies/role.py | 17 +++- keystone/common/policies/user.py | 16 ++- keystone/tests/unit/test_policy.py | 1 + ...main-manager-persona-7921587ce2fab4fd.yaml | 12 +++ 10 files changed, 194 insertions(+), 50 deletions(-) create mode 100644 releasenotes/notes/domain-manager-persona-7921587ce2fab4fd.yaml diff --git a/doc/source/admin/service-api-protection.rst b/doc/source/admin/service-api-protection.rst index 518f7928fe..1ae07229ab 100644 --- a/doc/source/admin/service-api-protection.rst +++ b/doc/source/admin/service-api-protection.rst @@ -26,10 +26,14 @@ functionality to their team, auditors, customers, and users without maintaining custom policies. In addition to ``admin``, ``member``, and ``reader`` role, from 2023.2 (Bobcat) -release keystone will provide ``service`` role by default as well. Operators -can use this role for service to service API calls instead of using ``admin`` -role for the same. The service role will be separate from ``admin``, -``member``, ``reader`` and will not implicate any of these roles. +release keystone will provide the ``service`` and ``manager`` roles by default +as well. Operators can use the ``service`` role for service to service API +calls instead of using ``admin`` role for the same. The service role will be +separate from ``admin``, ``member``, ``reader`` and will not implicate any of +these roles. +Operators can give the ``manager`` role to users to within a domain to enable +self-service management of users, groups, projects and role assignments within +their domain. .. _`token guide`: https://docs.openstack.org/keystone/latest/admin/tokens-overview.html#authorization-scopes @@ -39,14 +43,16 @@ Roles Definitions The default roles provided by keystone via ``keystone-manage bootstrap`` (except for the ``service`` role) are related through role implications. The -``admin`` role implies the ``member`` role, and the ``member`` role implies -the ``reader`` role. These implications mean users with the ``admin`` role -automatically have the ``member`` and ``reader`` roles. Additionally, -users with the ``member`` role automatically have the ``reader`` role. -Implying roles reduces role assignments and forms a natural hierarchy between -the default roles. It also reduces the complexity of default policies by -making check strings short. For example, a policy that requires ``reader`` -can be expressed as: +``admin`` role implies the ``manager`` role, the ``manager`` implies the +``member`` role, and the ``member`` role implies the ``reader`` role. These +implications mean users with the ``admin`` role automatically have the +``manager``, ``member`` and ``reader`` roles. Additionally, users with the +``manager`` role automatically have the ``member`` and ``reader`` roles. Users +with the ``member`` role automatically have the ``reader`` role. Implying roles +reduces role assignments and forms a natural hierarchy between the default +roles. It also reduces the complexity of default policies by making check +strings short. For example, a policy that requires ``reader`` can be expressed +as: .. code-block:: yaml @@ -56,7 +62,7 @@ Instead of: .. code-block:: yaml - "identity:list_foo": "role:admin or role:member or role:reader" + "identity:list_foo": "role:admin or role:manager or role:member or role:reader" Reader ====== @@ -122,6 +128,30 @@ users with ``admin`` on a project can list, create, and delete instances. Service developers can use the ``member`` role to provide more flexibility between ``admin`` and ``reader`` on different scopes. +Manager +======= + +The ``manager`` role takes a special place in keystone. It sits between the +``admin`` and ``member`` role, allowing limited identity management while being +clearly differentiated from the ``admin`` role both in terms of purpose and +privileges. The ``manager`` role is meant to be assigned in a domain scope and +enables users to manage identity assets in a whole domain including users, +projects, groups and role assignments. This enables identity self-service +management capabilities for users within a domain without the need to assign +the most privileged ``admin`` role to them. + +The keystone default policies include a special rule that specifies the list of +roles a user with the ``manager`` role is be able to assign and revoke within +the domain scope. This prevents such user from escalating their own privileges +or those of others beyond ``manager`` and for this purpose the list excludes +the ``admin`` role. The list can be adjusted by cloud administrators via policy +definitions in case the role model differs. For example, if a new role is +introduced for a specific cloud environment, the list can be adjusted to allow +users with the ``manager`` role to also assign it. + +Other services might write default policies to enable the ``manager`` role to +have more privileged managing rights or cross-project privileges in a domain. + Admin ===== @@ -255,14 +285,15 @@ role assignments on a specific domain using the following query: .. code-block:: console $ openstack role assignment list --names --domain foobar - +--------+-----------------+----------------------+---------+--------+--------+-----------+ - | Role | User | Group | Project | Domain | System | Inherited | - +--------+-----------------+----------------------+---------+--------+--------+-----------+ - | reader | support@Default | | | foobar | | False | - | admin | jsmith@Default | | | foobar | | False | - | admin | | foobar-admins@foobar | | foobar | | False | - | member | jdoe@foobar | | | foobar | | False | - +--------+-----------------+----------------------+---------+--------+--------+-----------+ + +---------+-----------------+----------------------+---------+--------+--------+-----------+ + | Role | User | Group | Project | Domain | System | Inherited | + +---------+-----------------+----------------------+---------+--------+--------+-----------+ + | reader | support@Default | | | foobar | | False | + | admin | jsmith@Default | | | foobar | | False | + | admin | | foobar-admins@foobar | | foobar | | False | + | manager | alice@foobar | | | foobar | | False | + | member | jdoe@foobar | | | foobar | | False | + +---------+-----------------+----------------------+---------+--------+--------+-----------+ Domain Administrators ===================== @@ -288,6 +319,32 @@ assignment: | admin | | foobar-admins@foobar | | foobar | | False | +-------+----------------+----------------------+---------+--------+--------+-----------+ +Domain Managers +=============== + +*Domain managers* can only manage specific resources related to identity +management within their domain. This includes creating new users, projects and +groups as well as updating and deleting them. They can also assign and revoke +roles between those or in relation to the domain. Furthermore, they can inspect +role assignments within the domain. + +*Domain managers* cannot change any aspects of the domain itself. The role +assignments they can apply within their domain is limited to a specific list of +applicable roles and in the default configuration, this excludes the ``admin`` +role to prevent privilege escalation. + +You can find *domain managers* in your deployment with the following role +assignment: + +.. code-block:: console + + $ openstack role assignment list --names --domain foobar --role manager + +---------+-----------------+----------------------+---------+--------+--------+-----------+ + | Role | User | Group | Project | Domain | System | Inherited | + +---------+-----------------+----------------------+---------+--------+--------+-----------+ + | manager | alice@foobar | | | foobar | | False | + +---------+-----------------+----------------------+---------+--------+--------+-----------+ + Domain Members & Domain Readers =============================== diff --git a/keystone/api/roles.py b/keystone/api/roles.py index 3f9be46066..e1585c6d0c 100644 --- a/keystone/api/roles.py +++ b/keystone/api/roles.py @@ -111,7 +111,10 @@ class RoleResource(ks_flask.ResourceBase): """ role = self.request_body_json.get('role', {}) if self._is_domain_role(role): - ENFORCER.enforce_call(action='identity:create_domain_role') + target = {'role': role} + ENFORCER.enforce_call( + action='identity:create_domain_role', target_attr=target + ) else: ENFORCER.enforce_call(action='identity:create_role') validation.lazy_validate(schema.role_create, role) diff --git a/keystone/common/policies/base.py b/keystone/common/policies/base.py index 4797d22d7a..3483ddfdff 100644 --- a/keystone/common/policies/base.py +++ b/keystone/common/policies/base.py @@ -65,6 +65,18 @@ ADMIN_OR_CRED_OWNER = ( '(' + RULE_ADMIN_REQUIRED + ') ' 'or user_id:%(target.credential.user_id)s' ) +# This rule template is meant for restricting role assignments done by domain +# managers. It is intended to restrict the roles a domain manager can assign or +# revoke to a sensible default set while allowing overrides via policy file by +# adjusting the corresponding rule definition. +# For the default, any roles with higher-level privileges than "manager" (e.g. +# "admin") must be omitted to avoid privilege escalation. +DOMAIN_MANAGER_ALLOWED_ROLES = ( + "'manager':%(target.role.name)s or " + "'member':%(target.role.name)s or " + "'reader':%(target.role.name)s" +) + rules = [ policy.RuleDefault( name='admin_required', check_str='role:admin or is_admin:1' @@ -89,6 +101,10 @@ rules = [ name='service_admin_or_token_subject', check_str='rule:service_or_admin or rule:token_subject', ), + policy.RuleDefault( + name="domain_managed_target_role", + check_str=DOMAIN_MANAGER_ALLOWED_ROLES, + ), ] diff --git a/keystone/common/policies/grant.py b/keystone/common/policies/grant.py index 45dfb78a43..097f57d7f2 100644 --- a/keystone/common/policies/grant.py +++ b/keystone/common/policies/grant.py @@ -61,6 +61,17 @@ GRANTS_DOMAIN_ADMIN = ( ' ' + DOMAIN_MATCHES_TARGET_DOMAIN + ')' ) +GRANTS_DOMAIN_MANAGER = ( + '(role:manager and ' + DOMAIN_MATCHES_USER_DOMAIN + ' and' + ' ' + DOMAIN_MATCHES_PROJECT_DOMAIN + ') or ' + '(role:manager and ' + DOMAIN_MATCHES_USER_DOMAIN + ' and' + ' ' + DOMAIN_MATCHES_TARGET_DOMAIN + ') or ' + '(role:manager and ' + DOMAIN_MATCHES_GROUP_DOMAIN + ' and' + ' ' + DOMAIN_MATCHES_PROJECT_DOMAIN + ') or ' + '(role:manager and ' + DOMAIN_MATCHES_GROUP_DOMAIN + ' and' + ' ' + DOMAIN_MATCHES_TARGET_DOMAIN + ')' +) + ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER = ( '(' + base.RULE_ADMIN_REQUIRED + ') or ' '(' + SYSTEM_READER_OR_DOMAIN_READER + ')' @@ -71,10 +82,12 @@ ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER_LIST = ( '(' + SYSTEM_READER_OR_DOMAIN_READER_LIST + ')' ) -ADMIN_OR_DOMAIN_ADMIN = ( +ADMIN_OR_DOMAIN_ADMIN_OR_DOMAIN_MANAGER = ( '(' + base.RULE_ADMIN_REQUIRED + ') or ' '(' + GRANTS_DOMAIN_ADMIN + ') and ' - '(' + DOMAIN_MATCHES_ROLE + ')' + '(' + DOMAIN_MATCHES_ROLE + ') or ' + '(' + GRANTS_DOMAIN_MANAGER + ') and ' + 'rule:domain_managed_target_role' ) DEPRECATED_REASON = ( @@ -236,7 +249,7 @@ grant_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'create_grant', - check_str=ADMIN_OR_DOMAIN_ADMIN, + check_str=ADMIN_OR_DOMAIN_ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description=( 'Create a role grant between a target and an actor. A ' @@ -251,7 +264,7 @@ grant_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'revoke_grant', - check_str=ADMIN_OR_DOMAIN_ADMIN, + check_str=ADMIN_OR_DOMAIN_ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description=( 'Revoke a role grant between a target and an actor. A ' diff --git a/keystone/common/policies/group.py b/keystone/common/policies/group.py index 8613524109..4a7e9d3af6 100644 --- a/keystone/common/policies/group.py +++ b/keystone/common/policies/group.py @@ -33,6 +33,14 @@ SYSTEM_READER_OR_DOMAIN_READER_FOR_TARGET_GROUP_USER = ( 'domain_id:%(target.group.domain_id)s and ' 'domain_id:%(target.user.domain_id)s)' ) +DOMAIN_MANAGER_FOR_TARGET_GROUP = ( + 'role:manager and domain_id:%(target.group.domain_id)s' +) +DOMAIN_MANAGER_FOR_TARGET_GROUP_USER = ( + 'role:manager and ' + 'domain_id:%(target.group.domain_id)s and ' + 'domain_id:%(target.user.domain_id)s' +) ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER_FOR_TARGET_GROUP = ( '(' + base.RULE_ADMIN_REQUIRED @@ -53,6 +61,21 @@ SYSTEM_ADMIN_OR_DOMAIN_ADMIN = ( '(role:admin and domain_id:%(target.group.domain_id)s)' ) +ADMIN_OR_DOMAIN_MANAGER_FOR_GROUPS = ( + '(' + + base.RULE_ADMIN_REQUIRED + + ') or (' + + DOMAIN_MANAGER_FOR_TARGET_GROUP + + ')' +) +ADMIN_OR_DOMAIN_MANAGER_FOR_GROUP_ASSIGNMENTS = ( + '(' + + base.RULE_ADMIN_REQUIRED + + ') or (' + + DOMAIN_MANAGER_FOR_TARGET_GROUP_USER + + ')' +) + DEPRECATED_REASON = ( "The group API is now aware of system scope and default roles." ) @@ -154,7 +177,7 @@ group_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'create_group', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER_FOR_GROUPS, scope_types=['system', 'domain', 'project'], description='Create group.', operations=[{'path': '/v3/groups', 'method': 'POST'}], @@ -162,7 +185,7 @@ group_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'update_group', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER_FOR_GROUPS, scope_types=['system', 'domain', 'project'], description='Update group.', operations=[{'path': '/v3/groups/{group_id}', 'method': 'PATCH'}], @@ -170,7 +193,7 @@ group_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'delete_group', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER_FOR_GROUPS, scope_types=['system', 'domain', 'project'], description='Delete group.', operations=[{'path': '/v3/groups/{group_id}', 'method': 'DELETE'}], @@ -189,7 +212,7 @@ group_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'remove_user_from_group', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER_FOR_GROUP_ASSIGNMENTS, scope_types=['system', 'domain', 'project'], description='Remove user from group.', operations=[ @@ -216,7 +239,7 @@ group_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'add_user_to_group', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER_FOR_GROUP_ASSIGNMENTS, scope_types=['system', 'domain', 'project'], description='Add user to group.', operations=[ diff --git a/keystone/common/policies/project.py b/keystone/common/policies/project.py index c36e58da77..b65a877a13 100644 --- a/keystone/common/policies/project.py +++ b/keystone/common/policies/project.py @@ -49,7 +49,7 @@ SYSTEM_READER_OR_DOMAIN_READER_OR_OWNER = ( # the context user_id to the target user id. 'user_id:%(target.user.id)s' ) -ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER_OR_OWNER = ( +ADMIN_SYSTEM_READER_OR_DOMAIN_READER_OR_OWNER = ( '(' + base.RULE_ADMIN_REQUIRED + ') or ' @@ -65,9 +65,9 @@ ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER = ( '(' + base.RULE_ADMIN_REQUIRED + ') or ' + SYSTEM_READER_OR_DOMAIN_READER ) -SYSTEM_ADMIN_OR_DOMAIN_ADMIN = ( - '(role:admin and system_scope:all) or ' - '(role:admin and domain_id:%(target.project.domain_id)s)' +ADMIN_OR_DOMAIN_MANAGER = ( + '(' + base.RULE_ADMIN_REQUIRED + ') or ' + '(role:manager and domain_id:%(target.project.domain_id)s)' ) DEPRECATED_REASON = ( @@ -175,7 +175,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'list_user_projects', - check_str=ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER_OR_OWNER, + check_str=ADMIN_SYSTEM_READER_OR_DOMAIN_READER_OR_OWNER, scope_types=['system', 'domain', 'project'], description='List projects for user.', operations=[{'path': '/v3/users/{user_id}/projects', 'method': 'GET'}], @@ -183,7 +183,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'create_project', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Create project.', operations=[{'path': '/v3/projects', 'method': 'POST'}], @@ -191,7 +191,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'update_project', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Update project.', operations=[{'path': '/v3/projects/{project_id}', 'method': 'PATCH'}], @@ -199,7 +199,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'delete_project', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Delete project.', operations=[{'path': '/v3/projects/{project_id}', 'method': 'DELETE'}], @@ -235,7 +235,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'update_project_tags', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Replace all tags on a project with the new set of tags.', operations=[ @@ -245,7 +245,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'create_project_tag', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Add a single tag to a project.', operations=[ @@ -255,7 +255,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'delete_project_tags', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Remove all tags from a project.', operations=[ @@ -265,7 +265,7 @@ project_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'delete_project_tag', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Delete a specified tag from project.', operations=[ diff --git a/keystone/common/policies/role.py b/keystone/common/policies/role.py index ab7fe577a7..e075a3c101 100644 --- a/keystone/common/policies/role.py +++ b/keystone/common/policies/role.py @@ -15,6 +15,19 @@ from oslo_policy import policy from keystone.common.policies import base +ADMIN_OR_SYSTEM_READER_OR_DOMAIN_MANAGER_ROLE = ( + '(' + base.RULE_ADMIN_OR_SYSTEM_READER + ') or ' + '(role:manager and rule:domain_managed_target_role)' +) + +# For the domain manager persona we only check for a domain id in the token +# that is not None here to exclude scopes like a project manager. Since most +# roles are global we do not have a target domain attribute to match against. +ADMIN_OR_SYSTEM_READER_OR_DOMAIN_MANAGER = ( + '(' + base.RULE_ADMIN_OR_SYSTEM_READER + ') or ' + '(role:manager and not domain_id:None)' +) + DEPRECATED_REASON = ( "The role API is now aware of system scope and default roles." ) @@ -84,7 +97,7 @@ deprecated_delete_domain_role = policy.DeprecatedRule( role_policies = [ policy.DocumentedRuleDefault( name=base.IDENTITY % 'get_role', - check_str=base.RULE_ADMIN_OR_SYSTEM_READER, + check_str=ADMIN_OR_SYSTEM_READER_OR_DOMAIN_MANAGER_ROLE, scope_types=['system', 'domain', 'project'], description='Show role details.', operations=[ @@ -95,7 +108,7 @@ role_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'list_roles', - check_str=base.RULE_ADMIN_OR_SYSTEM_READER, + check_str=ADMIN_OR_SYSTEM_READER_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='List roles.', operations=[ diff --git a/keystone/common/policies/user.py b/keystone/common/policies/user.py index f2f94139ed..edfc3c83b0 100644 --- a/keystone/common/policies/user.py +++ b/keystone/common/policies/user.py @@ -30,10 +30,16 @@ ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER_OR_USER = ( SYSTEM_READER_OR_DOMAIN_READER = ( '(' + base.SYSTEM_READER + ') or (' + base.DOMAIN_READER + ')' ) -ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER = ( + +ADMIN_SYSTEM_READER_OR_DOMAIN_READER = ( '(' + base.RULE_ADMIN_REQUIRED + ') or ' + SYSTEM_READER_OR_DOMAIN_READER ) +ADMIN_OR_DOMAIN_MANAGER = ( + '(' + base.RULE_ADMIN_REQUIRED + ') or ' + '(role:manager and token.domain.id:%(target.user.domain_id)s)' +) + DEPRECATED_REASON = ( "The user API is now aware of system scope and default roles." ) @@ -83,7 +89,7 @@ user_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'list_users', - check_str=ADMIN_OR_SYSTEM_READER_OR_DOMAIN_READER, + check_str=ADMIN_SYSTEM_READER_OR_DOMAIN_READER, scope_types=['system', 'domain', 'project'], description='List users.', operations=[ @@ -120,7 +126,7 @@ user_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'create_user', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Create a user.', operations=[{'path': '/v3/users', 'method': 'POST'}], @@ -128,7 +134,7 @@ user_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'update_user', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Update a user, including administrative password resets.', operations=[{'path': '/v3/users/{user_id}', 'method': 'PATCH'}], @@ -136,7 +142,7 @@ user_policies = [ ), policy.DocumentedRuleDefault( name=base.IDENTITY % 'delete_user', - check_str=base.RULE_ADMIN_REQUIRED, + check_str=ADMIN_OR_DOMAIN_MANAGER, scope_types=['system', 'domain', 'project'], description='Delete a user.', operations=[{'path': '/v3/users/{user_id}', 'method': 'DELETE'}], diff --git a/keystone/tests/unit/test_policy.py b/keystone/tests/unit/test_policy.py index 5961fc7be1..081747a635 100644 --- a/keystone/tests/unit/test_policy.py +++ b/keystone/tests/unit/test_policy.py @@ -244,6 +244,7 @@ class PolicyJsonTestCase(unit.TestCase): 'service_or_admin', 'service_role', 'token_subject', + 'domain_managed_target_role', ] def read_doc_targets(): diff --git a/releasenotes/notes/domain-manager-persona-7921587ce2fab4fd.yaml b/releasenotes/notes/domain-manager-persona-7921587ce2fab4fd.yaml new file mode 100644 index 0000000000..fae86f985e --- /dev/null +++ b/releasenotes/notes/domain-manager-persona-7921587ce2fab4fd.yaml @@ -0,0 +1,12 @@ +--- +features: + - > + [`bug 2045974 `_] + The Domain Manager Persona has been added. This makes identity-related + self-service capabilities for users within domains possible without + requiring the 'admin' role. Assigning the 'manager' role to users in domain + scope now allows them to manage projects, groups, users and role + assignments within the domain. This is subject to the following + restriction: the roles that domain managers can assign and revoke are + limited by a new ``domain_managed_target_role`` policy rule which defaults + to 'reader', 'member' and 'manager'.