blob: 8fc356c8fb7750b85c027d24a44730853a357673 [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:
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -070062 self.fail('Credential {} "expected_allowed" for RBAC '
63 'testing was not created by tempest '
64 'credentials setup. This is likely a bug in the '
65 'test.'.format(cred))
Michael Johnson29d8e612021-06-23 16:16:12 +000066 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +000067 try:
68 method(*args, **kwargs)
69 except exceptions.Forbidden as e:
70 self.fail('Method {}.{} failed to allow access via RBAC using '
71 'credential {}. Error: {}'.format(
72 client_str, method_str, cred, str(e)))
73
74 def _check_disallowed(self, client_str, method_str, allowed_list,
75 status_method=None, obj_id=None, *args, **kwargs):
76 """Test an API call disallowed RBAC enforcement.
77
78 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +000079 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +000080 :param method_str: The method on the client to call for the test.
81 Example: 'list_amphorae'
82 :param allowed_list: The list of credentials expected to be
83 allowed. Example: ['os_roles_lb_member'].
84 :param status_method: The service client method that will provide
85 the object status for a status change waiter.
86 :param obj_id: The ID of the object to check for the expected status
87 update.
88 :param args: Any positional parameters needed by the method.
89 :param kwargs: Any named parameters needed by the method.
90 :raises AssertionError: Raised if the RBAC tests fail.
91 :raises Forbidden: Raised if a credential that should have access does
92 not and is denied.
93 :raises InvalidScope: Raised if a credential that should have the
94 correct scope for access is denied.
95 :returns: None on success
96 """
97 expected_disallowed = (set(self.allocated_credentials) -
98 set(allowed_list))
99 for cred in expected_disallowed:
100 cred_obj = getattr(self, cred)
Michael Johnson29d8e612021-06-23 16:16:12 +0000101 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +0000102
103 # Unfortunately tempest uses testtools assertRaises[1] which means
104 # we cannot use the unittest assertRaises context[2] with msg= to
105 # give a useful error.
106 # Also, testtools doesn't work with subTest[3], so we can't use
107 # that to expose the failing credential.
108 # This all means the exception raised testtools assertRaises
109 # is less than useful.
110 # TODO(johnsom) Remove this try block once testtools is useful.
111 # [1] https://testtools.readthedocs.io/en/latest/
112 # api.html#testtools.TestCase.assertRaises
113 # [2] https://docs.python.org/3/library/
114 # unittest.html#unittest.TestCase.assertRaises
115 # [3] https://github.com/testing-cabal/testtools/issues/235
116 try:
117 method(*args, **kwargs)
118 except exceptions.Forbidden:
119 if status_method:
120 waiters.wait_for_status(
121 status_method, obj_id,
122 constants.PROVISIONING_STATUS, constants.ACTIVE,
123 CONF.load_balancer.check_interval,
124 CONF.load_balancer.check_timeout)
125
126 continue
127 self.fail('Method {}.{} failed to deny access via RBAC using '
128 'credential {}.'.format(client_str, method_str, cred))
129
130 def _list_get_RBAC_enforcement(self, client_str, method_str,
131 expected_allowed, *args, **kwargs):
132 """Test an API call RBAC enforcement.
133
134 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000135 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000136 :param method_str: The method on the client to call for the test.
137 Example: 'list_amphorae'
138 :param expected_allowed: The list of credentials expected to be
139 allowed. Example: ['os_roles_lb_member'].
140 :param args: Any positional parameters needed by the method.
141 :param kwargs: Any named parameters needed by the method.
142 :raises AssertionError: Raised if the RBAC tests fail.
143 :raises Forbidden: Raised if a credential that should have access does
144 not and is denied.
145 :raises InvalidScope: Raised if a credential that should have the
146 correct scope for access is denied.
147 :returns: None on success
148 """
149
150 allowed_list = copy.deepcopy(expected_allowed)
Michael Johnson6006de72021-02-21 01:42:39 +0000151
152 # #### Test that disallowed credentials cannot access the API.
153 self._check_disallowed(client_str, method_str, allowed_list,
154 None, None, *args, **kwargs)
155
156 # #### Test that allowed credentials can access the API.
157 self._check_allowed(client_str, method_str, allowed_list,
158 *args, **kwargs)
159
160 def check_show_RBAC_enforcement(self, client_str, method_str,
161 expected_allowed, *args, **kwargs):
162 """Test an API show call RBAC enforcement.
163
164 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000165 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000166 :param method_str: The method on the client to call for the test.
167 Example: 'list_amphorae'
168 :param expected_allowed: The list of credentials expected to be
169 allowed. Example: ['os_roles_lb_member'].
170 :param args: Any positional parameters needed by the method.
171 :param kwargs: Any named parameters needed by the method.
172 :raises AssertionError: Raised if the RBAC tests fail.
173 :raises Forbidden: Raised if a credential that should have access does
174 not and is denied.
175 :raises InvalidScope: Raised if a credential that should have the
176 correct scope for access is denied.
177 :returns: None on success
178 """
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700179 if CONF.load_balancer.RBAC_test_type == constants.NONE:
180 return
Michael Johnson6006de72021-02-21 01:42:39 +0000181 self._list_get_RBAC_enforcement(client_str, method_str,
182 expected_allowed, *args, **kwargs)
183
184 def check_list_RBAC_enforcement(self, client_str, method_str,
185 expected_allowed, *args, **kwargs):
186 """Test an API list call RBAC enforcement.
187
188 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000189 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000190 :param method_str: The method on the client to call for the test.
191 Example: 'list_amphorae'
192 :param expected_allowed: The list of credentials expected to be
193 allowed. Example: ['os_roles_lb_member'].
194 :param args: Any positional parameters needed by the method.
195 :param kwargs: Any named parameters needed by the method.
196 :raises AssertionError: Raised if the RBAC tests fail.
197 :raises Forbidden: Raised if a credential that should have access does
198 not and is denied.
199 :raises InvalidScope: Raised if a credential that should have the
200 correct scope for access is denied.
201 :returns: None on success
202 """
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700203 if CONF.load_balancer.RBAC_test_type == constants.NONE:
204 return
Michael Johnson6006de72021-02-21 01:42:39 +0000205 self._list_get_RBAC_enforcement(client_str, method_str,
206 expected_allowed, *args, **kwargs)
207
208 def _CUD_RBAC_enforcement(self, client_str, method_str, expected_allowed,
209 status_method=None, obj_id=None,
210 *args, **kwargs):
211 """Test an API create/update/delete call RBAC enforcement.
212
213 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000214 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000215 :param method_str: The method on the client to call for the test.
216 Example: 'list_amphorae'
217 :param expected_allowed: The list of credentials expected to be
218 allowed. Example: ['os_roles_lb_member'].
219 :param status_method: The service client method that will provide
220 the object status for a status change waiter.
221 :param obj_id: The ID of the object to check for the expected status
222 update.
223 :param args: Any positional parameters needed by the method.
224 :param kwargs: Any named parameters needed by the method.
225 :raises AssertionError: Raised if the RBAC tests fail.
226 :raises Forbidden: Raised if a credential that should have access does
227 not and is denied.
228 :raises InvalidScope: Raised if a credential that should have the
229 correct scope for access is denied.
230 :returns: None on success
231 """
232
233 allowed_list = copy.deepcopy(expected_allowed)
Michael Johnson6006de72021-02-21 01:42:39 +0000234
235 # #### Test that disallowed credentials cannot access the API.
236 self._check_disallowed(client_str, method_str, allowed_list,
237 status_method, obj_id, *args, **kwargs)
238
239 def check_create_RBAC_enforcement(
240 self, client_str, method_str, expected_allowed,
241 status_method=None, obj_id=None, *args, **kwargs):
242 """Test an API create call RBAC enforcement.
243
244 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000245 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000246 :param method_str: The method on the client to call for the test.
247 Example: 'list_amphorae'
248 :param expected_allowed: The list of credentials expected to be
249 allowed. Example: ['os_roles_lb_member'].
250 :param status_method: The service client method that will provide
251 the object status for a status change waiter.
252 :param obj_id: The ID of the object to check for the expected status
253 update.
254 :param args: Any positional parameters needed by the method.
255 :param kwargs: Any named parameters needed by the method.
256 :raises AssertionError: Raised if the RBAC tests fail.
257 :raises Forbidden: Raised if a credential that should have access does
258 not and is denied.
259 :raises InvalidScope: Raised if a credential that should have the
260 correct scope for access is denied.
261 :returns: None on success
262 """
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700263 if CONF.load_balancer.RBAC_test_type == constants.NONE:
264 return
Michael Johnson6006de72021-02-21 01:42:39 +0000265 self._CUD_RBAC_enforcement(client_str, method_str, expected_allowed,
266 status_method, obj_id, *args, **kwargs)
267
268 def check_delete_RBAC_enforcement(
269 self, client_str, method_str, expected_allowed,
270 status_method=None, obj_id=None, *args, **kwargs):
271 """Test an API delete call RBAC enforcement.
272
273 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000274 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000275 :param method_str: The method on the client to call for the test.
276 Example: 'list_amphorae'
277 :param expected_allowed: The list of credentials expected to be
278 allowed. Example: ['os_roles_lb_member'].
279 :param status_method: The service client method that will provide
280 the object status for a status change waiter.
281 :param obj_id: The ID of the object to check for the expected status
282 update.
283 :param args: Any positional parameters needed by the method.
284 :param kwargs: Any named parameters needed by the method.
285 :raises AssertionError: Raised if the RBAC tests fail.
286 :raises Forbidden: Raised if a credential that should have access does
287 not and is denied.
288 :raises InvalidScope: Raised if a credential that should have the
289 correct scope for access is denied.
290 :returns: None on success
291 """
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700292 if CONF.load_balancer.RBAC_test_type == constants.NONE:
293 return
Michael Johnson6006de72021-02-21 01:42:39 +0000294 self._CUD_RBAC_enforcement(client_str, method_str, expected_allowed,
295 status_method, obj_id, *args, **kwargs)
296
297 def check_update_RBAC_enforcement(
298 self, client_str, method_str, expected_allowed,
299 status_method=None, obj_id=None, *args, **kwargs):
300 """Test an API update call RBAC enforcement.
301
302 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000303 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000304 :param method_str: The method on the client to call for the test.
305 Example: 'list_amphorae'
306 :param expected_allowed: The list of credentials expected to be
307 allowed. Example: ['os_roles_lb_member'].
308 :param status_method: The service client method that will provide
309 the object status for a status change waiter.
310 :param obj_id: The ID of the object to check for the expected status
311 update.
312 :param args: Any positional parameters needed by the method.
313 :param kwargs: Any named parameters needed by the method.
314 :raises AssertionError: Raised if the RBAC tests fail.
315 :raises Forbidden: Raised if a credential that should have access does
316 not and is denied.
317 :raises InvalidScope: Raised if a credential that should have the
318 correct scope for access is denied.
319 :returns: None on success
320 """
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700321 if CONF.load_balancer.RBAC_test_type == constants.NONE:
322 return
Michael Johnson6006de72021-02-21 01:42:39 +0000323 self._CUD_RBAC_enforcement(client_str, method_str, expected_allowed,
324 status_method, obj_id, *args, **kwargs)
325
326 def check_list_RBAC_enforcement_count(
327 self, client_str, method_str, expected_allowed, expected_count,
328 *args, **kwargs):
329 """Test an API list call RBAC enforcement result count.
330
331 List APIs will return the object list for the project associated
332 with the token used to access the API. This means most credentials
333 will have access, but will get differing results.
334
335 This test will query the list API using a list of credentials and
336 will validate that only the expected count of results are returned.
337
338 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000339 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000340 :param method_str: The method on the client to call for the test.
341 Example: 'list_amphorae'
342 :param expected_allowed: The list of credentials expected to be
343 allowed. Example: ['os_roles_lb_member'].
344 :param expected_count: The number of results expected in the list
345 returned from the API.
346 :param args: Any positional parameters needed by the method.
347 :param kwargs: Any named parameters needed by the method.
348 :raises AssertionError: Raised if the RBAC tests fail.
349 :raises Forbidden: Raised if a credential that should have access does
350 not and is denied.
351 :raises InvalidScope: Raised if a credential that should have the
352 correct scope for access is denied.
353 :returns: None on success
354 """
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700355 if CONF.load_balancer.RBAC_test_type == constants.NONE:
356 return
Michael Johnson6006de72021-02-21 01:42:39 +0000357
358 allowed_list = copy.deepcopy(expected_allowed)
Michael Johnson6006de72021-02-21 01:42:39 +0000359
360 for cred in allowed_list:
361 try:
362 cred_obj = getattr(self, cred)
363 except AttributeError:
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700364 self.fail('Credential {} "expected_allowed" for RBAC '
365 'testing was not created by tempest '
366 'credentials setup. This is likely a bug in the '
367 'test.'.format(cred))
Michael Johnson29d8e612021-06-23 16:16:12 +0000368 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +0000369 try:
370 result = method(*args, **kwargs)
371 except exceptions.Forbidden as e:
372 self.fail('Method {}.{} failed to allow access via RBAC using '
373 'credential {}. Error: {}'.format(
374 client_str, method_str, cred, str(e)))
375 self.assertEqual(expected_count, len(result), message='Credential '
376 '{} saw {} objects when {} was expected.'.format(
377 cred, len(result), expected_count))
378
379 def check_list_IDs_RBAC_enforcement(
380 self, client_str, method_str, expected_allowed, expected_ids,
381 *args, **kwargs):
382 """Test an API list call RBAC enforcement result contains IDs.
383
384 List APIs will return the object list for the project associated
385 with the token used to access the API. This means most credentials
386 will have access, but will get differing results.
387
388 This test will query the list API using a list of credentials and
389 will validate that the expected object Ids in included in the results.
390
391 :param client_str: The service client to use for the test, without the
Michael Johnson29d8e612021-06-23 16:16:12 +0000392 credential. Example: 'AmphoraClient'
Michael Johnson6006de72021-02-21 01:42:39 +0000393 :param method_str: The method on the client to call for the test.
394 Example: 'list_amphorae'
395 :param expected_allowed: The list of credentials expected to be
396 allowed. Example: ['os_roles_lb_member'].
397 :param expected_ids: The list of object IDs to validate are included
398 in the returned list from the API.
399 :param args: Any positional parameters needed by the method.
400 :param kwargs: Any named parameters needed by the method.
401 :raises AssertionError: Raised if the RBAC tests fail.
402 :raises Forbidden: Raised if a credential that should have access does
403 not and is denied.
404 :raises InvalidScope: Raised if a credential that should have the
405 correct scope for access is denied.
406 :returns: None on success
407 """
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700408 if CONF.load_balancer.RBAC_test_type == constants.NONE:
409 return
Michael Johnson6006de72021-02-21 01:42:39 +0000410
411 allowed_list = copy.deepcopy(expected_allowed)
Michael Johnson6006de72021-02-21 01:42:39 +0000412
413 for cred in allowed_list:
414 try:
415 cred_obj = getattr(self, cred)
416 except AttributeError:
Ghanshyam Mann5f36fab2024-08-21 19:23:28 -0700417 self.fail('Credential {} "expected_allowed" for RBAC '
418 'testing was not created by tempest '
419 'credentials setup. This is likely a bug in the '
420 'test.'.format(cred))
Michael Johnson29d8e612021-06-23 16:16:12 +0000421 method = self._get_client_method(cred_obj, client_str, method_str)
Michael Johnson6006de72021-02-21 01:42:39 +0000422 try:
423 result = method(*args, **kwargs)
424 except exceptions.Forbidden as e:
425 self.fail('Method {}.{} failed to allow access via RBAC using '
426 'credential {}. Error: {}'.format(
427 client_str, method_str, cred, str(e)))
428 result_ids = [lb[constants.ID] for lb in result]
429 self.assertTrue(set(expected_ids).issubset(set(result_ids)))