blob: 5e1e81670f7f95b0c3b6dcfb46bb948d5b008efd [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 Monteirob0595652017-01-23 16:51:58 -050016import logging
17
DavidPurcell029d8c32017-01-06 15:27:41 -050018from tempest import config
19from tempest.lib import exceptions
raiesmh088590c0c2017-03-14 18:06:52 +053020from tempest import test
DavidPurcell029d8c32017-01-06 15:27:41 -050021
22from patrole_tempest_plugin import rbac_auth
23from patrole_tempest_plugin import rbac_exceptions
24
25CONF = config.CONF
26LOG = logging.getLogger(__name__)
27
28
Felipe Monteirod5d76b82017-03-20 23:18:50 +000029def action(service, rule, admin_only=False, expected_error_code=403):
30 """A decorator which does a policy check and matches it against test run.
31
32 A decorator which allows for positive and negative RBAC testing. Given
33 an OpenStack service and a policy action enforced by that service, an
34 oslo.policy lookup is performed by calling `authority.get_permission`.
35 The following cases are possible:
36
37 * If `allowed` is True and the test passes, this is a success.
38 * If `allowed` is True and the test fails, this is a failure.
39 * If `allowed` is False and the test passes, this is a failure.
40 * If `allowed` is False and the test fails, this is a success.
41
42 :param service: A OpenStack service: for example, "nova" or "neutron".
43 :param rule: A policy action defined in a policy.json file (or in code).
44 :param admin_only: Skips over oslo.policy check because the policy action
45 defined by `rule` is not enforced by the service's
46 policy enforcement logic. For example, Keystone v2
47 performs an admin check for most of its endpoints. If
48 True, `rule` is effectively ignored.
49 :param expected_error_code: Overrides default value of 403 (Forbidden)
50 with endpoint-specific error code. Currently
51 only supports 403 and 404. Support for 404
52 is needed because some services, like Neutron,
53 intentionally throw a 404 for security reasons.
54
55 :raises NotFound: if `service` is invalid or
56 if Tempest credentials cannot be found.
57 :raises Forbidden: for bullet (2) above.
58 :raises RbacOverPermission: for bullet (3) above.
59 """
DavidPurcell029d8c32017-01-06 15:27:41 -050060 def decorator(func):
61 def wrapper(*args, **kwargs):
Felipe Monteirocbd06172017-01-24 13:49:16 -050062 try:
raiesmh088590c0c2017-03-14 18:06:52 +053063 caller_ref = None
64 if args and isinstance(args[0], test.BaseTestCase):
65 caller_ref = args[0]
66 tenant_id = caller_ref.auth_provider.credentials.tenant_id
67 user_id = caller_ref.auth_provider.credentials.user_id
68 except AttributeError as e:
Felipe Monteiro889264e2017-03-01 17:19:35 -050069 msg = ("{0}: tenant_id/user_id not found in "
Felipe Monteirocbd06172017-01-24 13:49:16 -050070 "cls.auth_provider.credentials".format(e))
71 LOG.error(msg)
72 raise rbac_exceptions.RbacResourceSetupFailed(msg)
raiesmh088590c0c2017-03-14 18:06:52 +053073
Felipe Monteirod5d76b82017-03-20 23:18:50 +000074 if admin_only:
75 LOG.info("As admin_only is True, only admin role should be "
76 "allowed to perform the API. Skipping oslo.policy "
77 "check for policy action {0}.".format(rule))
78 allowed = CONF.rbac.rbac_test_role == 'admin'
79 else:
80 authority = rbac_auth.RbacAuthority(tenant_id, user_id,
81 service)
82 allowed = authority.get_permission(rule,
83 CONF.rbac.rbac_test_role)
84
Rick Bartra12998942017-03-17 17:35:45 -040085 expected_exception, irregular_msg = _get_exception_type(
86 expected_error_code)
DavidPurcell029d8c32017-01-06 15:27:41 -050087
88 try:
89 func(*args)
Rick Bartra503c5572017-03-09 13:49:58 -050090 except rbac_exceptions.RbacInvalidService as e:
Felipe Monteiro48c913d2017-03-15 12:07:48 -040091 msg = ("%s is not a valid service." % service)
92 LOG.error(msg)
93 raise exceptions.NotFound(
94 "%s RbacInvalidService was: %s" %
95 (msg, e))
Rick Bartra12998942017-03-17 17:35:45 -040096 except expected_exception as e:
DavidPurcell029d8c32017-01-06 15:27:41 -050097 if allowed:
98 msg = ("Role %s was not allowed to perform %s." %
99 (CONF.rbac.rbac_test_role, rule))
100 LOG.error(msg)
101 raise exceptions.Forbidden(
102 "%s exception was: %s" %
103 (msg, e))
Rick Bartra12998942017-03-17 17:35:45 -0400104 if irregular_msg:
105 LOG.warning(irregular_msg.format(rule, service))
DavidPurcell029d8c32017-01-06 15:27:41 -0500106 except rbac_exceptions.RbacActionFailed as e:
107 if allowed:
108 msg = ("Role %s was not allowed to perform %s." %
109 (CONF.rbac.rbac_test_role, rule))
110 LOG.error(msg)
111 raise exceptions.Forbidden(
112 "%s RbacActionFailed was: %s" %
113 (msg, e))
114 else:
115 if not allowed:
116 LOG.error("Role %s was allowed to perform %s" %
117 (CONF.rbac.rbac_test_role, rule))
118 raise rbac_exceptions.RbacOverPermission(
119 "OverPermission: Role %s was allowed to perform %s" %
120 (CONF.rbac.rbac_test_role, rule))
raiesmh088590c0c2017-03-14 18:06:52 +0530121 finally:
122 caller_ref.rbac_utils.switch_role(caller_ref,
123 switchToRbacRole=False)
DavidPurcell029d8c32017-01-06 15:27:41 -0500124 return wrapper
125 return decorator
Rick Bartra12998942017-03-17 17:35:45 -0400126
127
128def _get_exception_type(expected_error_code):
129 expected_exception = None
130 irregular_msg = None
131 supported_error_codes = [403, 404]
132 if expected_error_code == 403:
133 expected_exception = exceptions.Forbidden
134 elif expected_error_code == 404:
135 expected_exception = exceptions.NotFound
136 irregular_msg = ("NotFound exception was caught for policy action "
137 "{0}. The service {1} throws a 404 instead of a 403, "
138 "which is irregular.")
139 else:
140 msg = ("Please pass an expected error code. Currently "
141 "supported codes: {0}".format(str(supported_error_codes)))
142 LOG.error(msg)
143 raise rbac_exceptions.RbacInvalidErrorCode()
144
145 return expected_exception, irregular_msg