blob: 860a53d64b162265e9fb0fdf1c209ab60686cedb [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 copy
DavidPurcell029d8c32017-01-06 15:27:41 -050017import os
18
DavidPurcell029d8c32017-01-06 15:27:41 -050019from oslo_log import log as logging
Felipe Monteiro9c978502017-01-27 17:07:54 -050020from oslo_policy import generator
DavidPurcell029d8c32017-01-06 15:27:41 -050021from oslo_policy import policy
DavidPurcell029d8c32017-01-06 15:27:41 -050022
Felipe Monteirob0595652017-01-23 16:51:58 -050023from patrole_tempest_plugin import rbac_exceptions
DavidPurcell029d8c32017-01-06 15:27:41 -050024
DavidPurcell029d8c32017-01-06 15:27:41 -050025LOG = logging.getLogger(__name__)
26
DavidPurcell029d8c32017-01-06 15:27:41 -050027
Felipe Monteiro322c5b62017-02-26 02:44:21 +000028class RbacPolicyParser(object):
DavidPurcell029d8c32017-01-06 15:27:41 -050029 """A class for parsing policy rules into lists of allowed roles.
30
31 RBAC testing requires that each rule in a policy file be broken up into
32 the roles that constitute it. This class automates that process.
Felipe Monteirob0595652017-01-23 16:51:58 -050033
34 The list of roles per rule can be reverse-engineered by checking, for
35 each role, whether a given rule is allowed using oslo policy.
DavidPurcell029d8c32017-01-06 15:27:41 -050036 """
37
Felipe Monteirob0595652017-01-23 16:51:58 -050038 def __init__(self, tenant_id, service, path=None):
Felipe Monteiro322c5b62017-02-26 02:44:21 +000039 """Initialization of Rbac Policy Parser.
DavidPurcell029d8c32017-01-06 15:27:41 -050040
Felipe Monteiro9c978502017-01-27 17:07:54 -050041 Parses a policy file to create a dictionary, mapping policy actions to
42 roles. If a policy file does not exist, checks whether the policy file
43 is registered as a namespace under oslo.policy.policies. Nova, for
44 example, doesn't use a policy.json file by default; its policy is
45 implemented in code and registered as 'nova' under
46 oslo.policy.policies.
47
48 If the policy file is not found in either place, raises an exception.
49
50 Additionally, if the policy file exists in both code and as a
51 policy.json (for example, by creating a custom nova policy.json file),
52 the custom policy file over the default policy implementation is
53 prioritized.
Felipe Monteirob0595652017-01-23 16:51:58 -050054
55 :param tenant_id: type uuid
DavidPurcell029d8c32017-01-06 15:27:41 -050056 :param service: type string
57 :param path: type string
58 """
Felipe Monteiro9c978502017-01-27 17:07:54 -050059 service = service.lower().strip()
DavidPurcell029d8c32017-01-06 15:27:41 -050060 if path is None:
Felipe Monteirob0595652017-01-23 16:51:58 -050061 self.path = '/etc/{0}/policy.json'.format(service)
62 else:
63 self.path = path
DavidPurcell029d8c32017-01-06 15:27:41 -050064
Felipe Monteiro9c978502017-01-27 17:07:54 -050065 policy_data = "{}"
DavidPurcell029d8c32017-01-06 15:27:41 -050066
Felipe Monteiro9c978502017-01-27 17:07:54 -050067 # First check whether policy file exists.
68 if os.path.isfile(self.path):
69 policy_data = open(self.path, 'r').read()
70 # Otherwise use oslo_policy to fetch the rules for provided service.
71 else:
72 policy_generator = generator._get_policies_dict([service])
73 if policy_generator and service in policy_generator:
74 policy_data = "{\n"
75 for r in policy_generator[service]:
76 policy_data = policy_data + r.__str__() + ",\n"
77 policy_data = policy_data[:-2] + "\n}"
78 # Otherwise raise an exception.
79 else:
80 raise rbac_exceptions.RbacResourceSetupFailed(
81 'Policy file for service: {0}, {1} not found.'
82 .format(service, self.path))
83
84 self.rules = policy.Rules.load(policy_data, "default")
Felipe Monteirob0595652017-01-23 16:51:58 -050085 self.tenant_id = tenant_id
DavidPurcell029d8c32017-01-06 15:27:41 -050086
Felipe Monteirob0595652017-01-23 16:51:58 -050087 def allowed(self, rule_name, role):
Felipe Monteiro9c978502017-01-27 17:07:54 -050088 is_admin_context = self._is_admin_context(role)
Felipe Monteirob0595652017-01-23 16:51:58 -050089 is_allowed = self._allowed(
Felipe Monteiro9c978502017-01-27 17:07:54 -050090 access=self._get_access_token(role),
Felipe Monteirob0595652017-01-23 16:51:58 -050091 apply_rule=rule_name,
Felipe Monteiro9c978502017-01-27 17:07:54 -050092 is_admin=is_admin_context)
DavidPurcell029d8c32017-01-06 15:27:41 -050093
Felipe Monteiro9c978502017-01-27 17:07:54 -050094 return is_allowed
DavidPurcell029d8c32017-01-06 15:27:41 -050095
Felipe Monteiro9c978502017-01-27 17:07:54 -050096 def _is_admin_context(self, role):
97 """Checks whether a role has admin context.
98
99 If context_is_admin is contained in the policy file, then checks
100 whether the given role is contained in context_is_admin. If it is not
101 in the policy file, then default to context_is_admin: admin.
102 """
103 if 'context_is_admin' in self.rules.keys():
104 return self._allowed(
105 access=self._get_access_token(role),
106 apply_rule='context_is_admin')
107 return role == 'admin'
DavidPurcell029d8c32017-01-06 15:27:41 -0500108
Felipe Monteirob0595652017-01-23 16:51:58 -0500109 def _get_access_token(self, role):
110 access_token = {
111 "token": {
112 "roles": [
113 {
114 "name": role
115 }
116 ],
Felipe Monteiro9fc782e2017-02-01 15:38:46 -0500117 "project_id": self.tenant_id,
118 "tenant_id": self.tenant_id
Felipe Monteirob0595652017-01-23 16:51:58 -0500119 }
120 }
121 return access_token
DavidPurcell029d8c32017-01-06 15:27:41 -0500122
Felipe Monteiro9c978502017-01-27 17:07:54 -0500123 def _allowed(self, access, apply_rule, is_admin=False):
Felipe Monteirob0595652017-01-23 16:51:58 -0500124 """Checks if a given rule in a policy is allowed with given access.
DavidPurcell029d8c32017-01-06 15:27:41 -0500125
Felipe Monteirob0595652017-01-23 16:51:58 -0500126 Adapted from oslo_policy.shell.
DavidPurcell029d8c32017-01-06 15:27:41 -0500127
Felipe Monteirob0595652017-01-23 16:51:58 -0500128 :param access: type dict: dictionary from ``_get_access_token``
129 :param apply_rule: type string: rule to be checked
130 :param is_admin: type bool: whether admin context is used
DavidPurcell029d8c32017-01-06 15:27:41 -0500131 """
Felipe Monteirob0595652017-01-23 16:51:58 -0500132 access_data = copy.copy(access['token'])
133 access_data['roles'] = [role['name'] for role in access_data['roles']]
Felipe Monteirob0595652017-01-23 16:51:58 -0500134 access_data['is_admin'] = is_admin
Felipe Monteiro9c978502017-01-27 17:07:54 -0500135 # TODO(felipemonteiro): Dynamically calculate is_admin_project rather
136 # than hard-coding it to True. is_admin_project cannot be determined
137 # from the role, but rather from project and domain names. See
138 # _populate_is_admin_project in keystone.token.providers.common
139 # for more information.
140 access_data['is_admin_project'] = True
DavidPurcell029d8c32017-01-06 15:27:41 -0500141
Felipe Monteirob0595652017-01-23 16:51:58 -0500142 class Object(object):
143 pass
144 o = Object()
Felipe Monteiro9c978502017-01-27 17:07:54 -0500145 o.rules = self.rules
DavidPurcell029d8c32017-01-06 15:27:41 -0500146
Felipe Monteiro9fc782e2017-02-01 15:38:46 -0500147 target = {"project_id": access_data['project_id'],
148 "tenant_id": access_data['project_id'],
149 "network:tenant_id": access_data['project_id']}
Felipe Monteirob0595652017-01-23 16:51:58 -0500150
Felipe Monteiro9c978502017-01-27 17:07:54 -0500151 result = self._try_rule(apply_rule, target, access_data, o)
Felipe Monteirob0595652017-01-23 16:51:58 -0500152 return result
153
Felipe Monteiro9c978502017-01-27 17:07:54 -0500154 def _try_rule(self, apply_rule, target, access_data, o):
Felipe Monteirob0595652017-01-23 16:51:58 -0500155 try:
Felipe Monteiro9c978502017-01-27 17:07:54 -0500156 rule = self.rules[apply_rule]
Felipe Monteirob0595652017-01-23 16:51:58 -0500157 return rule(target, access_data, o)
Felipe Monteiro9c978502017-01-27 17:07:54 -0500158 except KeyError as e:
159 LOG.debug("{0} not found in policy file.".format(apply_rule))
160 return False
Felipe Monteirob0595652017-01-23 16:51:58 -0500161 except Exception as e:
Felipe Monteiro322c5b62017-02-26 02:44:21 +0000162 LOG.debug("Exception: {0} for rule: {1}.".format(e, apply_rule))
Felipe Monteirob0595652017-01-23 16:51:58 -0500163 return False