Merge branch 'master' of git.corp.cloudwatt.com:nassim.babaci/swiftpolicy
This commit is contained in:
commit
201c42af71
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.egg-info
|
||||
*.pyc
|
||||
.idea
|
1
setup.py
1
setup.py
@ -26,5 +26,6 @@ setup(name='swiftpolicy',
|
||||
url='https://git.corp.cloudwatt.com/nassim.babaci/swiftpolicy',
|
||||
packages=['swiftpolicy'],
|
||||
requires=['swift(>=1.7)'],
|
||||
test_suite='tests',
|
||||
entry_points={'paste.filter_factory':
|
||||
['swiftpolicy=swiftpolicy.swiftpolicy:filter_factory']})
|
||||
|
@ -12,30 +12,26 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from policy import register
|
||||
from policy import Enforcer
|
||||
from policy import Check
|
||||
from policy import Rules
|
||||
|
||||
from openstack.common import policy_parser as parser
|
||||
|
||||
def get_enforcer(operators_roles, reseller_role, is_admin, logger, policy_file=None):
|
||||
swift_operators = [role.strip()
|
||||
for role in operators_roles.split(',')]
|
||||
parser.registry.register('logger', logger)
|
||||
if policy_file:
|
||||
return FileBasedEnforcer(policy_file, logger=logger)
|
||||
else:
|
||||
return DefaultEnforcer(swift_operators, reseller_role, is_admin, logger=logger)
|
||||
|
||||
|
||||
class DefaultEnforcer(Enforcer):
|
||||
def __init__(self, swift_operator, swift_reseller, is_admin=False, logger=None):
|
||||
class DefaultEnforcer(object):
|
||||
def __init__(self, swift_operator, swift_reseller, is_admin=False):
|
||||
super(DefaultEnforcer, self).__init__(policy_file=None, rules=None,
|
||||
default_rule=None)
|
||||
|
||||
self.swift_operator = swift_operator
|
||||
self.swift_reseller = swift_reseller
|
||||
self.is_admin = is_admin
|
||||
self.log = logger
|
||||
|
||||
def _get_policy(self):
|
||||
param = {
|
||||
@ -54,15 +50,14 @@ class DefaultEnforcer(Enforcer):
|
||||
def load_rules(self, force_reload=False):
|
||||
#import pdb; pdb.set_trace()
|
||||
policy = self._get_policy()
|
||||
rules = Rules.load_json(policy, self.default_rule)
|
||||
rules = parser.Rules.load_json(policy, self.default_rule)
|
||||
self.set_rules(rules)
|
||||
|
||||
class FileBasedEnforcer(Enforcer):
|
||||
def __init__(self, policy_file, logger):
|
||||
class FileBasedEnforcer(object):
|
||||
def __init__(self, policy_file):
|
||||
super(FileBasedEnforcer, self).__init__(policy_file=None, rules=None,
|
||||
default_rule=None)
|
||||
self.policy_file = policy_file
|
||||
self.log = logger
|
||||
|
||||
def _get_policy(self):
|
||||
with open(self.policy_file, 'r') as policies:
|
||||
@ -74,15 +69,15 @@ class FileBasedEnforcer(Enforcer):
|
||||
#import pdb; pdb.set_trace()
|
||||
policy = self._get_policy()
|
||||
try:
|
||||
rules = Rules.load_json(policy, self.default_rule)
|
||||
rules = parser.Rules.load_json(policy, self.default_rule)
|
||||
#TODO error is not used
|
||||
except ValueError as error:
|
||||
raise
|
||||
self.set_rules(rules)
|
||||
|
||||
|
||||
@register("acl")
|
||||
class AclCheck(Check):
|
||||
@parser.register("acl")
|
||||
class AclCheck(parser.Check):
|
||||
@staticmethod
|
||||
def _authorize_cross_tenant(user_id, user_name,
|
||||
tenant_id, tenant_name, acls):
|
||||
|
1
swiftpolicy/openstack/__init__.py
Normal file
1
swiftpolicy/openstack/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
1
swiftpolicy/openstack/common/__init__.py
Normal file
1
swiftpolicy/openstack/common/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Copyright (c) 2012 OpenStack Foundation.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -17,11 +16,6 @@
|
||||
"""
|
||||
Common Policy Engine Implementation
|
||||
|
||||
Based on:
|
||||
https://github.com/openstack/oslo-incubator/blob/master
|
||||
/openstack/common/policy.py
|
||||
and adapted to remove dependency to oslo.cfg.
|
||||
|
||||
Policies can be expressed in one of two forms: A list of lists, or a
|
||||
string written in the new policy language.
|
||||
|
||||
@ -83,17 +77,36 @@ as it allows particular rules to be explicitly disabled.
|
||||
|
||||
import abc
|
||||
import ast
|
||||
import gettext
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
import six
|
||||
import six.moves.urllib.parse as urlparse
|
||||
import six.moves.urllib.request as urlrequest
|
||||
|
||||
from swift import gettext_ as _
|
||||
import json
|
||||
|
||||
class Registry(object):
|
||||
components = {
|
||||
"rule_formatter": json,
|
||||
"trans": gettext.gettext,
|
||||
"trans_error": gettext.gettext,
|
||||
"logger": logging.getLogger(__name__)
|
||||
}
|
||||
|
||||
_checks = {}
|
||||
def register(self, name, obj):
|
||||
if name in self.components:
|
||||
self.components[name] = obj
|
||||
|
||||
def get(self, name):
|
||||
return self.components.get(name, None)
|
||||
|
||||
registry = Registry()
|
||||
|
||||
# set global components.
|
||||
rule_formatter = registry.get('rule_formatter')
|
||||
_, _LE = registry.get('trans'), registry.get('trans_error')
|
||||
log = registry.get('logger')
|
||||
|
||||
|
||||
class PolicyNotAuthorized(Exception):
|
||||
@ -112,7 +125,7 @@ class Rules(dict):
|
||||
|
||||
# Suck in the JSON data and parse the rules
|
||||
rules = dict((k, parse_rule(v)) for k, v in
|
||||
json.loads(data).items())
|
||||
rule_formatter.loads(data).items())
|
||||
|
||||
return cls(rules, default_rule)
|
||||
|
||||
@ -156,116 +169,7 @@ class Rules(dict):
|
||||
out_rules[key] = str(value)
|
||||
|
||||
# Dump a pretty-printed JSON representation
|
||||
return json.dumps(out_rules, indent=4)
|
||||
|
||||
|
||||
class Enforcer(object):
|
||||
"""Responsible for loading and enforcing rules.
|
||||
|
||||
:param policy_file: Custom policy file to use, if none is
|
||||
specified, `CONF.policy_file` will be
|
||||
used.
|
||||
:param rules: Default dictionary / Rules to use. It will be
|
||||
considered just in the first instantiation. If
|
||||
`load_rules(True)`, `clear()` or `set_rules(True)`
|
||||
is called this will be overwritten.
|
||||
:param default_rule: Default rule to use, CONF.default_rule will
|
||||
be used if none is specified.
|
||||
"""
|
||||
|
||||
def __init__(self, policy_file=None, rules=None,
|
||||
default_rule=None):
|
||||
self.rules = Rules(rules, default_rule)
|
||||
self.default_rule = default_rule
|
||||
|
||||
self.policy_path = None
|
||||
self.policy_file = policy_file
|
||||
|
||||
def set_rules(self, rules, overwrite=True):
|
||||
"""Create a new Rules object based on the provided dict of rules.
|
||||
|
||||
:param rules: New rules to use. It should be an instance of dict.
|
||||
:param overwrite: Whether to overwrite current rules or update them
|
||||
with the new rules.
|
||||
:param use_conf: Whether to reload rules from cache or config file.
|
||||
"""
|
||||
|
||||
if not isinstance(rules, dict):
|
||||
raise TypeError(_("Rules must be an instance of dict or Rules, "
|
||||
"got %s instead") % type(rules))
|
||||
if overwrite:
|
||||
self.rules = Rules(rules, self.default_rule)
|
||||
else:
|
||||
self.rules.update(rules)
|
||||
|
||||
def clear(self):
|
||||
"""Clears Enforcer rules, policy's cache and policy's path."""
|
||||
self.set_rules({})
|
||||
self.default_rule = None
|
||||
self.policy_path = None
|
||||
|
||||
def load_rules(self, force_reload=False):
|
||||
"""Loads policy_path's rules. """
|
||||
raise NotImplemented
|
||||
|
||||
def _get_policy_path(self):
|
||||
"""Locate the policy json data file."""
|
||||
raise NotImplemented
|
||||
|
||||
def enforce(self, rule, target, creds, do_raise=False,
|
||||
exc=None, *args, **kwargs):
|
||||
"""Checks authorization of a rule against the target and credentials.
|
||||
|
||||
:param rule: A string or BaseCheck instance specifying the rule
|
||||
to evaluate.
|
||||
:param target: As much information about the object being operated
|
||||
on as possible, as a dictionary.
|
||||
:param creds: As much information about the user performing the
|
||||
action as possible, as a dictionary.
|
||||
:param do_raise: Whether to raise an exception or not if check
|
||||
fails.
|
||||
:param exc: Class of the exception to raise if the check fails.
|
||||
Any remaining arguments passed to check() (both
|
||||
positional and keyword arguments) will be passed to
|
||||
the exception class. If not specified, PolicyNotAuthorized
|
||||
will be used.
|
||||
|
||||
:return: Returns False if the policy does not allow the action and
|
||||
exc is not provided; otherwise, returns a value that
|
||||
evaluates to True. Note: for rules using the "case"
|
||||
expression, this True value will be the specified string
|
||||
from the expression.
|
||||
"""
|
||||
|
||||
# NOTE(flaper87): Not logging target or creds to avoid
|
||||
# potential security issues.
|
||||
#LOG.debug("Rule %s will be now enforced" % rule)
|
||||
|
||||
self.load_rules()
|
||||
|
||||
# Allow the rule to be a Check tree
|
||||
if isinstance(rule, BaseCheck):
|
||||
result = rule(target, creds, self)
|
||||
elif not self.rules:
|
||||
# No rules to reference means we're going to fail closed
|
||||
result = False
|
||||
else:
|
||||
try:
|
||||
# Evaluate the rule
|
||||
result = self.rules[rule](target, creds, self)
|
||||
except KeyError:
|
||||
#LOG.debug("Rule [%s] doesn't exist" % rule)
|
||||
# If the rule doesn't exist, fail closed
|
||||
result = False
|
||||
|
||||
# If it is False, raise the exception if requested
|
||||
if do_raise and not result:
|
||||
if exc:
|
||||
raise exc(*args, **kwargs)
|
||||
|
||||
raise PolicyNotAuthorized(rule)
|
||||
|
||||
return result
|
||||
return rule_formatter.dumps(out_rules, indent=4)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
@ -449,6 +353,8 @@ class OrCheck(BaseCheck):
|
||||
self.rules.append(rule)
|
||||
return self
|
||||
|
||||
_checks = {}
|
||||
|
||||
|
||||
def _parse_check(rule):
|
||||
"""Parse a single base check rule into an appropriate Check object."""
|
||||
@ -462,7 +368,7 @@ def _parse_check(rule):
|
||||
try:
|
||||
kind, match = rule.split(':', 1)
|
||||
except Exception:
|
||||
#LOG.exception(_LE("Failed to understand rule %s") % rule)
|
||||
log.exception(_LE("Failed to understand rule %s") % rule)
|
||||
# If the rule is invalid, we'll fail closed
|
||||
return FalseCheck()
|
||||
|
||||
@ -472,7 +378,7 @@ def _parse_check(rule):
|
||||
elif None in _checks:
|
||||
return _checks[None](kind, match)
|
||||
else:
|
||||
#LOG.error(_LE("No handler for matches of kind %s") % kind)
|
||||
log.error(_LE("No handler for matches of kind %s") % kind)
|
||||
return FalseCheck()
|
||||
|
||||
|
||||
@ -742,7 +648,7 @@ def _parse_text_rule(rule):
|
||||
return state.result
|
||||
except ValueError:
|
||||
# Couldn't parse the rule
|
||||
#LOG.exception(_LE("Failed to understand rule %r") % rule)
|
||||
log.exception(_LE("Failed to understand rule %r") % rule)
|
||||
|
||||
# Fail closed
|
||||
return FalseCheck()
|
||||
@ -789,9 +695,7 @@ class RuleCheck(Check):
|
||||
"""Recursively checks credentials based on the defined rules."""
|
||||
|
||||
try:
|
||||
result = enforcer.rules[self.match](target, creds, enforcer)
|
||||
enforcer.log.debug("Rule '%s' evaluated to %s" % (self.match, result))
|
||||
return result
|
||||
return enforcer.rules[self.match](target, creds, enforcer)
|
||||
except KeyError:
|
||||
# We don't have any matching rule; fail closed
|
||||
return False
|
||||
@ -815,8 +719,8 @@ class HttpCheck(Check):
|
||||
"""
|
||||
|
||||
url = ('http:' + self.match) % target
|
||||
data = {'target': json.dumps(target),
|
||||
'credentials': json.dumps(creds)}
|
||||
data = {'target': rule_formatter.dumps(target),
|
||||
'credentials': rule_formatter.dumps(creds)}
|
||||
post_data = urlparse.urlencode(data)
|
||||
f = urlrequest.urlopen(url, post_data)
|
||||
return f.read() == "True"
|
@ -21,7 +21,8 @@ from enforcer import get_enforcer
|
||||
|
||||
|
||||
class SwiftPolicy(object):
|
||||
"""Swift middleware to Keystone authorization system.
|
||||
"""Swift middleware to handle Keystone authorization based
|
||||
openstack policy.json format
|
||||
|
||||
In Swift's proxy-server.conf add this middleware to your pipeline::
|
||||
|
||||
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
Loading…
x
Reference in New Issue
Block a user