# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2011 OpenStack Foundation # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Policy Engine For Glance""" import json import os.path from oslo.config import cfg from glance.common import exception import glance.domain.proxy import glance.openstack.common.log as logging from glance.openstack.common import policy LOG = logging.getLogger(__name__) policy_opts = [ cfg.StrOpt('policy_file', default='policy.json', help=_('The location of the policy file.')), cfg.StrOpt('policy_default_rule', default='default', help=_('The default policy to use.')), ] CONF = cfg.CONF CONF.register_opts(policy_opts) DEFAULT_RULES = { 'context_is_admin': policy.RoleCheck('role', 'admin'), 'default': policy.TrueCheck(), 'manage_image_cache': policy.RoleCheck('role', 'admin'), } class Enforcer(object): """Responsible for loading and enforcing rules""" def __init__(self): self.default_rule = CONF.policy_default_rule self.policy_path = self._find_policy_file() self.policy_file_mtime = None self.policy_file_contents = None self.load_rules() def set_rules(self, rules): """Create a new Rules object based on the provided dict of rules""" rules_obj = policy.Rules(rules, self.default_rule) policy.set_rules(rules_obj) def add_rules(self, rules): """Add new rules to the Rules object""" if policy._rules: rules_obj = policy.Rules(rules) policy._rules.update(rules_obj) else: self.set_rules(rules) def load_rules(self): """Set the rules found in the json file on disk""" if self.policy_path: rules = self._read_policy_file() rule_type = "" else: rules = DEFAULT_RULES rule_type = "default " text_rules = dict((k, str(v)) for k, v in rules.items()) msg = (_('Loaded %(rule_type)spolicy rules: %(text_rules)s') % {'rule_type': rule_type, 'text_rules': text_rules}) LOG.debug(msg) self.set_rules(rules) @staticmethod def _find_policy_file(): """Locate the policy json data file""" policy_file = CONF.find_file(CONF.policy_file) if policy_file: return policy_file else: LOG.warn(_('Unable to find policy file')) return None def _read_policy_file(self): """Read contents of the policy file This re-caches policy data if the file has been changed. """ mtime = os.path.getmtime(self.policy_path) if not self.policy_file_contents or mtime != self.policy_file_mtime: LOG.debug(_("Loading policy from %s") % self.policy_path) with open(self.policy_path) as fap: raw_contents = fap.read() rules_dict = json.loads(raw_contents) self.policy_file_contents = dict( (k, policy.parse_rule(v)) for k, v in rules_dict.items()) self.policy_file_mtime = mtime return self.policy_file_contents def _check(self, context, rule, target, *args, **kwargs): """Verifies that the action is valid on the target in this context. :param context: Glance request context :param rule: String representing the action to be checked :param object: Dictionary representing the object of the action. :raises: `glance.common.exception.Forbidden` :returns: A non-False value if access is allowed. """ credentials = { 'roles': context.roles, 'user': context.user, 'tenant': context.tenant, } return policy.check(rule, target, credentials, *args, **kwargs) def enforce(self, context, action, target): """Verifies that the action is valid on the target in this context. :param context: Glance request context :param action: String representing the action to be checked :param object: Dictionary representing the object of the action. :raises: `glance.common.exception.Forbidden` :returns: A non-False value if access is allowed. """ return self._check(context, action, target, exception.Forbidden, action=action) def check(self, context, action, target): """Verifies that the action is valid on the target in this context. :param context: Glance request context :param action: String representing the action to be checked :param object: Dictionary representing the object of the action. :returns: A non-False value if access is allowed. """ return self._check(context, action, target) def check_is_admin(self, context): """Check if the given context is associated with an admin role, as defined via the 'context_is_admin' RBAC rule. :param context: Glance request context :returns: A non-False value if context role is admin. """ target = context.to_dict() return self.check(context, 'context_is_admin', target) class ImageRepoProxy(glance.domain.proxy.Repo): def __init__(self, image_repo, context, policy): self.context = context self.policy = policy self.image_repo = image_repo proxy_kwargs = {'context': self.context, 'policy': self.policy} super(ImageRepoProxy, self).__init__(image_repo, item_proxy_class=ImageProxy, item_proxy_kwargs=proxy_kwargs) def get(self, image_id): self.policy.enforce(self.context, 'get_image', {}) return super(ImageRepoProxy, self).get(image_id) def list(self, *args, **kwargs): self.policy.enforce(self.context, 'get_images', {}) return super(ImageRepoProxy, self).list(*args, **kwargs) def save(self, image): self.policy.enforce(self.context, 'modify_image', {}) return super(ImageRepoProxy, self).save(image) def add(self, image): self.policy.enforce(self.context, 'add_image', {}) return super(ImageRepoProxy, self).add(image) class ImageProxy(glance.domain.proxy.Image): def __init__(self, image, context, policy): self.image = image self.context = context self.policy = policy super(ImageProxy, self).__init__(image) @property def visibility(self): return self.image.visibility @visibility.setter def visibility(self, value): if value == 'public': self.policy.enforce(self.context, 'publicize_image', {}) self.image.visibility = value @property def locations(self): return ImageLocationsProxy(self.image.locations, self.context, self.policy) @locations.setter def locations(self, value): if not isinstance(value, (list, ImageLocationsProxy)): raise exception.Invalid(_('Invalid locations: %s') % value) self.policy.enforce(self.context, 'set_image_location', {}) new_locations = list(value) if (set([loc['url'] for loc in self.image.locations]) - set([loc['url'] for loc in new_locations])): self.policy.enforce(self.context, 'delete_image_location', {}) self.image.locations = new_locations def delete(self): self.policy.enforce(self.context, 'delete_image', {}) return self.image.delete() def get_data(self, *args, **kwargs): self.policy.enforce(self.context, 'download_image', {}) return self.image.get_data(*args, **kwargs) def set_data(self, *args, **kwargs): self.policy.enforce(self.context, 'upload_image', {}) return self.image.set_data(*args, **kwargs) def get_member_repo(self, **kwargs): member_repo = self.image.get_member_repo(**kwargs) return ImageMemberRepoProxy(member_repo, self.context, self.policy) class ImageFactoryProxy(glance.domain.proxy.ImageFactory): def __init__(self, image_factory, context, policy): self.image_factory = image_factory self.context = context self.policy = policy proxy_kwargs = {'context': self.context, 'policy': self.policy} super(ImageFactoryProxy, self).__init__(image_factory, proxy_class=ImageProxy, proxy_kwargs=proxy_kwargs) def new_image(self, **kwargs): if kwargs.get('visibility') == 'public': self.policy.enforce(self.context, 'publicize_image', {}) return super(ImageFactoryProxy, self).new_image(**kwargs) class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory): def __init__(self, member_factory, context, policy): super(ImageMemberFactoryProxy, self).__init__( member_factory, image_proxy_class=ImageProxy, image_proxy_kwargs={'context': context, 'policy': policy}) class ImageMemberRepoProxy(glance.domain.proxy.Repo): def __init__(self, member_repo, context, policy): self.member_repo = member_repo self.context = context self.policy = policy def add(self, member): self.policy.enforce(self.context, 'add_member', {}) self.member_repo.add(member) def get(self, member_id): self.policy.enforce(self.context, 'get_member', {}) return self.member_repo.get(member_id) def save(self, member): self.policy.enforce(self.context, 'modify_member', {}) self.member_repo.save(member) def list(self, *args, **kwargs): self.policy.enforce(self.context, 'get_members', {}) return self.member_repo.list(*args, **kwargs) def remove(self, member): self.policy.enforce(self.context, 'delete_member', {}) self.member_repo.remove(member) class ImageLocationsProxy(object): __hash__ = None def __init__(self, locations, context, policy): self.locations = locations self.context = context self.policy = policy def _get_checker(action, func_name): def _checker(self, *args, **kwargs): self.policy.enforce(self.context, action, {}) assert hasattr(self.locations, func_name) method = getattr(self.locations, func_name) return method(*args, **kwargs) return _checker count = _get_checker('get_image_location', 'count') index = _get_checker('get_image_location', 'index') __getitem__ = _get_checker('get_image_location', '__getitem__') __contains__ = _get_checker('get_image_location', '__contains__') __len__ = _get_checker('get_image_location', '__len__') __cast = _get_checker('get_image_location', '__cast') __cmp__ = _get_checker('get_image_location', '__cmp__') __iter__ = _get_checker('get_image_location', '__iter__') append = _get_checker('set_image_location', 'append') extend = _get_checker('set_image_location', 'extend') insert = _get_checker('set_image_location', 'insert') reverse = _get_checker('set_image_location', 'reverse') __iadd__ = _get_checker('set_image_location', '__iadd__') __setitem__ = _get_checker('set_image_location', '__setitem__') pop = _get_checker('delete_image_location', 'pop') remove = _get_checker('delete_image_location', 'remove') __delitem__ = _get_checker('delete_image_location', '__delitem__') __delslice__ = _get_checker('delete_image_location', '__delslice__') del _get_checker class TaskProxy(glance.domain.proxy.Task): def __init__(self, task, context, policy): self.task = task self.context = context self.policy = policy super(TaskProxy, self).__init__(task) def run(self, executor): self.base.run(executor) class TaskRepoProxy(glance.domain.proxy.Repo): def __init__(self, task_repo, context, policy): self.context = context self.policy = policy self.task_repo = task_repo proxy_kwargs = {'context': self.context, 'policy': self.policy} super(TaskRepoProxy, self).__init__( task_repo, item_proxy_class=TaskProxy, item_proxy_kwargs=proxy_kwargs ) def get(self, task_id): self.policy.enforce(self.context, 'get_task', {}) return super(TaskRepoProxy, self).get(task_id) def list(self, *args, **kwargs): self.policy.enforce(self.context, 'get_tasks', {}) return super(TaskRepoProxy, self).list(*args, **kwargs) def add(self, task): self.policy.enforce(self.context, 'add_task', {}) return super(TaskRepoProxy, self).add(task) def save(self, task): self.policy.enforce(self.context, 'modify_task', {}) return super(TaskRepoProxy, self).save(task) class TaskFactoryProxy(glance.domain.proxy.TaskFactory): def __init__(self, task_factory, context, policy): self.task_factory = task_factory self.context = context self.policy = policy proxy_kwargs = {'context': self.context, 'policy': self.policy} super(TaskFactoryProxy, self).__init__( task_factory, proxy_class=TaskProxy, proxy_kwargs=proxy_kwargs )