"Fossies" - the Fresh Open Source Software Archive

Member "glance-19.0.0/glance/common/property_utils.py" (16 Oct 2019, 9607 Bytes) of package /linux/misc/openstack/glance-19.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "property_utils.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 15.0.1_vs_16.0.0.

    1 #    Copyright 2013 Rackspace
    2 #
    3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 #    not use this file except in compliance with the License. You may obtain
    5 #    a copy of the License at
    6 #
    7 #         http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 #    Unless required by applicable law or agreed to in writing, software
   10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 #    License for the specific language governing permissions and limitations
   13 #    under the License.
   14 
   15 import re
   16 import sys
   17 
   18 from oslo_config import cfg
   19 from oslo_log import log as logging
   20 from oslo_policy import policy
   21 from six.moves import configparser
   22 
   23 import glance.api.policy
   24 from glance.common import exception
   25 from glance.i18n import _, _LE, _LW
   26 
   27 # SafeConfigParser was deprecated in Python 3.2
   28 if sys.version_info >= (3, 2):
   29     CONFIG = configparser.ConfigParser()
   30 else:
   31     CONFIG = configparser.SafeConfigParser()
   32 
   33 LOG = logging.getLogger(__name__)
   34 
   35 property_opts = [
   36     cfg.StrOpt('property_protection_file',
   37                help=_("""
   38 The location of the property protection file.
   39 
   40 Provide a valid path to the property protection file which contains
   41 the rules for property protections and the roles/policies associated
   42 with them.
   43 
   44 A property protection file, when set, restricts the Glance image
   45 properties to be created, read, updated and/or deleted by a specific
   46 set of users that are identified by either roles or policies.
   47 If this configuration option is not set, by default, property
   48 protections won't be enforced. If a value is specified and the file
   49 is not found, the glance-api service will fail to start.
   50 More information on property protections can be found at:
   51 https://docs.openstack.org/glance/latest/admin/property-protections.html
   52 
   53 Possible values:
   54     * Empty string
   55     * Valid path to the property protection configuration file
   56 
   57 Related options:
   58     * property_protection_rule_format
   59 
   60 """)),
   61     cfg.StrOpt('property_protection_rule_format',
   62                default='roles',
   63                choices=('roles', 'policies'),
   64                help=_("""
   65 Rule format for property protection.
   66 
   67 Provide the desired way to set property protection on Glance
   68 image properties. The two permissible values are ``roles``
   69 and ``policies``. The default value is ``roles``.
   70 
   71 If the value is ``roles``, the property protection file must
   72 contain a comma separated list of user roles indicating
   73 permissions for each of the CRUD operations on each property
   74 being protected. If set to ``policies``, a policy defined in
   75 policy.json is used to express property protections for each
   76 of the CRUD operations. Examples of how property protections
   77 are enforced based on ``roles`` or ``policies`` can be found at:
   78 https://docs.openstack.org/glance/latest/admin/property-protections.html#examples
   79 
   80 Possible values:
   81     * roles
   82     * policies
   83 
   84 Related options:
   85     * property_protection_file
   86 
   87 """)),
   88 ]
   89 
   90 CONF = cfg.CONF
   91 CONF.register_opts(property_opts)
   92 
   93 # NOTE (spredzy): Due to the particularly lengthy name of the exception
   94 # and the number of occurrence it is raise in this file, a variable is
   95 # created
   96 InvalidPropProtectConf = exception.InvalidPropertyProtectionConfiguration
   97 
   98 
   99 def is_property_protection_enabled():
  100     if CONF.property_protection_file:
  101         return True
  102     return False
  103 
  104 
  105 class PropertyRules(object):
  106 
  107     def __init__(self, policy_enforcer=None):
  108         self.rules = []
  109         self.prop_exp_mapping = {}
  110         self.policies = []
  111         self.policy_enforcer = policy_enforcer or glance.api.policy.Enforcer()
  112         self.prop_prot_rule_format = CONF.property_protection_rule_format
  113         self.prop_prot_rule_format = self.prop_prot_rule_format.lower()
  114         self._load_rules()
  115 
  116     def _load_rules(self):
  117         try:
  118             conf_file = CONF.find_file(CONF.property_protection_file)
  119             CONFIG.read(conf_file)
  120         except Exception as e:
  121             msg = (_LE("Couldn't find property protection file %(file)s: "
  122                        "%(error)s.") % {'file': CONF.property_protection_file,
  123                                         'error': e})
  124             LOG.error(msg)
  125             raise InvalidPropProtectConf()
  126 
  127         if self.prop_prot_rule_format not in ['policies', 'roles']:
  128             msg = _LE("Invalid value '%s' for "
  129                       "'property_protection_rule_format'. "
  130                       "The permitted values are "
  131                       "'roles' and 'policies'") % self.prop_prot_rule_format
  132             LOG.error(msg)
  133             raise InvalidPropProtectConf()
  134 
  135         operations = ['create', 'read', 'update', 'delete']
  136         properties = CONFIG.sections()
  137         for property_exp in properties:
  138             property_dict = {}
  139             compiled_rule = self._compile_rule(property_exp)
  140 
  141             for operation in operations:
  142                 permissions = CONFIG.get(property_exp, operation)
  143                 if permissions:
  144                     if self.prop_prot_rule_format == 'policies':
  145                         if ',' in permissions:
  146                             LOG.error(
  147                                 _LE("Multiple policies '%s' not allowed "
  148                                     "for a given operation. Policies can be "
  149                                     "combined in the policy file"),
  150                                 permissions)
  151                             raise InvalidPropProtectConf()
  152                         self.prop_exp_mapping[compiled_rule] = property_exp
  153                         self._add_policy_rules(property_exp, operation,
  154                                                permissions)
  155                         permissions = [permissions]
  156                     else:
  157                         permissions = [permission.strip() for permission in
  158                                        permissions.split(',')]
  159                         if '@' in permissions and '!' in permissions:
  160                             msg = (_LE(
  161                                 "Malformed property protection rule in "
  162                                 "[%(prop)s] %(op)s=%(perm)s: '@' and '!' "
  163                                 "are mutually exclusive") %
  164                                 dict(prop=property_exp,
  165                                      op=operation,
  166                                      perm=permissions))
  167                             LOG.error(msg)
  168                             raise InvalidPropProtectConf()
  169                     property_dict[operation] = permissions
  170                 else:
  171                     property_dict[operation] = []
  172                     LOG.warn(
  173                         _LW('Property protection on operation %(operation)s'
  174                             ' for rule %(rule)s is not found. No role will be'
  175                             ' allowed to perform this operation.') %
  176                         {'operation': operation,
  177                          'rule': property_exp})
  178 
  179             self.rules.append((compiled_rule, property_dict))
  180 
  181     def _compile_rule(self, rule):
  182         try:
  183             return re.compile(rule)
  184         except Exception as e:
  185             msg = (_LE("Encountered a malformed property protection rule"
  186                        " %(rule)s: %(error)s.") % {'rule': rule,
  187                                                    'error': e})
  188             LOG.error(msg)
  189             raise InvalidPropProtectConf()
  190 
  191     def _add_policy_rules(self, property_exp, action, rule):
  192         """Add policy rules to the policy enforcer.
  193 
  194         For example, if the file listed as property_protection_file has:
  195         [prop_a]
  196         create = glance_creator
  197         then the corresponding policy rule would be:
  198         "prop_a:create": "rule:glance_creator"
  199         where glance_creator is defined in policy.json. For example:
  200         "glance_creator": "role:admin or role:glance_create_user"
  201         """
  202         rule = "rule:%s" % rule
  203         rule_name = "%s:%s" % (property_exp, action)
  204         rule_dict = policy.Rules.from_dict({
  205             rule_name: rule
  206         })
  207         self.policy_enforcer.add_rules(rule_dict)
  208 
  209     def _check_policy(self, property_exp, action, context):
  210         try:
  211             action = ":".join([property_exp, action])
  212             self.policy_enforcer.enforce(context, action, {})
  213         except exception.Forbidden:
  214             return False
  215         return True
  216 
  217     def check_property_rules(self, property_name, action, context):
  218         roles = context.roles
  219 
  220         # Include service roles to check if an action can be
  221         # performed on the property or not
  222         if context.service_roles:
  223             roles.extend(context.service_roles)
  224         if not self.rules:
  225             return True
  226 
  227         if action not in ['create', 'read', 'update', 'delete']:
  228             return False
  229 
  230         for rule_exp, rule in self.rules:
  231             if rule_exp.search(str(property_name)):
  232                 break
  233         else:  # no matching rules
  234             return False
  235 
  236         rule_roles = rule.get(action)
  237         if rule_roles:
  238             if '!' in rule_roles:
  239                 return False
  240             elif '@' in rule_roles:
  241                 return True
  242             if self.prop_prot_rule_format == 'policies':
  243                 prop_exp_key = self.prop_exp_mapping[rule_exp]
  244                 return self._check_policy(prop_exp_key, action,
  245                                           context)
  246             if set(roles).intersection(set([role.lower() for role
  247                                             in rule_roles])):
  248                 return True
  249         return False