blob: 75df9f4bd7fac470cfab582623099685c34b8594 [file] [log] [blame]
Rick Bartraed950052017-06-29 17:20:33 -04001# Copyright 2017 AT&T Corporation.
2# 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.
15import yaml
16
17from oslo_log import log as logging
18
Felipe Monteiro778b7802018-05-31 19:52:58 -040019from tempest import config
Rick Bartraed950052017-06-29 17:20:33 -040020from tempest.lib import exceptions
21
Felipe Monteiro31e308e2018-05-22 12:05:10 -070022from patrole_tempest_plugin.rbac_authority import RbacAuthority
Rick Bartraed950052017-06-29 17:20:33 -040023
Felipe Monteiro778b7802018-05-31 19:52:58 -040024CONF = config.CONF
Rick Bartraed950052017-06-29 17:20:33 -040025LOG = logging.getLogger(__name__)
26
27
28class RequirementsParser(object):
Felipe Monteiro778b7802018-05-31 19:52:58 -040029 """A class that parses a custom requirements file."""
Rick Bartraed950052017-06-29 17:20:33 -040030 _inner = None
31
32 class Inner(object):
33 _rbac_map = None
34
35 def __init__(self, filepath):
36 with open(filepath) as f:
37 RequirementsParser.Inner._rbac_map = \
38 list(yaml.safe_load_all(f))
39
40 def __init__(self, filepath):
41 if RequirementsParser._inner is None:
42 RequirementsParser._inner = RequirementsParser.Inner(filepath)
43
44 @staticmethod
45 def parse(component):
Felipe Monteiro778b7802018-05-31 19:52:58 -040046 """Parses a requirements file with the following format:
47
48 .. code-block:: yaml
49
50 <service_foo>:
51 <api_action_a>:
52 - <allowed_role_1>
53 - <allowed_role_2>
54 - <allowed_role_3>
55 <api_action_b>:
56 - <allowed_role_2>
57 - <allowed_role_4>
58 <service_bar>:
59 <api_action_c>:
60 - <allowed_role_3>
61
62 :param str component: Name of the OpenStack service to be validated.
63 :returns: The dictionary that maps each policy action to the list
64 of allowed roles, for the given ``component``.
65 :rtype: dict
66 """
Rick Bartraed950052017-06-29 17:20:33 -040067 try:
68 for section in RequirementsParser.Inner._rbac_map:
69 if component in section:
70 return section[component]
71 except yaml.parser.ParserError:
72 LOG.error("Error while parsing the requirements YAML file. Did "
73 "you pass a valid component name from the test case?")
74 return None
75
76
77class RequirementsAuthority(RbacAuthority):
Felipe Monteiro778b7802018-05-31 19:52:58 -040078 """A class that uses a custom requirements file to validate RBAC."""
79
Rick Bartraed950052017-06-29 17:20:33 -040080 def __init__(self, filepath=None, component=None):
Felipe Monteiro778b7802018-05-31 19:52:58 -040081 """This class can be used to achieve a requirements-driven approach to
82 validating an OpenStack cloud's RBAC implementation. Using this
83 approach, Patrole computes expected test results by performing lookups
84 against a custom requirements file which precisely defines the cloud's
85 RBAC requirements.
86
87 :param str filepath: Path where the custom requirements file lives.
88 Defaults to ``[patrole].custom_requirements_file``.
89 :param str component: Name of the OpenStack service to be validated.
90 """
91 filepath = filepath or CONF.patrole.custom_requirements_file
92
93 if component is not None:
Rick Bartraed950052017-06-29 17:20:33 -040094 self.roles_dict = RequirementsParser(filepath).parse(component)
95 else:
96 self.roles_dict = None
97
98 def allowed(self, rule_name, role):
Felipe Monteiro778b7802018-05-31 19:52:58 -040099 """Checks if a given rule in a policy is allowed with given role.
100
101 :param string rule_name: Rule to be checked using provided requirements
102 file specified by ``[patrole].custom_requirements_file``. Must be
103 a key present in this file, under the appropriate component.
104 :param string role: Role to validate against custom requirements file.
105 :returns: True if ``role`` is allowed to perform ``rule_name``, else
106 False.
107 :rtype: bool
108 :raises KeyError: If ``rule_name`` does not exist among the keyed
109 policy names in the custom requirements file.
110 """
Rick Bartraed950052017-06-29 17:20:33 -0400111 if self.roles_dict is None:
112 raise exceptions.InvalidConfiguration(
113 "Roles dictionary parsed from requirements YAML file is "
114 "empty. Ensure the requirements YAML file is correctly "
115 "formatted.")
116 try:
117 _api = self.roles_dict[rule_name]
118 return role in _api
119 except KeyError:
120 raise KeyError("'%s' API is not defined in the requirements YAML "
121 "file" % rule_name)
122 return False