blob: 9b959d796486d414acc960c9d9b287cdb6bea273 [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
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +000017import sys
18
19import six
Felipe Monteirob0595652017-01-23 16:51:58 -050020
DavidPurcell029d8c32017-01-06 15:27:41 -050021from tempest import config
22from tempest.lib import exceptions
raiesmh088590c0c2017-03-14 18:06:52 +053023from tempest import test
DavidPurcell029d8c32017-01-06 15:27:41 -050024
25from patrole_tempest_plugin import rbac_auth
26from patrole_tempest_plugin import rbac_exceptions
27
28CONF = config.CONF
29LOG = logging.getLogger(__name__)
30
31
Felipe Monteirod5d76b82017-03-20 23:18:50 +000032def action(service, rule, admin_only=False, expected_error_code=403):
33 """A decorator which does a policy check and matches it against test run.
34
35 A decorator which allows for positive and negative RBAC testing. Given
36 an OpenStack service and a policy action enforced by that service, an
37 oslo.policy lookup is performed by calling `authority.get_permission`.
38 The following cases are possible:
39
40 * If `allowed` is True and the test passes, this is a success.
41 * If `allowed` is True and the test fails, this is a failure.
42 * If `allowed` is False and the test passes, this is a failure.
43 * If `allowed` is False and the test fails, this is a success.
44
45 :param service: A OpenStack service: for example, "nova" or "neutron".
46 :param rule: A policy action defined in a policy.json file (or in code).
47 :param admin_only: Skips over oslo.policy check because the policy action
48 defined by `rule` is not enforced by the service's
49 policy enforcement logic. For example, Keystone v2
50 performs an admin check for most of its endpoints. If
51 True, `rule` is effectively ignored.
52 :param expected_error_code: Overrides default value of 403 (Forbidden)
53 with endpoint-specific error code. Currently
54 only supports 403 and 404. Support for 404
55 is needed because some services, like Neutron,
56 intentionally throw a 404 for security reasons.
57
58 :raises NotFound: if `service` is invalid or
59 if Tempest credentials cannot be found.
60 :raises Forbidden: for bullet (2) above.
61 :raises RbacOverPermission: for bullet (3) above.
62 """
DavidPurcell029d8c32017-01-06 15:27:41 -050063 def decorator(func):
64 def wrapper(*args, **kwargs):
Felipe Monteirocbd06172017-01-24 13:49:16 -050065 try:
raiesmh088590c0c2017-03-14 18:06:52 +053066 caller_ref = None
67 if args and isinstance(args[0], test.BaseTestCase):
68 caller_ref = args[0]
69 tenant_id = caller_ref.auth_provider.credentials.tenant_id
70 user_id = caller_ref.auth_provider.credentials.user_id
71 except AttributeError as e:
Felipe Monteiro889264e2017-03-01 17:19:35 -050072 msg = ("{0}: tenant_id/user_id not found in "
Felipe Monteirocbd06172017-01-24 13:49:16 -050073 "cls.auth_provider.credentials".format(e))
74 LOG.error(msg)
75 raise rbac_exceptions.RbacResourceSetupFailed(msg)
raiesmh088590c0c2017-03-14 18:06:52 +053076
Felipe Monteirod5d76b82017-03-20 23:18:50 +000077 if admin_only:
78 LOG.info("As admin_only is True, only admin role should be "
79 "allowed to perform the API. Skipping oslo.policy "
80 "check for policy action {0}.".format(rule))
81 allowed = CONF.rbac.rbac_test_role == 'admin'
82 else:
83 authority = rbac_auth.RbacAuthority(tenant_id, user_id,
84 service)
85 allowed = authority.get_permission(rule,
86 CONF.rbac.rbac_test_role)
87
Rick Bartra12998942017-03-17 17:35:45 -040088 expected_exception, irregular_msg = _get_exception_type(
89 expected_error_code)
DavidPurcell029d8c32017-01-06 15:27:41 -050090
91 try:
92 func(*args)
Rick Bartra503c5572017-03-09 13:49:58 -050093 except rbac_exceptions.RbacInvalidService as e:
Felipe Monteiro48c913d2017-03-15 12:07:48 -040094 msg = ("%s is not a valid service." % service)
95 LOG.error(msg)
96 raise exceptions.NotFound(
97 "%s RbacInvalidService was: %s" %
98 (msg, e))
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +000099 except (expected_exception, rbac_exceptions.RbacActionFailed) as e:
100 if irregular_msg:
101 LOG.warning(irregular_msg.format(rule, service))
DavidPurcell029d8c32017-01-06 15:27:41 -0500102 if allowed:
103 msg = ("Role %s was not allowed to perform %s." %
104 (CONF.rbac.rbac_test_role, rule))
105 LOG.error(msg)
106 raise exceptions.Forbidden(
107 "%s exception was: %s" %
108 (msg, e))
Felipe Monteiro8eda8cc2017-03-22 14:15:14 +0000109 except Exception as e:
110 exc_info = sys.exc_info()
111 error_details = exc_info[1].__str__()
112 msg = ("%s An unexpected exception has occurred: Expected "
113 "exception was %s, which was not thrown."
114 % (error_details, expected_exception.__name__))
115 LOG.error(msg)
116 six.reraise(exc_info[0], exc_info[0](msg), exc_info[2])
DavidPurcell029d8c32017-01-06 15:27:41 -0500117 else:
118 if not allowed:
119 LOG.error("Role %s was allowed to perform %s" %
120 (CONF.rbac.rbac_test_role, rule))
121 raise rbac_exceptions.RbacOverPermission(
122 "OverPermission: Role %s was allowed to perform %s" %
123 (CONF.rbac.rbac_test_role, rule))
raiesmh088590c0c2017-03-14 18:06:52 +0530124 finally:
125 caller_ref.rbac_utils.switch_role(caller_ref,
126 switchToRbacRole=False)
DavidPurcell029d8c32017-01-06 15:27:41 -0500127 return wrapper
128 return decorator
Rick Bartra12998942017-03-17 17:35:45 -0400129
130
131def _get_exception_type(expected_error_code):
132 expected_exception = None
133 irregular_msg = None
134 supported_error_codes = [403, 404]
135 if expected_error_code == 403:
136 expected_exception = exceptions.Forbidden
137 elif expected_error_code == 404:
138 expected_exception = exceptions.NotFound
139 irregular_msg = ("NotFound exception was caught for policy action "
140 "{0}. The service {1} throws a 404 instead of a 403, "
141 "which is irregular.")
142 else:
143 msg = ("Please pass an expected error code. Currently "
144 "supported codes: {0}".format(str(supported_error_codes)))
145 LOG.error(msg)
146 raise rbac_exceptions.RbacInvalidErrorCode()
147
148 return expected_exception, irregular_msg