Add default service role support to boostrap command

Added service role support to bootstrap command.

Closes-Bug: #1951632
Change-Id: I9cb25a111c84ecb3a09158cbe44b0954df89096c
This commit is contained in:
Abhishek Kekane 2022-11-02 15:54:51 +00:00
parent f5db9801c2
commit d0eacc4729
7 changed files with 134 additions and 14 deletions

View File

@ -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

View File

@ -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?
=========================================================

View File

@ -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']

View File

@ -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

View File

@ -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(

View File

@ -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,

View File

@ -0,0 +1,12 @@
---
features:
- |
[`bug 1951632 <https://bugs.launchpad.net/keystone/+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 <https://bugs.launchpad.net/keystone/+bug/1951632>`_]
for more details.``