diff --git a/keystone/cmd/bootstrap.py b/keystone/cmd/bootstrap.py index 66ac7626eb..2d1ec8577b 100644 --- a/keystone/cmd/bootstrap.py +++ b/keystone/cmd/bootstrap.py @@ -36,6 +36,12 @@ class Bootstrapper(object): self.project_id = None self.project_name = None + self.reader_role_id = None + self.reader_role_name = 'reader' + + self.member_role_id = None + self.member_role_name = 'member' + self.admin_role_id = None self.admin_role_name = None @@ -55,6 +61,8 @@ class Bootstrapper(object): self._bootstrap_default_domain() self._bootstrap_project() self._bootstrap_admin_user() + self._bootstrap_reader_role() + self._bootstrap_member_role() self._bootstrap_admin_role() self._bootstrap_project_role_assignment() self._bootstrap_system_role_assignment() @@ -101,6 +109,53 @@ class Bootstrapper(object): self.project_id = project['id'] + def _ensure_role_exists(self, role_name): + # NOTE(morganfainberg): Do not create the role if it already exists. + try: + role_id = uuid.uuid4().hex + role = {'name': role_name, 'id': role_id} + role = PROVIDERS.role_api.create_role(role_id, role) + LOG.info('Created role %s', role_name) + return role + except exception.Conflict: + LOG.info('Role %s exists, skipping creation.', role_name) + # NOTE(davechen): There is no backend method to get the role + # by name, so build the hints to list the roles and filter by + # name instead. + hints = driver_hints.Hints() + hints.add_filter('name', role_name) + return PROVIDERS.role_api.list_roles(hints)[0] + + def _ensure_implied_role(self, prior_role_id, implied_role_id): + try: + PROVIDERS.role_api.create_implied_role(prior_role_id, + implied_role_id) + LOG.info( + 'Created implied role where %s implies %s', + prior_role_id, + implied_role_id + ) + except exception.Conflict: + LOG.info( + 'Implied role where %s implies %s exists, skipping creation.', + prior_role_id, + implied_role_id + ) + + def _bootstrap_reader_role(self): + role = self._ensure_role_exists(self.reader_role_name) + self.reader_role_id = role['id'] + + def _bootstrap_member_role(self): + role = self._ensure_role_exists(self.member_role_name) + self.member_role_id = role['id'] + self._ensure_implied_role(self.member_role_id, self.reader_role_id) + + def _bootstrap_admin_role(self): + role = self._ensure_role_exists(self.admin_role_name) + self.admin_role_id = role['id'] + self._ensure_implied_role(self.admin_role_id, self.member_role_id) + def _bootstrap_admin_user(self): # NOTE(morganfainberg): Do not create the user if it already exists. try: @@ -156,26 +211,6 @@ class Bootstrapper(object): self.admin_user_id = user['id'] - def _bootstrap_admin_role(self): - # NOTE(morganfainberg): Do not create the role if it already exists. - try: - role_id = uuid.uuid4().hex - role = {'name': self.admin_role_name, 'id': role_id} - role = PROVIDERS.role_api.create_role(role_id, role) - LOG.info('Created role %s', self.admin_role_name) - except exception.Conflict: - LOG.info( - 'Role %s exists, skipping creation.', self.admin_role_name - ) - # NOTE(davechen): There is no backend method to get the role - # by name, so build the hints to list the roles and filter by - # name instead. - hints = driver_hints.Hints() - hints.add_filter('name', self.admin_role_name) - role = PROVIDERS.role_api.list_roles(hints)[0] - - self.admin_role_id = role['id'] - def _bootstrap_project_role_assignment(self): try: PROVIDERS.assignment_api.add_role_to_user_and_project( diff --git a/keystone/cmd/cli.py b/keystone/cmd/cli.py index a03d61dac4..531c8b1f10 100644 --- a/keystone/cmd/cli.py +++ b/keystone/cmd/cli.py @@ -167,6 +167,8 @@ class BootStrap(BaseApp): self.bootstrapper.region_id = self.region_id self.bootstrapper.bootstrap() + 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 self.project_id = self.bootstrapper.project_id diff --git a/keystone/tests/unit/test_cli.py b/keystone/tests/unit/test_cli.py index fa64e43971..bf67707f46 100644 --- a/keystone/tests/unit/test_cli.py +++ b/keystone/tests/unit/test_cli.py @@ -107,20 +107,24 @@ class CliBootStrapTestCase(unit.SQLDriverOverrides, unit.TestCase): user = PROVIDERS.identity_api.get_user_by_name( bootstrap.username, 'default') - 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) + member_role = PROVIDERS.role_api.get_role(bootstrap.member_role_id) role_list = ( PROVIDERS.assignment_api.get_roles_for_user_and_project( user['id'], project['id'])) - self.assertIs(1, len(role_list)) - self.assertEqual(role_list[0], role['id']) + self.assertIs(3, len(role_list)) + self.assertIn(admin_role['id'], role_list) + self.assertIn(reader_role['id'], role_list) + self.assertIn(member_role['id'], role_list) system_roles = ( PROVIDERS.assignment_api.list_system_grants_for_user( user['id'] ) ) self.assertIs(1, len(system_roles)) - self.assertEqual(system_roles[0]['id'], role['id']) + self.assertEqual(system_roles[0]['id'], admin_role['id']) # NOTE(morganfainberg): Pass an empty context, it isn't used by # `authenticate` method. PROVIDERS.identity_api.authenticate( diff --git a/releasenotes/notes/bp-basic-default-roles-4ff6502b6ac57d48.yaml b/releasenotes/notes/bp-basic-default-roles-4ff6502b6ac57d48.yaml new file mode 100644 index 0000000000..d47a822621 --- /dev/null +++ b/releasenotes/notes/bp-basic-default-roles-4ff6502b6ac57d48.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + [`blueprint basic-default-roles `_] + Support has been added for deploying two new roles during the bootstrap + process, `reader` and `member`, in addition to the `admin` role. +upgrades: + - | + If the bootstrap process is re-run, and a `reader`, `member`, or `admin` + role already exists, a role implication chain will be created: `admin` + implies `member` implies `reader`. If you do not want these role + implications either skip running bootstrap or delete them after it has + completed execution. See + [`blueprint basic-default-roles `_] + for more details.