diff --git a/doc/source/admin/service-api-protection.rst b/doc/source/admin/service-api-protection.rst index 2499443544..518f7928fe 100644 --- a/doc/source/admin/service-api-protection.rst +++ b/doc/source/admin/service-api-protection.rst @@ -25,21 +25,28 @@ Default roles and behaviors across scopes allow operators to delegate more 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. + .. _`token guide`: https://docs.openstack.org/keystone/latest/admin/tokens-overview.html#authorization-scopes ----------------- Roles Definitions ----------------- -The default roles provided by keystone, via ``keystone-manage bootstrap``, 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: +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: .. code-block:: yaml @@ -127,6 +134,36 @@ shouldn't be able to manage things outside the project because it would violate the tenancy of their role assignment (this doesn't apply consistently since services are addressing this individually at their own pace). +Service +======= + +We reserve the ``service`` role for Service-to-service communication. The aim +of a ``service`` role is to allow a service to communicate with another service +and possibly be granted elevated privileges by the service receiving the +request. Before the introduction of the ``service`` role, a service had to be +granted the ``admin`` role in order to have elevated privileges, which gave a +service powers way beyond what was necessary. With the ``service`` role in +place, we can now allow all service-to-service APIs to default to the +``service`` role only. For example, a policy that requires +``service`` can be expressed as: + +.. code-block:: yaml + + "identity:create_foo": "role:service" + +There might be exception service-to-service APIs which project think are +useful to be used by admin or non-admin user then they can take the +exceptional decision to default them to user role and ``service`` role. For +example, a policy that requires ``service`` and ``admin`` can be expressed as: + +.. code-block:: yaml + + "identity:create_foo": "role:service" or "role:admin" + +.. note:: + Unlike the other default roles, the ``service`` role is *not* a member + of a role hierarchy. It is a standalone role. + .. note:: As of the Train release, keystone applies the following personas diff --git a/doc/source/contributor/services.rst b/doc/source/contributor/services.rst index c1c397e309..448ecd7901 100644 --- a/doc/source/contributor/services.rst +++ b/doc/source/contributor/services.rst @@ -337,6 +337,58 @@ simplified check string expression: "service:foobar:update": "role:member" "service:foobar:delete": "role:admin" +In addition to above roles, from 2023.2 (Bobcat) release +``keystone-manage bootstrap`` will provide `service` role as well. If a +``service`` role is already present in the deployment, then a new one +is not created. This way any local scripts relying on the role ID will not +be broken. + +.. note:: + If you already have a ``service`` role in your deployment, you should + review its usage to make sure it is used only for service-to-service + communication. + +Once ``service`` role is created, OpenStack service +developers can start integrating it into their default policies as expressed: + +.. code-block:: python + + policy.DocumentedRuleDefault( + name='os_compute_api:os-server-external-events:create', + check_str='role:service', + scope_types=['project'] + ) + +It is important to note that we need to keep all the service-to-service APIs +default to ``service`` role only. For example, a policy that requires +``service`` can be expressed as: + +.. code-block:: yaml + + "service:foobar:create": "role:service" + +There might be exception service-to-service APIs which project think are +useful to be used by admin or non-admin user then they can take the +exceptional decision to default them to user role and ``service`` role. For +example, a policy that requires ``service`` and ``admin`` can be expressed as: + +.. code-block:: yaml + + "service:foobar:create": "role:service" OR "role:admin" + +Additionally, any deployment tools that create service accounts for OpenStack +services, should start preparing for these policy changes by updating their +role assignments and performing the deployment language equivalent of the +following: + +.. code-block:: console + + $ openstack role add --user nova --project service service + $ openstack role add --user cinder --project service service + $ openstack role add --user neutron --project service service + $ openstack role add --user glance --project service service + $ openstack role add --user manila --project service service + How do I incorporate authorization scopes into a service? ========================================================= diff --git a/keystone/cmd/bootstrap.py b/keystone/cmd/bootstrap.py index bf9d960cff..3c2f5e01ef 100644 --- a/keystone/cmd/bootstrap.py +++ b/keystone/cmd/bootstrap.py @@ -45,6 +45,9 @@ class Bootstrapper(object): self.admin_role_id = None self.admin_role_name = None + self.service_role_id = None + self.service_role_name = 'service' + self.region_id = None self.service_name = None @@ -66,6 +69,7 @@ class Bootstrapper(object): self._bootstrap_reader_role() self._bootstrap_member_role() self._bootstrap_admin_role() + self._bootstrap_service_role() self._bootstrap_project_role_assignment() self._bootstrap_system_role_assignment() self._bootstrap_region() @@ -160,6 +164,10 @@ class Bootstrapper(object): implied_role_id ) + def _bootstrap_service_role(self): + role = self._ensure_role_exists(self.service_role_name) + self.service_role_id = role['id'] + def _bootstrap_reader_role(self): role = self._ensure_role_exists(self.reader_role_name) self.reader_role_id = role['id'] diff --git a/keystone/cmd/cli.py b/keystone/cmd/cli.py index ad65b26229..c5b7a7b4e3 100644 --- a/keystone/cmd/cli.py +++ b/keystone/cmd/cli.py @@ -183,6 +183,7 @@ class BootStrap(BaseApp): self.bootstrapper.immutable_roles = True self.bootstrapper.bootstrap() + self.service_role_id = self.bootstrapper.service_role_id self.reader_role_id = self.bootstrapper.reader_role_id self.member_role_id = self.bootstrapper.member_role_id self.role_id = self.bootstrapper.admin_role_id diff --git a/keystone/tests/protection/v3/test_roles.py b/keystone/tests/protection/v3/test_roles.py index 09667553d8..becfeee123 100644 --- a/keystone/tests/protection/v3/test_roles.py +++ b/keystone/tests/protection/v3/test_roles.py @@ -34,9 +34,9 @@ class _SystemUserRoleTests(object): with self.test_client() as c: r = c.get('/v3/roles', headers=self.headers) # With bootstrap setup and the role we just created, there should - # be four roles present in the deployment. Bootstrap creates - # ``admin``, ``member``, and ``reader``. - self.assertEqual(4, len(r.json['roles'])) + # be five roles present in the deployment. Bootstrap creates + # ``service``, ``admin``, ``member``, and ``reader``. + self.assertEqual(5, len(r.json['roles'])) def test_user_can_get_a_role(self): role = PROVIDERS.role_api.create_role( diff --git a/keystone/tests/unit/test_cli.py b/keystone/tests/unit/test_cli.py index 2f9bed0646..d8899ff1db 100644 --- a/keystone/tests/unit/test_cli.py +++ b/keystone/tests/unit/test_cli.py @@ -134,14 +134,24 @@ class CliBootStrapTestCase(unit.SQLDriverOverrides, unit.TestCase): admin_role = PROVIDERS.role_api.get_role(bootstrap.role_id) reader_role = PROVIDERS.role_api.get_role(bootstrap.reader_role_id) member_role = PROVIDERS.role_api.get_role(bootstrap.member_role_id) + service_role = PROVIDERS.role_api.get_role(bootstrap.service_role_id) role_list = ( PROVIDERS.assignment_api.get_roles_for_user_and_project( user['id'], project['id'])) - self.assertIs(3, len(role_list)) + + role_list_len = 4 + if bootstrap.bootstrapper.project_name: + role_list_len = 3 + + self.assertIs(role_list_len, len(role_list)) self.assertIn(admin_role['id'], role_list) self.assertIn(reader_role['id'], role_list) self.assertIn(member_role['id'], role_list) + + if not bootstrap.bootstrapper.project_name: + self.assertIn(service_role['id'], role_list) + system_roles = ( PROVIDERS.assignment_api.list_system_grants_for_user( user['id'] @@ -352,7 +362,7 @@ class CliBootStrapTestCase(unit.SQLDriverOverrides, unit.TestCase): domain = PROVIDERS.resource_api.create_domain(domain['id'], domain) domain_roles = {} - for name in ['admin', 'member', 'reader']: + for name in ['admin', 'member', 'reader', 'service']: domain_role = { 'domain_id': domain['id'], 'id': uuid.uuid4().hex, diff --git a/releasenotes/notes/bug-1951632-11272e49e2fa439d.yaml b/releasenotes/notes/bug-1951632-11272e49e2fa439d.yaml new file mode 100644 index 0000000000..1e8f65cc88 --- /dev/null +++ b/releasenotes/notes/bug-1951632-11272e49e2fa439d.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + [`bug 1951632 `_] + ``Support has been added for deploying `service` role during the bootstrap + process in addition to the `admin`, `member` and `reader` role.`` +upgrades: + - | + ``If the bootstrap process is re-run, and a `service` role already exists, + it does not recreate the `service` role. See + [`bug 1951632 `_] + for more details.``