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:
parent
f5db9801c2
commit
d0eacc4729
@ -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
|
functionality to their team, auditors, customers, and users without maintaining
|
||||||
custom policies.
|
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
|
.. _`token guide`: https://docs.openstack.org/keystone/latest/admin/tokens-overview.html#authorization-scopes
|
||||||
|
|
||||||
-----------------
|
-----------------
|
||||||
Roles Definitions
|
Roles Definitions
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
The default roles provided by keystone, via ``keystone-manage bootstrap``, are
|
The default roles provided by keystone via ``keystone-manage bootstrap``
|
||||||
related through role implications. The ``admin`` role implies the ``member``
|
(except for the ``service`` role) are related through role implications. The
|
||||||
role, and the ``member`` role implies the ``reader`` role. These implications
|
``admin`` role implies the ``member`` role, and the ``member`` role implies
|
||||||
mean users with the ``admin`` role automatically have the ``member`` and
|
the ``reader`` role. These implications mean users with the ``admin`` role
|
||||||
``reader`` roles. Additionally, users with the ``member`` role automatically
|
automatically have the ``member`` and ``reader`` roles. Additionally,
|
||||||
have the ``reader`` role. Implying roles reduces role assignments and forms a
|
users with the ``member`` role automatically have the ``reader`` role.
|
||||||
natural hierarchy between the default roles. It also reduces the complexity of
|
Implying roles reduces role assignments and forms a natural hierarchy between
|
||||||
default policies by making check strings short. For example, a policy that
|
the default roles. It also reduces the complexity of default policies by
|
||||||
requires ``reader`` can be expressed as:
|
making check strings short. For example, a policy that requires ``reader``
|
||||||
|
can be expressed as:
|
||||||
|
|
||||||
.. code-block:: yaml
|
.. 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
|
the tenancy of their role assignment (this doesn't apply consistently since
|
||||||
services are addressing this individually at their own pace).
|
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::
|
.. note::
|
||||||
|
|
||||||
As of the Train release, keystone applies the following personas
|
As of the Train release, keystone applies the following personas
|
||||||
|
@ -337,6 +337,58 @@ simplified check string expression:
|
|||||||
"service:foobar:update": "role:member"
|
"service:foobar:update": "role:member"
|
||||||
"service:foobar:delete": "role:admin"
|
"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?
|
How do I incorporate authorization scopes into a service?
|
||||||
=========================================================
|
=========================================================
|
||||||
|
|
||||||
|
@ -45,6 +45,9 @@ class Bootstrapper(object):
|
|||||||
self.admin_role_id = None
|
self.admin_role_id = None
|
||||||
self.admin_role_name = None
|
self.admin_role_name = None
|
||||||
|
|
||||||
|
self.service_role_id = None
|
||||||
|
self.service_role_name = 'service'
|
||||||
|
|
||||||
self.region_id = None
|
self.region_id = None
|
||||||
|
|
||||||
self.service_name = None
|
self.service_name = None
|
||||||
@ -66,6 +69,7 @@ class Bootstrapper(object):
|
|||||||
self._bootstrap_reader_role()
|
self._bootstrap_reader_role()
|
||||||
self._bootstrap_member_role()
|
self._bootstrap_member_role()
|
||||||
self._bootstrap_admin_role()
|
self._bootstrap_admin_role()
|
||||||
|
self._bootstrap_service_role()
|
||||||
self._bootstrap_project_role_assignment()
|
self._bootstrap_project_role_assignment()
|
||||||
self._bootstrap_system_role_assignment()
|
self._bootstrap_system_role_assignment()
|
||||||
self._bootstrap_region()
|
self._bootstrap_region()
|
||||||
@ -160,6 +164,10 @@ class Bootstrapper(object):
|
|||||||
implied_role_id
|
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):
|
def _bootstrap_reader_role(self):
|
||||||
role = self._ensure_role_exists(self.reader_role_name)
|
role = self._ensure_role_exists(self.reader_role_name)
|
||||||
self.reader_role_id = role['id']
|
self.reader_role_id = role['id']
|
||||||
|
@ -183,6 +183,7 @@ class BootStrap(BaseApp):
|
|||||||
self.bootstrapper.immutable_roles = True
|
self.bootstrapper.immutable_roles = True
|
||||||
|
|
||||||
self.bootstrapper.bootstrap()
|
self.bootstrapper.bootstrap()
|
||||||
|
self.service_role_id = self.bootstrapper.service_role_id
|
||||||
self.reader_role_id = self.bootstrapper.reader_role_id
|
self.reader_role_id = self.bootstrapper.reader_role_id
|
||||||
self.member_role_id = self.bootstrapper.member_role_id
|
self.member_role_id = self.bootstrapper.member_role_id
|
||||||
self.role_id = self.bootstrapper.admin_role_id
|
self.role_id = self.bootstrapper.admin_role_id
|
||||||
|
@ -34,9 +34,9 @@ class _SystemUserRoleTests(object):
|
|||||||
with self.test_client() as c:
|
with self.test_client() as c:
|
||||||
r = c.get('/v3/roles', headers=self.headers)
|
r = c.get('/v3/roles', headers=self.headers)
|
||||||
# With bootstrap setup and the role we just created, there should
|
# With bootstrap setup and the role we just created, there should
|
||||||
# be four roles present in the deployment. Bootstrap creates
|
# be five roles present in the deployment. Bootstrap creates
|
||||||
# ``admin``, ``member``, and ``reader``.
|
# ``service``, ``admin``, ``member``, and ``reader``.
|
||||||
self.assertEqual(4, len(r.json['roles']))
|
self.assertEqual(5, len(r.json['roles']))
|
||||||
|
|
||||||
def test_user_can_get_a_role(self):
|
def test_user_can_get_a_role(self):
|
||||||
role = PROVIDERS.role_api.create_role(
|
role = PROVIDERS.role_api.create_role(
|
||||||
|
@ -134,14 +134,24 @@ class CliBootStrapTestCase(unit.SQLDriverOverrides, unit.TestCase):
|
|||||||
admin_role = PROVIDERS.role_api.get_role(bootstrap.role_id)
|
admin_role = PROVIDERS.role_api.get_role(bootstrap.role_id)
|
||||||
reader_role = PROVIDERS.role_api.get_role(bootstrap.reader_role_id)
|
reader_role = PROVIDERS.role_api.get_role(bootstrap.reader_role_id)
|
||||||
member_role = PROVIDERS.role_api.get_role(bootstrap.member_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 = (
|
role_list = (
|
||||||
PROVIDERS.assignment_api.get_roles_for_user_and_project(
|
PROVIDERS.assignment_api.get_roles_for_user_and_project(
|
||||||
user['id'],
|
user['id'],
|
||||||
project['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(admin_role['id'], role_list)
|
||||||
self.assertIn(reader_role['id'], role_list)
|
self.assertIn(reader_role['id'], role_list)
|
||||||
self.assertIn(member_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 = (
|
system_roles = (
|
||||||
PROVIDERS.assignment_api.list_system_grants_for_user(
|
PROVIDERS.assignment_api.list_system_grants_for_user(
|
||||||
user['id']
|
user['id']
|
||||||
@ -352,7 +362,7 @@ class CliBootStrapTestCase(unit.SQLDriverOverrides, unit.TestCase):
|
|||||||
domain = PROVIDERS.resource_api.create_domain(domain['id'], domain)
|
domain = PROVIDERS.resource_api.create_domain(domain['id'], domain)
|
||||||
domain_roles = {}
|
domain_roles = {}
|
||||||
|
|
||||||
for name in ['admin', 'member', 'reader']:
|
for name in ['admin', 'member', 'reader', 'service']:
|
||||||
domain_role = {
|
domain_role = {
|
||||||
'domain_id': domain['id'],
|
'domain_id': domain['id'],
|
||||||
'id': uuid.uuid4().hex,
|
'id': uuid.uuid4().hex,
|
||||||
|
12
releasenotes/notes/bug-1951632-11272e49e2fa439d.yaml
Normal file
12
releasenotes/notes/bug-1951632-11272e49e2fa439d.yaml
Normal 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.``
|
Loading…
Reference in New Issue
Block a user