blob: 2ef88ca8fe0201d5478ff76fd3dcce0d6dc67899 [file] [log] [blame]
DavidPurcellb25f93d2017-01-27 12:46:27 -05001# Copyright 2017 AT&T Corporation.
DavidPurcell029d8c32017-01-06 15:27:41 -05002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000016from contextlib import contextmanager
DavidPurcell029d8c32017-01-06 15:27:41 -050017import time
Felipe Monteiro34a138c2017-03-02 17:01:37 -050018
Rajiv Kumar645dfc92017-01-19 13:48:27 +053019from oslo_log import log as logging
Felipe Monteiro2693bf72017-08-12 22:56:47 +010020from oslo_utils import excutils
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010021
Felipe Monteiro3e14f472017-08-17 23:02:11 +010022from tempest import clients
Felipe Monteiroe7e552e2017-05-02 17:04:12 +010023from tempest.common import credentials_factory as credentials
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010024from tempest import config
DavidPurcell029d8c32017-01-06 15:27:41 -050025
Felipe Monteiro34a138c2017-03-02 17:01:37 -050026from patrole_tempest_plugin import rbac_exceptions
DavidPurcell029d8c32017-01-06 15:27:41 -050027
DavidPurcell029d8c32017-01-06 15:27:41 -050028CONF = config.CONF
Felipe Monteiro34a138c2017-03-02 17:01:37 -050029LOG = logging.getLogger(__name__)
DavidPurcell029d8c32017-01-06 15:27:41 -050030
31
DavidPurcell029d8c32017-01-06 15:27:41 -050032class RbacUtils(object):
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000033 """Utility class responsible for switching ``os_primary`` role.
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010034
35 This class is responsible for overriding the value of the primary Tempest
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000036 credential's role (i.e. ``os_primary`` role). By doing so, it is possible
37 to seamlessly swap between admin credentials, needed for setup and clean
38 up, and primary credentials, needed to perform the API call which does
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010039 policy enforcement. The primary credentials always cycle between roles
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000040 defined by ``CONF.identity.admin_role`` and
41 ``CONF.patrole.rbac_test_role``.
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010042 """
DavidPurcell029d8c32017-01-06 15:27:41 -050043
Felipe Monteirob35de582017-05-05 00:16:53 +010044 def __init__(self, test_obj):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010045 """Constructor for ``RbacUtils``.
46
Felipe Monteiro2693bf72017-08-12 22:56:47 +010047 :param test_obj: An instance of `tempest.test.BaseTestCase`.
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010048 """
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010049 # Intialize the admin roles_client to perform role switching.
Felipe Monteiro3e14f472017-08-17 23:02:11 +010050 admin_mgr = clients.Manager(
51 credentials.get_configured_admin_credentials())
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010052 if test_obj.get_identity_version() == 'v3':
Felipe Monteiro3e14f472017-08-17 23:02:11 +010053 admin_roles_client = admin_mgr.roles_v3_client
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010054 else:
Felipe Monteiro3e14f472017-08-17 23:02:11 +010055 admin_roles_client = admin_mgr.roles_client
Felipe Monteiroe8d93e02017-07-19 20:52:20 +010056
57 self.admin_roles_client = admin_roles_client
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000058 self._override_role(test_obj, False)
Felipe Monteirob35de582017-05-05 00:16:53 +010059
Felipe Monteirofa01d5f2017-04-01 06:18:25 +010060 admin_role_id = None
61 rbac_role_id = None
DavidPurcell029d8c32017-01-06 15:27:41 -050062
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000063 @contextmanager
64 def override_role(self, test_obj):
65 """Override the role used by ``os_primary`` Tempest credentials.
66
67 Temporarily change the role used by ``os_primary`` credentials to:
Masayuki Igawa80b9aab2018-01-09 17:00:45 +090068
69 * ``[patrole] rbac_test_role`` before test execution
70 * ``[identity] admin_role`` after test execution
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000071
72 Automatically switches to admin role after test execution.
73
74 :param test_obj: Instance of ``tempest.test.BaseTestCase``.
75 :returns: None
76
77 .. warning::
78
79 This function can alter user roles for pre-provisioned credentials.
80 Work is underway to safely clean up after this function.
81
82 Example::
83
84 @rbac_rule_validation.action(service='test',
85 rule='a:test:rule')
86 def test_foo(self):
87 # Allocate test-level resources here.
88 with self.rbac_utils.override_role(self):
melissaml7cd21612018-05-23 21:00:50 +080089 # The role for `os_primary` has now been overridden. Within
Felipe Monteiro10e82fd2017-11-21 01:47:20 +000090 # this block, call the API endpoint that enforces the
91 # expected policy specified by "rule" in the decorator.
92 self.foo_service.bar_api_call()
93 # The role is switched back to admin automatically. Note that
94 # if the API call above threw an exception, any code below this
95 # point in the test is not executed.
96 """
97 self._override_role(test_obj, True)
98 try:
99 # Execute the test.
100 yield
101 finally:
102 # This code block is always executed, no matter the result of the
103 # test. Automatically switch back to the admin role for test clean
104 # up.
105 self._override_role(test_obj, False)
106
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000107 def _override_role(self, test_obj, toggle_rbac_role=False):
108 """Private helper for overriding ``os_primary`` Tempest credentials.
109
Felipe Monteiro07a1c172017-12-10 04:26:08 +0000110 :param test_obj: instance of :py:class:`tempest.test.BaseTestCase`
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000111 :param toggle_rbac_role: Boolean value that controls the role that
112 overrides default role of ``os_primary`` credentials.
113 * If True: role is set to ``[patrole] rbac_test_role``
114 * If False: role is set to ``[identity] admin_role``
115 """
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100116 self.user_id = test_obj.os_primary.credentials.user_id
117 self.project_id = test_obj.os_primary.credentials.tenant_id
118 self.token = test_obj.os_primary.auth_provider.get_token()
DavidPurcell029d8c32017-01-06 15:27:41 -0500119
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000120 LOG.debug('Overriding role to: %s.', toggle_rbac_role)
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100121 role_already_present = False
122
DavidPurcell029d8c32017-01-06 15:27:41 -0500123 try:
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100124 if not all([self.admin_role_id, self.rbac_role_id]):
125 self._get_roles_by_name()
DavidPurcell029d8c32017-01-06 15:27:41 -0500126
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100127 target_role = (
128 self.rbac_role_id if toggle_rbac_role else self.admin_role_id)
129 role_already_present = self._list_and_clear_user_roles_on_project(
130 target_role)
131
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000132 # Do not override roles if `target_role` already exists.
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100133 if not role_already_present:
134 self._create_user_role_on_project(target_role)
DavidPurcell029d8c32017-01-06 15:27:41 -0500135 except Exception as exp:
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100136 with excutils.save_and_reraise_exception():
137 LOG.exception(exp)
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500138 finally:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100139 test_obj.os_primary.auth_provider.clear_auth()
Felipe Monteiro7be94e82017-07-26 02:17:08 +0100140 # Fernet tokens are not subsecond aware so sleep to ensure we are
141 # passing the second boundary before attempting to authenticate.
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100142 # Only sleep if a token revocation occurred as a result of role
Felipe Monteiro10e82fd2017-11-21 01:47:20 +0000143 # overriding. This will optimize test runtime in the case where
Felipe Monteirob58c1192017-11-20 01:50:24 +0000144 # ``[identity] admin_role`` == ``[patrole] rbac_test_role``.
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100145 if not role_already_present:
Rick Bartra89f498f2017-03-20 15:54:45 -0400146 time.sleep(1)
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100147 test_obj.os_primary.auth_provider.set_auth()
Felipe Monteiro34a138c2017-03-02 17:01:37 -0500148
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100149 def _get_roles_by_name(self):
150 available_roles = self.admin_roles_client.list_roles()
151 admin_role_id = rbac_role_id = None
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100152
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100153 for role in available_roles['roles']:
Felipe Monteirof6eb8622017-08-06 06:08:02 +0100154 if role['name'] == CONF.patrole.rbac_test_role:
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100155 rbac_role_id = role['id']
156 if role['name'] == CONF.identity.admin_role:
157 admin_role_id = role['id']
158
159 if not all([admin_role_id, rbac_role_id]):
Felipe Monteirof6eb8622017-08-06 06:08:02 +0100160 msg = ("Roles defined by `[patrole] rbac_test_role` and "
161 "`[identity] admin_role` must be defined in the system.")
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100162 raise rbac_exceptions.RbacResourceSetupFailed(msg)
163
164 self.admin_role_id = admin_role_id
165 self.rbac_role_id = rbac_role_id
166
167 def _create_user_role_on_project(self, role_id):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100168 self.admin_roles_client.create_user_role_on_project(
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100169 self.project_id, self.user_id, role_id)
170
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100171 def _list_and_clear_user_roles_on_project(self, role_id):
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100172 roles = self.admin_roles_client.list_user_roles_on_project(
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100173 self.project_id, self.user_id)['roles']
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100174 role_ids = [role['id'] for role in roles]
Felipe Monteiro2693bf72017-08-12 22:56:47 +0100175
176 # NOTE(felipemonteiro): We do not use ``role_id in role_ids`` here to
177 # avoid over-permission errors: if the current list of roles on the
178 # project includes "admin" and "Member", and we are switching to the
179 # "Member" role, then we must delete the "admin" role. Thus, we only
180 # return early if the user's roles on the project are an exact match.
181 if [role_id] == role_ids:
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100182 return True
Felipe Monteirob3b7bc82017-03-03 15:58:15 -0500183
184 for role in roles:
Felipe Monteiroe8d93e02017-07-19 20:52:20 +0100185 self.admin_roles_client.delete_role_from_user_on_project(
Felipe Monteirofa01d5f2017-04-01 06:18:25 +0100186 self.project_id, self.user_id, role['id'])
187
188 return False
189
Felipe Monteiro17e9b492017-05-27 05:45:20 +0100190
Felipe Monteiro07a1c172017-12-10 04:26:08 +0000191class RbacUtilsMixin(object):
192 """Mixin class to be used alongside an instance of
193 :py:class:`tempest.test.BaseTestCase`.
194
195 Should be used to perform Patrole class setup for a base RBAC class. Child
196 classes should not use this mixin.
197
198 Example::
199
200 class BaseRbacTest(rbac_utils.RbacUtilsMixin, base.BaseV2ComputeTest):
201
202 @classmethod
203 def skip_checks(cls):
204 super(BaseRbacTest, cls).skip_checks()
205 cls.skip_rbac_checks()
206
207 @classmethod
208 def setup_clients(cls):
209 super(BaseRbacTest, cls).setup_clients()
210 cls.setup_rbac_utils()
211 """
212
213 @classmethod
214 def skip_rbac_checks(cls):
215 if not CONF.patrole.enable_rbac:
216 raise cls.skipException(
Felipe Monteirod7371992018-04-25 16:57:09 +0100217 'Patrole testing not enabled so skipping %s.' % cls.__name__)
Felipe Monteiro07a1c172017-12-10 04:26:08 +0000218
219 @classmethod
220 def setup_rbac_utils(cls):
221 cls.rbac_utils = RbacUtils(cls)
222
223
Felipe Monteiro8a043fb2017-08-06 06:29:05 +0100224def is_admin():
225 """Verifies whether the current test role equals the admin role.
226
227 :returns: True if ``rbac_test_role`` is the admin role.
228 """
Felipe Monteirof6eb8622017-08-06 06:08:02 +0100229 return CONF.patrole.rbac_test_role == CONF.identity.admin_role