blob: 8dae024ff22d9aaf54e8ae5a68166dd6a9752d6d [file] [log] [blame]
Michael Johnson6006de72021-02-21 01:42:39 +00001# Copyright 2021 Red Hat, Inc. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import copy
16
17from oslo_log import log as logging
18from tempest import config
19from tempest.lib import exceptions
20from tempest import test
21
22from octavia_tempest_plugin.common import constants
23from octavia_tempest_plugin.tests import waiters
24
25CONF = config.CONF
26LOG = logging.getLogger(__name__)
27
28
29class RBACTestsMixin(test.BaseTestCase):
30
Michael Johnson29d8e612021-06-23 16:16:12 +000031 def _get_client_method(self, cred_obj, client_str, method_str):
32 """Get requested method from registered clients in Tempest."""
33 lb_clients = getattr(cred_obj, 'load_balancer_v2')
34 client = getattr(lb_clients, client_str)
35 client_obj = client()
36 method = getattr(client_obj, method_str)
37 return method
38
Michael Johnson6006de72021-02-21 01:42:39 +000039 def _check_allowed(self, client_str, method_str, allowed_list,
40 *args, **kwargs):
41 """Test an API call allowed RBAC enforcement.
42
43 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +000044 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +000045 :param method_str: The method on the client to call for the test.
46 Example: 'list_amphorae'
47 :param allowed_list: The list of credentials expected to be
48 allowed. Example: ['os_roles_lb_member'].
49 :param args: Any positional parameters needed by the method.
50 :param kwargs: Any named parameters needed by the method.
51 :raises AssertionError: Raised if the RBAC tests fail.
52 :raises Forbidden: Raised if a credential that should have access does
53 not and is denied.
54 :raises InvalidScope: Raised if a credential that should have the
55 correct scope for access is denied.
56 :returns: None on success
57 """
58 for cred in allowed_list:
59 try:
60 cred_obj = getattr(self, cred)
61 except AttributeError:
62 # TODO(johnsom) Remove once scoped tokens is the default.
63 if ((cred == 'os_system_admin' or cred == 'os_system_reader')
64 and not CONF.enforce_scope.octavia):
65 LOG.info('Skipping %s allowed RBAC test because '
66 'enforce_scope.octavia is not True', cred)
67 continue
68 else:
69 self.fail('Credential {} "expected_allowed" for RBAC '
70 'testing was not created by tempest '
71 'credentials setup. This is likely a bug in the '
72 'test.'.format(cred))
Michael Johnson29d8e612021-06-23 16:16:12 +000073 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +000074 try:
75 method(*args, **kwargs)
76 except exceptions.Forbidden as e:
77 self.fail('Method {}.{} failed to allow access via RBAC using '
78 'credential {}. Error: {}'.format(
79 client_str, method_str, cred, str(e)))
80
81 def _check_disallowed(self, client_str, method_str, allowed_list,
82 status_method=None, obj_id=None, *args, **kwargs):
83 """Test an API call disallowed RBAC enforcement.
84
85 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +000086 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +000087 :param method_str: The method on the client to call for the test.
88 Example: 'list_amphorae'
89 :param allowed_list: The list of credentials expected to be
90 allowed. Example: ['os_roles_lb_member'].
91 :param status_method: The service client method that will provide
92 the object status for a status change waiter.
93 :param obj_id: The ID of the object to check for the expected status
94 update.
95 :param args: Any positional parameters needed by the method.
96 :param kwargs: Any named parameters needed by the method.
97 :raises AssertionError: Raised if the RBAC tests fail.
98 :raises Forbidden: Raised if a credential that should have access does
99 not and is denied.
100 :raises InvalidScope: Raised if a credential that should have the
101 correct scope for access is denied.
102 :returns: None on success
103 """
104 expected_disallowed = (set(self.allocated_credentials) -
105 set(allowed_list))
106 for cred in expected_disallowed:
107 cred_obj = getattr(self, cred)
Michael Johnson29d8e612021-06-23 16:16:12 +0000108 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +0000109
110 # Unfortunately tempest uses testtools assertRaises[1] which means
111 # we cannot use the unittest assertRaises context[2] with msg= to
112 # give a useful error.
113 # Also, testtools doesn't work with subTest[3], so we can't use
114 # that to expose the failing credential.
115 # This all means the exception raised testtools assertRaises
116 # is less than useful.
117 # TODO(johnsom) Remove this try block once testtools is useful.
118 # [1] https://testtools.readthedocs.io/en/latest/
119 # api.html#testtools.TestCase.assertRaises
120 # [2] https://docs.python.org/3/library/
121 # unittest.html#unittest.TestCase.assertRaises
122 # [3] https://github.com/testing-cabal/testtools/issues/235
123 try:
124 method(*args, **kwargs)
125 except exceptions.Forbidden:
126 if status_method:
127 waiters.wait_for_status(
128 status_method, obj_id,
129 constants.PROVISIONING_STATUS, constants.ACTIVE,
130 CONF.load_balancer.check_interval,
131 CONF.load_balancer.check_timeout)
132
133 continue
134 self.fail('Method {}.{} failed to deny access via RBAC using '
135 'credential {}.'.format(client_str, method_str, cred))
136
137 def _list_get_RBAC_enforcement(self, client_str, method_str,
138 expected_allowed, *args, **kwargs):
139 """Test an API call RBAC enforcement.
140
141 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000142 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000143 :param method_str: The method on the client to call for the test.
144 Example: 'list_amphorae'
145 :param expected_allowed: The list of credentials expected to be
146 allowed. Example: ['os_roles_lb_member'].
147 :param args: Any positional parameters needed by the method.
148 :param kwargs: Any named parameters needed by the method.
149 :raises AssertionError: Raised if the RBAC tests fail.
150 :raises Forbidden: Raised if a credential that should have access does
151 not and is denied.
152 :raises InvalidScope: Raised if a credential that should have the
153 correct scope for access is denied.
154 :returns: None on success
155 """
156
157 allowed_list = copy.deepcopy(expected_allowed)
Gregory Thiemongefb04e6e2023-02-03 13:53:10 +0100158 # The legacy admin behavior changed during the sRBAC development,
159 # os_admin is still a valid admin [0]
160 # [0] https://governance.openstack.org/tc/goals/selected/
161 # consistent-and-secure-rbac.html
162 # #legacy-admin-continues-to-work-as-it-is
163 # TODO(gthiemonge) we may have to revisit it in the future if the
164 # legacy admin scope changes.
165 if 'os_system_admin' in expected_allowed:
Michael Johnson6006de72021-02-21 01:42:39 +0000166 allowed_list.append('os_admin')
167
168 # #### Test that disallowed credentials cannot access the API.
169 self._check_disallowed(client_str, method_str, allowed_list,
170 None, None, *args, **kwargs)
171
172 # #### Test that allowed credentials can access the API.
173 self._check_allowed(client_str, method_str, allowed_list,
174 *args, **kwargs)
175
176 def check_show_RBAC_enforcement(self, client_str, method_str,
177 expected_allowed, *args, **kwargs):
178 """Test an API show call RBAC enforcement.
179
180 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000181 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000182 :param method_str: The method on the client to call for the test.
183 Example: 'list_amphorae'
184 :param expected_allowed: The list of credentials expected to be
185 allowed. Example: ['os_roles_lb_member'].
186 :param args: Any positional parameters needed by the method.
187 :param kwargs: Any named parameters needed by the method.
188 :raises AssertionError: Raised if the RBAC tests fail.
189 :raises Forbidden: Raised if a credential that should have access does
190 not and is denied.
191 :raises InvalidScope: Raised if a credential that should have the
192 correct scope for access is denied.
193 :returns: None on success
194 """
195 self._list_get_RBAC_enforcement(client_str, method_str,
196 expected_allowed, *args, **kwargs)
197
198 def check_list_RBAC_enforcement(self, client_str, method_str,
199 expected_allowed, *args, **kwargs):
200 """Test an API list call RBAC enforcement.
201
202 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000203 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000204 :param method_str: The method on the client to call for the test.
205 Example: 'list_amphorae'
206 :param expected_allowed: The list of credentials expected to be
207 allowed. Example: ['os_roles_lb_member'].
208 :param args: Any positional parameters needed by the method.
209 :param kwargs: Any named parameters needed by the method.
210 :raises AssertionError: Raised if the RBAC tests fail.
211 :raises Forbidden: Raised if a credential that should have access does
212 not and is denied.
213 :raises InvalidScope: Raised if a credential that should have the
214 correct scope for access is denied.
215 :returns: None on success
216 """
217 self._list_get_RBAC_enforcement(client_str, method_str,
218 expected_allowed, *args, **kwargs)
219
220 def _CUD_RBAC_enforcement(self, client_str, method_str, expected_allowed,
221 status_method=None, obj_id=None,
222 *args, **kwargs):
223 """Test an API create/update/delete call RBAC enforcement.
224
225 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000226 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000227 :param method_str: The method on the client to call for the test.
228 Example: 'list_amphorae'
229 :param expected_allowed: The list of credentials expected to be
230 allowed. Example: ['os_roles_lb_member'].
231 :param status_method: The service client method that will provide
232 the object status for a status change waiter.
233 :param obj_id: The ID of the object to check for the expected status
234 update.
235 :param args: Any positional parameters needed by the method.
236 :param kwargs: Any named parameters needed by the method.
237 :raises AssertionError: Raised if the RBAC tests fail.
238 :raises Forbidden: Raised if a credential that should have access does
239 not and is denied.
240 :raises InvalidScope: Raised if a credential that should have the
241 correct scope for access is denied.
242 :returns: None on success
243 """
244
245 allowed_list = copy.deepcopy(expected_allowed)
Gregory Thiemongefb04e6e2023-02-03 13:53:10 +0100246 # The legacy admin behavior changed during the sRBAC development,
247 # os_admin is still a valid admin [0]
248 # [0] https://governance.openstack.org/tc/goals/selected/
249 # consistent-and-secure-rbac.html
250 # #legacy-admin-continues-to-work-as-it-is
251 # TODO(gthiemonge) we may have to revisit it in the future if the
252 # legacy admin scope changes.
253 if 'os_system_admin' in expected_allowed:
Michael Johnson6006de72021-02-21 01:42:39 +0000254 allowed_list.append('os_admin')
255
256 # #### Test that disallowed credentials cannot access the API.
257 self._check_disallowed(client_str, method_str, allowed_list,
258 status_method, obj_id, *args, **kwargs)
259
260 def check_create_RBAC_enforcement(
261 self, client_str, method_str, expected_allowed,
262 status_method=None, obj_id=None, *args, **kwargs):
263 """Test an API create call RBAC enforcement.
264
265 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000266 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000267 :param method_str: The method on the client to call for the test.
268 Example: 'list_amphorae'
269 :param expected_allowed: The list of credentials expected to be
270 allowed. Example: ['os_roles_lb_member'].
271 :param status_method: The service client method that will provide
272 the object status for a status change waiter.
273 :param obj_id: The ID of the object to check for the expected status
274 update.
275 :param args: Any positional parameters needed by the method.
276 :param kwargs: Any named parameters needed by the method.
277 :raises AssertionError: Raised if the RBAC tests fail.
278 :raises Forbidden: Raised if a credential that should have access does
279 not and is denied.
280 :raises InvalidScope: Raised if a credential that should have the
281 correct scope for access is denied.
282 :returns: None on success
283 """
284 self._CUD_RBAC_enforcement(client_str, method_str, expected_allowed,
285 status_method, obj_id, *args, **kwargs)
286
287 def check_delete_RBAC_enforcement(
288 self, client_str, method_str, expected_allowed,
289 status_method=None, obj_id=None, *args, **kwargs):
290 """Test an API delete call RBAC enforcement.
291
292 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000293 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000294 :param method_str: The method on the client to call for the test.
295 Example: 'list_amphorae'
296 :param expected_allowed: The list of credentials expected to be
297 allowed. Example: ['os_roles_lb_member'].
298 :param status_method: The service client method that will provide
299 the object status for a status change waiter.
300 :param obj_id: The ID of the object to check for the expected status
301 update.
302 :param args: Any positional parameters needed by the method.
303 :param kwargs: Any named parameters needed by the method.
304 :raises AssertionError: Raised if the RBAC tests fail.
305 :raises Forbidden: Raised if a credential that should have access does
306 not and is denied.
307 :raises InvalidScope: Raised if a credential that should have the
308 correct scope for access is denied.
309 :returns: None on success
310 """
311 self._CUD_RBAC_enforcement(client_str, method_str, expected_allowed,
312 status_method, obj_id, *args, **kwargs)
313
314 def check_update_RBAC_enforcement(
315 self, client_str, method_str, expected_allowed,
316 status_method=None, obj_id=None, *args, **kwargs):
317 """Test an API update call RBAC enforcement.
318
319 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000320 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000321 :param method_str: The method on the client to call for the test.
322 Example: 'list_amphorae'
323 :param expected_allowed: The list of credentials expected to be
324 allowed. Example: ['os_roles_lb_member'].
325 :param status_method: The service client method that will provide
326 the object status for a status change waiter.
327 :param obj_id: The ID of the object to check for the expected status
328 update.
329 :param args: Any positional parameters needed by the method.
330 :param kwargs: Any named parameters needed by the method.
331 :raises AssertionError: Raised if the RBAC tests fail.
332 :raises Forbidden: Raised if a credential that should have access does
333 not and is denied.
334 :raises InvalidScope: Raised if a credential that should have the
335 correct scope for access is denied.
336 :returns: None on success
337 """
338 self._CUD_RBAC_enforcement(client_str, method_str, expected_allowed,
339 status_method, obj_id, *args, **kwargs)
340
341 def check_list_RBAC_enforcement_count(
342 self, client_str, method_str, expected_allowed, expected_count,
343 *args, **kwargs):
344 """Test an API list call RBAC enforcement result count.
345
346 List APIs will return the object list for the project associated
347 with the token used to access the API. This means most credentials
348 will have access, but will get differing results.
349
350 This test will query the list API using a list of credentials and
351 will validate that only the expected count of results are returned.
352
353 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000354 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000355 :param method_str: The method on the client to call for the test.
356 Example: 'list_amphorae'
357 :param expected_allowed: The list of credentials expected to be
358 allowed. Example: ['os_roles_lb_member'].
359 :param expected_count: The number of results expected in the list
360 returned from the API.
361 :param args: Any positional parameters needed by the method.
362 :param kwargs: Any named parameters needed by the method.
363 :raises AssertionError: Raised if the RBAC tests fail.
364 :raises Forbidden: Raised if a credential that should have access does
365 not and is denied.
366 :raises InvalidScope: Raised if a credential that should have the
367 correct scope for access is denied.
368 :returns: None on success
369 """
370
371 allowed_list = copy.deepcopy(expected_allowed)
Gregory Thiemongefb04e6e2023-02-03 13:53:10 +0100372 # The legacy admin behavior changed during the sRBAC development,
373 # os_admin is still a valid admin [0]
374 # [0] https://governance.openstack.org/tc/goals/selected/
375 # consistent-and-secure-rbac.html
376 # #legacy-admin-continues-to-work-as-it-is
377 # TODO(gthiemonge) we may have to revisit it in the future if the
378 # legacy admin scope changes.
379 if 'os_system_admin' in expected_allowed:
Michael Johnson6006de72021-02-21 01:42:39 +0000380 allowed_list.append('os_admin')
381
382 for cred in allowed_list:
383 try:
384 cred_obj = getattr(self, cred)
385 except AttributeError:
386 # TODO(johnsom) Remove once scoped tokens is the default.
387 if ((cred == 'os_system_admin' or cred == 'os_system_reader')
388 and not CONF.enforce_scope.octavia):
389 LOG.info('Skipping %s allowed RBAC test because '
390 'enforce_scope.octavia is not True', cred)
391 continue
392 else:
393 self.fail('Credential {} "expected_allowed" for RBAC '
394 'testing was not created by tempest '
395 'credentials setup. This is likely a bug in the '
396 'test.'.format(cred))
Michael Johnson29d8e612021-06-23 16:16:12 +0000397 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +0000398 try:
399 result = method(*args, **kwargs)
400 except exceptions.Forbidden as e:
401 self.fail('Method {}.{} failed to allow access via RBAC using '
402 'credential {}. Error: {}'.format(
403 client_str, method_str, cred, str(e)))
404 self.assertEqual(expected_count, len(result), message='Credential '
405 '{} saw {} objects when {} was expected.'.format(
406 cred, len(result), expected_count))
407
408 def check_list_IDs_RBAC_enforcement(
409 self, client_str, method_str, expected_allowed, expected_ids,
410 *args, **kwargs):
411 """Test an API list call RBAC enforcement result contains IDs.
412
413 List APIs will return the object list for the project associated
414 with the token used to access the API. This means most credentials
415 will have access, but will get differing results.
416
417 This test will query the list API using a list of credentials and
418 will validate that the expected object Ids in included in the results.
419
420 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000421 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000422 :param method_str: The method on the client to call for the test.
423 Example: 'list_amphorae'
424 :param expected_allowed: The list of credentials expected to be
425 allowed. Example: ['os_roles_lb_member'].
426 :param expected_ids: The list of object IDs to validate are included
427 in the returned list from the API.
428 :param args: Any positional parameters needed by the method.
429 :param kwargs: Any named parameters needed by the method.
430 :raises AssertionError: Raised if the RBAC tests fail.
431 :raises Forbidden: Raised if a credential that should have access does
432 not and is denied.
433 :raises InvalidScope: Raised if a credential that should have the
434 correct scope for access is denied.
435 :returns: None on success
436 """
437
438 allowed_list = copy.deepcopy(expected_allowed)
Gregory Thiemongefb04e6e2023-02-03 13:53:10 +0100439 # The legacy admin behavior changed during the sRBAC development,
440 # os_admin is still a valid admin [0]
441 # [0] https://governance.openstack.org/tc/goals/selected/
442 # consistent-and-secure-rbac.html
443 # #legacy-admin-continues-to-work-as-it-is
444 # TODO(gthiemonge) we may have to revisit it in the future if the
445 # legacy admin scope changes.
446 if 'os_system_admin' in expected_allowed:
Michael Johnson6006de72021-02-21 01:42:39 +0000447 allowed_list.append('os_admin')
448
449 for cred in allowed_list:
450 try:
451 cred_obj = getattr(self, cred)
452 except AttributeError:
453 # TODO(johnsom) Remove once scoped tokens is the default.
454 if ((cred == 'os_system_admin' or cred == 'os_system_reader')
455 and not CONF.enforce_scope.octavia):
456 LOG.info('Skipping %s allowed RBAC test because '
457 'enforce_scope.octavia is not True', cred)
458 continue
459 else:
460 self.fail('Credential {} "expected_allowed" for RBAC '
461 'testing was not created by tempest '
462 'credentials setup. This is likely a bug in the '
463 'test.'.format(cred))
Michael Johnson29d8e612021-06-23 16:16:12 +0000464 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +0000465 try:
466 result = method(*args, **kwargs)
467 except exceptions.Forbidden as e:
468 self.fail('Method {}.{} failed to allow access via RBAC using '
469 'credential {}. Error: {}'.format(
470 client_str, method_str, cred, str(e)))
471 result_ids = [lb[constants.ID] for lb in result]
472 self.assertTrue(set(expected_ids).issubset(set(result_ids)))