# Copyright (c) 2011 OpenStack Foundation # 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 Ironic.""" import sys from oslo_concurrency import lockutils from oslo_config import cfg from oslo_log import log from oslo_policy import policy from iotronic.common import exception from iotronic.common.i18n import _LW _ENFORCER = None CONF = cfg.CONF LOG = log.getLogger(__name__) default_policies = [ # Legacy setting, don't remove. Likely to be overridden by operators who # forget to update their policy.json configuration file. # This gets rolled into the new "is_admin" rule below. policy.RuleDefault('admin_api', 'role:admin or role:administrator', description='Legacy rule for cloud admin access'), # is_public_api is set in the environment from AuthTokenMiddleware policy.RuleDefault('public_api', 'is_public_api:True', description='Internal flag for public API routes'), policy.RuleDefault('is_admin', 'rule:admin_api', description='Full read/write API access'), policy.RuleDefault('is_admin_iot_project', 'role:admin_iot_project', description='Full read/write API access'), policy.RuleDefault('is_manager_iot_project', 'role:manager_iot_project', description='Full read/write API access'), policy.RuleDefault('is_user_iot', 'role:user_iot', description='Full read/write API access'), policy.RuleDefault('is_owner', 'user:%(owner)s', description='full access to the owner'), policy.RuleDefault('admin_or_owner', 'rule:is_admin or rule:is_owner', description='full access to the owner or the admin'), policy.RuleDefault('is_iot_member', 'rule:is_admin_iot_project ' 'or rule:is_manager_iot_project or rule:is_user_iot', description='define a member on iot context'), ] # NOTE(deva): to follow policy-in-code spec, we define defaults for # the granular policies in code, rather than in policy.json. # All of these may be overridden by configuration, but we can # depend on their existence throughout the code. board_policies = [ policy.RuleDefault('iot:board:get', 'rule:is_admin or rule:is_iot_member', description='Retrieve Board records'), policy.RuleDefault('iot:board:create', 'rule:is_admin_iot_project', description='Create Board records'), policy.RuleDefault('iot:board:delete', 'rule:is_admin or rule:is_admin_iot_project ' 'or rule:is_manager_iot_project', description='Delete Board records'), policy.RuleDefault('iot:board:update', 'rule:is_admin or rule:is_admin_iot_project ' 'or rule:is_manager_iot_project', description='Update Board records'), ] plugin_policies = [ policy.RuleDefault('iot:plugin:get', 'rule:is_admin or rule:is_iot_member', description='Retrieve Plugin records'), policy.RuleDefault('iot:plugin:create', 'rule:is_iot_member', description='Create Plugin records'), policy.RuleDefault('iot:plugin:get_one', 'rule:admin_or_owner', description='Retrieve a Plugin record'), policy.RuleDefault('iot:plugin:delete', 'rule:admin_or_owner', description='Delete Plugin records'), policy.RuleDefault('iot:plugin:update', 'rule:admin_or_owner', description='Update Plugin records'), ] injection_plugin_policies = [ policy.RuleDefault('iot:plugin_on_board:get', 'rule:admin_or_owner', description='Retrieve Plugin records'), policy.RuleDefault('iot:plugin_remove:delete', 'rule:admin_or_owner', description='Delete Plugin records'), policy.RuleDefault('iot:plugin_action:post', 'rule:admin_or_owner', description='Create Plugin records'), policy.RuleDefault('iot:plugin_inject:put', 'rule:admin_or_owner', description='Retrieve a Plugin record'), ] service_policies = [ policy.RuleDefault('iot:service:get', 'rule:is_admin or rule:is_iot_member', description='Retrieve Service records'), policy.RuleDefault('iot:service:create', 'rule:is_iot_member', description='Create Service records'), policy.RuleDefault('iot:service:get_one', 'rule:admin_or_owner', description='Retrieve a Service record'), policy.RuleDefault('iot:service:delete', 'rule:admin_or_owner', description='Delete Service records'), policy.RuleDefault('iot:service:update', 'rule:admin_or_owner', description='Update Service records'), ] exposed_service_policies = [ policy.RuleDefault('iot:service_on_board:get', 'rule:admin_or_owner', description='Retrieve Service records'), policy.RuleDefault('iot:service_remove:delete', 'rule:admin_or_owner', description='Delete Service records'), policy.RuleDefault('iot:service_action:post', 'rule:admin_or_owner', description='Create Service records'), policy.RuleDefault('iot:service_inject:put', 'rule:admin_or_owner', description='Retrieve a Service record'), ] def list_policies(): policies = (default_policies + board_policies + plugin_policies + injection_plugin_policies + service_policies + exposed_service_policies ) return policies @lockutils.synchronized('policy_enforcer') def init_enforcer(policy_file=None, rules=None, default_rule=None, use_conf=True): """Synchronously initializes the policy enforcer :param policy_file: Custom policy file to use, if none is specified, `CONF.oslo_policy.policy_file` will be used. :param rules: Default dictionary / Rules to use. It will be considered just in the first instantiation. :param default_rule: Default rule to use, CONF.oslo_policy.policy_default_rule will be used if none is specified. :param use_conf: Whether to load rules from config file. """ global _ENFORCER if _ENFORCER: return # NOTE(deva): Register defaults for policy-in-code here so that they are # loaded exactly once - when this module-global is initialized. # Defining these in the relevant API modules won't work # because API classes lack singletons and don't use globals. _ENFORCER = policy.Enforcer(CONF, policy_file=policy_file, rules=rules, default_rule=default_rule, use_conf=use_conf) _ENFORCER.register_defaults(list_policies()) def get_enforcer(): """Provides access to the single instance of Policy enforcer.""" if not _ENFORCER: init_enforcer() return _ENFORCER def get_oslo_policy_enforcer(): # This method is for use by oslopolicy CLI scripts. Those scripts need the # 'output-file' and 'namespace' options, but having those in sys.argv means # loading the Ironic config options will fail as those are not expected to # be present. So we pass in an arg list with those stripped out. conf_args = [] # Start at 1 because cfg.CONF expects the equivalent of sys.argv[1:] i = 1 while i < len(sys.argv): if sys.argv[i].strip('-') in ['namespace', 'output-file']: i += 2 continue conf_args.append(sys.argv[i]) i += 1 cfg.CONF(conf_args, project='ironic') return get_enforcer() # NOTE(deva): We can't call these methods from within decorators because the # 'target' and 'creds' parameter must be fetched from the call time # context-local pecan.request magic variable, but decorators are compiled # at module-load time. def authorize(rule, target, creds, *args, **kwargs): """A shortcut for policy.Enforcer.authorize() Checks authorization of a rule against the target and credentials, and raises an exception if the rule is not defined. Always returns true if CONF.auth_strategy == noauth. Beginning with the Newton cycle, this should be used in place of 'enforce'. """ if CONF.auth_strategy == 'noauth': return True enforcer = get_enforcer() try: return enforcer.authorize(rule, target, creds, do_raise=True, *args, **kwargs) except policy.PolicyNotAuthorized: raise exception.HTTPForbidden(resource=rule) def check(rule, target, creds, *args, **kwargs): """A shortcut for policy.Enforcer.enforce() Checks authorization of a rule against the target and credentials and returns True or False. """ enforcer = get_enforcer() return enforcer.enforce(rule, target, creds, *args, **kwargs) def enforce(rule, target, creds, do_raise=False, exc=None, *args, **kwargs): """A shortcut for policy.Enforcer.enforce() Checks authorization of a rule against the target and credentials. Always returns true if CONF.auth_strategy == noauth. """ # NOTE(deva): this method is obsoleted by authorize(), but retained for # backwards compatibility in case it has been used downstream. # It may be removed in the Pike cycle. LOG.warning(_LW( "Deprecation warning: calls to ironic.common.policy.enforce() " "should be replaced with authorize(). This method may be removed " "in a future release.")) if CONF.auth_strategy == 'noauth': return True enforcer = get_enforcer() return enforcer.enforce(rule, target, creds, do_raise=do_raise, exc=exc, *args, **kwargs)