Sync policy module from oslo-incubator

The policy module has not had a sync since back in
November.  There have been a number of changes that
should be pulled into Kilo.

Current HEAD in OSLO:
---------------------
commit 9bf01f9d98487cb13e3d95ad2a580fe8fc6f2479
Date:  Fri Feb 13 14:18:58 2015 -0500
Switch from oslo.config to oslo_config

Changes merged with this patch:
---------------------
2aacb111 Change oslo.config to oslo_config
2fbf5065 Remove oslo.log code and clean up versionutils API
262279b1 switch to oslo_serialization
07e9b32a Improving docstrings for policy API
e67f5cd0 Merge "Don't log missing policy.d as a warning"
99d991ce Merge "Fixed a problem with neutron http policy check"
b19af080 Don't log missing policy.d as a warning
2324c775 Add rule overwrite flag to Enforcer class
6166a960 Fixed a problem with neutron http policy check

Closes-bug: 1288178
Change-Id: I6987029b9c15f3d35fa591014859f5f96c98f3a3
This commit is contained in:
Jay S. Bryant 2015-02-16 17:05:01 -06:00
parent 7cd5fe6bb5
commit 1c28ead900
1 changed files with 61 additions and 34 deletions

View File

@ -1,3 +1,5 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 OpenStack Foundation.
# All Rights Reserved.
#
@ -22,22 +24,43 @@ string written in the new policy language.
In the list-of-lists representation, each check inside the innermost
list is combined as with an "and" conjunction--for that check to pass,
all the specified checks must pass. These innermost lists are then
combined as with an "or" conjunction. This is the original way of
expressing policies, but there now exists a new way: the policy
language.
In the policy language, each check is specified the same way as in the
list-of-lists representation: a simple "a:b" pair that is matched to
the correct code to perform that check. However, conjunction
operators are available, allowing for more expressiveness in crafting
policies.
As an example, take the following rule, expressed in the list-of-lists
representation::
combined as with an "or" conjunction. As an example, take the following
rule, expressed in the list-of-lists representation::
[["role:admin"], ["project_id:%(project_id)s", "role:projectadmin"]]
In the policy language, this becomes::
This is the original way of expressing policies, but there now exists a
new way: the policy language.
In the policy language, each check is specified the same way as in the
list-of-lists representation: a simple "a:b" pair that is matched to
the correct class to perform that check::
+===========================================================================+
| TYPE | SYNTAX |
+===========================================================================+
|User's Role | role:admin |
+---------------------------------------------------------------------------+
|Rules already defined on policy | rule:admin_required |
+---------------------------------------------------------------------------+
|Against URL's¹ | http://my-url.org/check |
+---------------------------------------------------------------------------+
|User attributes² | project_id:%(target.project.id)s |
+---------------------------------------------------------------------------+
|Strings | <variable>:'xpto2035abc' |
| | 'myproject':<variable> |
+---------------------------------------------------------------------------+
| | project_id:xpto2035abc |
|Literals | domain_id:20 |
| | True:%(user.enabled)s |
+===========================================================================+
¹URL checking must return 'True' to be valid
²User attributes (obtained through the token): user_id, domain_id or project_id
Conjunction operators are available, allowing for more expressiveness
in crafting policies. So, in the policy language, the previous check in
list-of-lists becomes::
role:admin or (project_id:%(project_id)s and role:projectadmin)
@ -46,26 +69,16 @@ policy rule::
project_id:%(project_id)s and not role:dunce
It is possible to perform policy checks on the following user
attributes (obtained through the token): user_id, domain_id or
project_id::
domain_id:<some_value>
Attributes sent along with API calls can be used by the policy engine
(on the right side of the expression), by using the following syntax::
<some_value>:user.id
<some_value>:%(user.id)s
Contextual attributes of objects identified by their IDs are loaded
from the database. They are also available to the policy engine and
can be checked through the `target` keyword::
<some_value>:target.role.name
All these attributes (related to users, API calls, and context) can be
checked against each other or against constants, be it literals (True,
<a_number>) or strings.
<some_value>:%(target.role.name)s
Finally, two special policy checks should be mentioned; the policy
check "@" will always accept an access, and the policy check "!" will
@ -78,6 +91,7 @@ as it allows particular rules to be explicitly disabled.
import abc
import ast
import copy
import logging
import os
import re
@ -88,8 +102,7 @@ import six.moves.urllib.parse as urlparse
import six.moves.urllib.request as urlrequest
from cinder.openstack.common import fileutils
from cinder.openstack.common._i18n import _, _LE, _LW
from cinder.openstack.common import log as logging
from cinder.openstack.common._i18n import _, _LE, _LI
policy_opts = [
@ -199,16 +212,19 @@ class Enforcer(object):
:param default_rule: Default rule to use, CONF.default_rule will
be used if none is specified.
:param use_conf: Whether to load rules from cache or config file.
:param overwrite: Whether to overwrite existing rules when reload rules
from config file.
"""
def __init__(self, policy_file=None, rules=None,
default_rule=None, use_conf=True):
default_rule=None, use_conf=True, overwrite=True):
self.default_rule = default_rule or CONF.policy_default_rule
self.rules = Rules(rules, self.default_rule)
self.policy_path = None
self.policy_file = policy_file or CONF.policy_file
self.use_conf = use_conf
self.overwrite = overwrite
def set_rules(self, rules, overwrite=True, use_conf=False):
"""Create a new Rules object based on the provided dict of rules.
@ -240,7 +256,7 @@ class Enforcer(object):
Policy file is cached and will be reloaded if modified.
:param force_reload: Whether to overwrite current rules.
:param force_reload: Whether to reload rules from config file.
"""
if force_reload:
@ -250,12 +266,13 @@ class Enforcer(object):
if not self.policy_path:
self.policy_path = self._get_policy_path(self.policy_file)
self._load_policy_file(self.policy_path, force_reload)
self._load_policy_file(self.policy_path, force_reload,
overwrite=self.overwrite)
for path in CONF.policy_dirs:
try:
path = self._get_policy_path(path)
except cfg.ConfigFilesNotFoundError:
LOG.warn(_LW("Can not find policy directory: %s"), path)
LOG.info(_LI("Can not find policy directory: %s"), path)
continue
self._walk_through_policy_directory(path,
self._load_policy_file,
@ -272,9 +289,9 @@ class Enforcer(object):
def _load_policy_file(self, path, force_reload, overwrite=True):
reloaded, data = fileutils.read_cached_file(
path, force_reload=force_reload)
if reloaded or not self.rules:
if reloaded or not self.rules or not overwrite:
rules = Rules.load_json(data, self.default_rule)
self.set_rules(rules, overwrite)
self.set_rules(rules, overwrite=overwrite, use_conf=True)
LOG.debug("Rules successfully reloaded")
def _get_policy_path(self, path):
@ -894,7 +911,17 @@ class HttpCheck(Check):
"""
url = ('http:' + self.match) % target
data = {'target': jsonutils.dumps(target),
# Convert instances of object() in target temporarily to
# empty dict to avoid circular reference detection
# errors in jsonutils.dumps().
temp_target = copy.deepcopy(target)
for key in target.keys():
element = target.get(key)
if type(element) is object:
temp_target[key] = {}
data = {'target': jsonutils.dumps(temp_target),
'credentials': jsonutils.dumps(creds)}
post_data = urlparse.urlencode(data)
f = urlrequest.urlopen(url, post_data)