Use openstack-common's policy module

Reworks glance to use the new policy module in openstack-common.

Change-Id: I6012c71a16d83762535e7490d8f5bc5102160fc0
This commit is contained in:
Kevin L. Mitchell 2012-06-06 10:05:17 -05:00
parent fc758a46e7
commit c07be927fb
3 changed files with 100 additions and 46 deletions

View File

@ -22,8 +22,8 @@ import logging
import os.path import os.path
from glance.common import exception from glance.common import exception
from glance.common import policy
from glance.openstack.common import cfg from glance.openstack.common import cfg
from glance.openstack.common import policy
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -99,7 +99,5 @@ class Enforcer(object):
'tenant': context.tenant, 'tenant': context.tenant,
} }
try: policy.enforce(match_list, target, credentials,
policy.enforce(match_list, target, credentials) exception.Forbidden, action=action)
except policy.NotAuthorized:
raise exception.Forbidden(action=action)

View File

@ -18,10 +18,12 @@
"""Common Policy Engine Implementation""" """Common Policy Engine Implementation"""
import json import json
import logging
import urllib
import urllib2
class NotAuthorized(Exception): LOG = logging.getLogger(__name__)
pass
_BRAIN = None _BRAIN = None
@ -43,30 +45,45 @@ def reset():
_BRAIN = None _BRAIN = None
def enforce(match_list, target_dict, credentials_dict): def enforce(match_list, target_dict, credentials_dict, exc=None,
*args, **kwargs):
"""Enforces authorization of some rules against credentials. """Enforces authorization of some rules against credentials.
:param match_list: nested tuples of data to match against :param match_list: nested tuples of data to match against
The basic brain supports three types of match lists: The basic brain supports three types of match lists:
1) rules 1) rules
looks like: ('rule:compute:get_instance',)
looks like: ``('rule:compute:get_instance',)``
Retrieves the named rule from the rules dict and recursively Retrieves the named rule from the rules dict and recursively
checks against the contents of the rule. checks against the contents of the rule.
2) roles 2) roles
looks like: ('role:compute:admin',)
looks like: ``('role:compute:admin',)``
Matches if the specified role is in credentials_dict['roles']. Matches if the specified role is in credentials_dict['roles'].
3) generic 3) generic
('tenant_id:%(tenant_id)s',)
looks like: ``('tenant_id:%(tenant_id)s',)``
Substitutes values from the target dict into the match using Substitutes values from the target dict into the match using
the % operator and matches them against the creds dict. the % operator and matches them against the creds dict.
Combining rules: Combining rules:
The brain returns True if any of the outer tuple of rules match
and also True if all of the inner tuples match. You can use this to The brain returns True if any of the outer tuple of rules
perform simple boolean logic. For example, the following rule would match and also True if all of the inner tuples match. You
return True if the creds contain the role 'admin' OR the if the can use this to perform simple boolean logic. For
tenant_id matches the target dict AND the the creds contains the example, the following rule would return True if the creds
role 'compute_sysadmin': contain the role 'admin' OR the if the tenant_id matches
the target dict AND the the creds contains the role
'compute_sysadmin':
::
{ {
"rule:combined": ( "rule:combined": (
@ -75,28 +92,39 @@ def enforce(match_list, target_dict, credentials_dict):
) )
} }
Note that rule and role are reserved words in the credentials match, so Note that rule and role are reserved words in the credentials match, so
you can't match against properties with those names. Custom brains may you can't match against properties with those names. Custom brains may
also add new reserved words. For example, the HttpBrain adds http as a also add new reserved words. For example, the HttpBrain adds http as a
reserved word. reserved word.
:param target_dict: dict of object properties :param target_dict: dict of object properties
Target dicts contain as much information as we can about the object being Target dicts contain as much information as we can about the object being
operated on. operated on.
:param credentials_dict: dict of actor properties :param credentials_dict: dict of actor properties
Credentials dicts contain as much information as we can about the user Credentials dicts contain as much information as we can about the user
performing the action. performing the action.
:raises NotAuthorized if the check fails :param exc: exception to raise
Class of the exception to raise if the check fails. Any remaining
arguments passed to enforce() (both positional and keyword arguments)
will be passed to the exception class. If exc is not provided, returns
False.
:return: True if the policy allows the action
:return: False if the policy does not allow the action and exc is not set
""" """
global _BRAIN global _BRAIN
if not _BRAIN: if not _BRAIN:
_BRAIN = Brain() _BRAIN = Brain()
if not _BRAIN.check(match_list, target_dict, credentials_dict): if not _BRAIN.check(match_list, target_dict, credentials_dict):
raise NotAuthorized() if exc:
raise exc(*args, **kwargs)
return False
return True
class Brain(object): class Brain(object):
@ -115,7 +143,12 @@ class Brain(object):
self.rules[key] = match self.rules[key] = match
def _check(self, match, target_dict, cred_dict): def _check(self, match, target_dict, cred_dict):
try:
match_kind, match_value = match.split(':', 1) match_kind, match_value = match.split(':', 1)
except Exception:
LOG.exception(_("Failed to understand rule %(match)r") % locals())
# If the rule is invalid, fail closed
return False
try: try:
f = getattr(self, '_check_%s' % match_kind) f = getattr(self, '_check_%s' % match_kind)
except AttributeError: except AttributeError:
@ -162,7 +195,7 @@ class Brain(object):
def _check_role(self, match, target_dict, cred_dict): def _check_role(self, match, target_dict, cred_dict):
"""Check that there is a matching role in the cred dict.""" """Check that there is a matching role in the cred dict."""
return match in cred_dict['roles'] return match.lower() in [x.lower() for x in cred_dict['roles']]
def _check_generic(self, match, target_dict, cred_dict): def _check_generic(self, match, target_dict, cred_dict):
"""Check an individual match. """Check an individual match.
@ -180,3 +213,26 @@ class Brain(object):
if key in cred_dict: if key in cred_dict:
return value == cred_dict[key] return value == cred_dict[key]
return False return False
class HttpBrain(Brain):
"""A brain that can check external urls for policy.
Posts json blobs for target and credentials.
"""
def _check_http(self, match, target_dict, cred_dict):
"""Check http: rules by calling to a remote server.
This example implementation simply verifies that the response is
exactly 'True'. A custom brain using response codes could easily
be implemented.
"""
url = match % target_dict
data = {'target': json.dumps(target_dict),
'credentials': json.dumps(cred_dict)}
post_data = urllib.urlencode(data)
f = urllib2.urlopen(url, post_data)
return f.read() == "True"

View File

@ -1,7 +1,7 @@
[DEFAULT] [DEFAULT]
# The list of modules to copy from openstack-common # The list of modules to copy from openstack-common
modules=cfg,importutils,iniparser,setup,timeutils modules=cfg,importutils,iniparser,policy,setup,timeutils
# The base module to hold the copy of openstack.common # The base module to hold the copy of openstack.common
base=glance base=glance