keystone middleware integration
This commit is contained in:
parent
62496fe7f1
commit
5e415581a8
@ -16,3 +16,7 @@
|
||||
"""
|
||||
Cloudkeep's Barbican module root
|
||||
"""
|
||||
|
||||
import gettext
|
||||
|
||||
gettext.install('barbican', unicode=1)
|
||||
|
145
barbican/api/policy.py
Normal file
145
barbican/api/policy.py
Normal file
@ -0,0 +1,145 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2011 OpenStack, LLC.
|
||||
# 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 Glance"""
|
||||
|
||||
import json
|
||||
import os.path
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from barbican.common import exception
|
||||
import barbican.openstack.common.log as logging
|
||||
from barbican.openstack.common import policy
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
policy_opts = [
|
||||
cfg.StrOpt('policy_file', default='policy.json',
|
||||
help=_('The location of the policy file.')),
|
||||
cfg.StrOpt('policy_default_rule', default='default',
|
||||
help=_('The default policy to use.')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(policy_opts)
|
||||
|
||||
|
||||
DEFAULT_RULES = {
|
||||
'default': policy.TrueCheck(),
|
||||
}
|
||||
|
||||
|
||||
class Enforcer(object):
|
||||
"""Responsible for loading and enforcing rules"""
|
||||
|
||||
def __init__(self):
|
||||
self.default_rule = CONF.policy_default_rule
|
||||
self.policy_path = self._find_policy_file()
|
||||
self.policy_file_mtime = None
|
||||
self.policy_file_contents = None
|
||||
|
||||
def set_rules(self, rules):
|
||||
"""Create a new Rules object based on the provided dict of rules"""
|
||||
rules_obj = policy.Rules(rules, self.default_rule)
|
||||
policy.set_rules(rules_obj)
|
||||
|
||||
def load_rules(self):
|
||||
"""Set the rules found in the json file on disk"""
|
||||
if self.policy_path:
|
||||
rules = self._read_policy_file()
|
||||
rule_type = ""
|
||||
else:
|
||||
rules = DEFAULT_RULES
|
||||
rule_type = "default "
|
||||
|
||||
text_rules = dict((k, str(v)) for k, v in rules.items())
|
||||
LOG.debug(_('Loaded %(rule_type)spolicy rules: %(text_rules)s') %
|
||||
locals())
|
||||
|
||||
self.set_rules(rules)
|
||||
|
||||
@staticmethod
|
||||
def _find_policy_file():
|
||||
"""Locate the policy json data file"""
|
||||
policy_file = CONF.find_file(CONF.policy_file)
|
||||
if policy_file:
|
||||
return policy_file
|
||||
else:
|
||||
LOG.warn(_('Unable to find policy file'))
|
||||
return None
|
||||
|
||||
def _read_policy_file(self):
|
||||
"""Read contents of the policy file
|
||||
|
||||
This re-caches policy data if the file has been changed.
|
||||
"""
|
||||
mtime = os.path.getmtime(self.policy_path)
|
||||
if not self.policy_file_contents or mtime != self.policy_file_mtime:
|
||||
LOG.debug(_("Loading policy from %s") % self.policy_path)
|
||||
with open(self.policy_path) as fap:
|
||||
raw_contents = fap.read()
|
||||
rules_dict = json.loads(raw_contents)
|
||||
self.policy_file_contents = dict(
|
||||
(k, policy.parse_rule(v))
|
||||
for k, v in rules_dict.items())
|
||||
self.policy_file_mtime = mtime
|
||||
return self.policy_file_contents
|
||||
|
||||
def _check(self, context, rule, target, *args, **kwargs):
|
||||
"""Verifies that the action is valid on the target in this context.
|
||||
|
||||
:param context: Barbican request context
|
||||
:param rule: String representing the action to be checked
|
||||
:param object: Dictionary representing the object of the action.
|
||||
:raises: `barbican.common.exception.Forbidden`
|
||||
:returns: A non-False value if access is allowed.
|
||||
"""
|
||||
self.load_rules()
|
||||
|
||||
credentials = {
|
||||
'roles': context.roles,
|
||||
'user': context.user,
|
||||
'tenant': context.tenant,
|
||||
}
|
||||
|
||||
return policy.check(rule, target, credentials, *args, **kwargs)
|
||||
|
||||
def enforce(self, context, action, target):
|
||||
"""Verifies that the action is valid on the target in this context.
|
||||
|
||||
:param context: Barbican request context
|
||||
:param action: String representing the action to be checked
|
||||
:param object: Dictionary representing the object of the action.
|
||||
:raises: `barbican.common.exception.Forbidden`
|
||||
:returns: A non-False value if access is allowed.
|
||||
"""
|
||||
LOG.debug("== policy.enforce satisfied ==")
|
||||
return self._check(context, action, target,
|
||||
exception.Forbidden, action=action)
|
||||
|
||||
|
||||
def check(self, context, action, target):
|
||||
"""Verifies that the action is valid on the target in this context.
|
||||
|
||||
:param context: Barbican request context
|
||||
:param action: String representing the action to be checked
|
||||
:param object: Dictionary representing the object of the action.
|
||||
:returns: A non-False value if access is allowed.
|
||||
"""
|
||||
return self._check(context, action, target)
|
||||
|
@ -36,6 +36,7 @@ from barbican.common import utils
|
||||
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
PORT = 9311
|
||||
|
||||
def _tenant_not_found():
|
||||
abort(falcon.HTTP_404, 'Unable to locate tenant.')
|
||||
@ -62,7 +63,6 @@ class VersionResource(ApiResource):
|
||||
self.policy = policy_enforcer or policy.Enforcer()
|
||||
|
||||
def on_get(self, req, resp):
|
||||
LOG.debug('=== Authenticated and policy satisfied VersionResource ===')
|
||||
resp.status = falcon.HTTP_200
|
||||
resp.body = json.dumps({'v1': 'current',
|
||||
'build': __version__})
|
||||
@ -141,8 +141,9 @@ class SecretsResource(ApiResource):
|
||||
resp.set_header('Location', '/{0}/secrets/{1}'.format(tenant_id,
|
||||
new_secret.id))
|
||||
#TODO: Generate URL...Use .format() approach here too
|
||||
url = 'http://localhost:8080/{0}/secrets/{1}'.format(tenant_id,
|
||||
new_secret.id)
|
||||
url = 'http://localhost:{0}/{1}/secrets/{2}'.format(PORT,
|
||||
TENANT_id,
|
||||
new_secret.id)
|
||||
LOG.debug('URI to secret is {0}'.format(url))
|
||||
resp.body = json.dumps({'ref': url})
|
||||
|
||||
@ -208,7 +209,7 @@ class OrdersResource(ApiResource):
|
||||
new_order = Order()
|
||||
new_order.secret_name = body['secret_name']
|
||||
new_order.secret_mime_type = body['secret_mime_type']
|
||||
#TODO: new_order.secret_expiration = body['secret_expiration']
|
||||
#TODO: new_order.secret_expiration = body['secret_expiration']
|
||||
new_order.tenant_id = tenant.id
|
||||
self.order_repo.create_from(new_order)
|
||||
|
||||
@ -219,8 +220,9 @@ class OrdersResource(ApiResource):
|
||||
resp.set_header('Location', '/{0}/orders/{1}'.format(tenant_id,
|
||||
new_order.id))
|
||||
#TODO: Generate URL...
|
||||
url = 'http://localhost:8080/{0}/orders/{1}'.format(tenant_id,
|
||||
new_order.id)
|
||||
url = 'http://localhost:{0}/{1}/orders/{2}'.format(PORT,
|
||||
tenant_id,
|
||||
new_order.id)
|
||||
resp.body = json.dumps({'ref': url})
|
||||
|
||||
|
||||
|
779
barbican/openstack/common/policy.py
Normal file
779
barbican/openstack/common/policy.py
Normal file
@ -0,0 +1,779 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2012 OpenStack, LLC.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Common Policy Engine Implementation
|
||||
|
||||
Policies can be expressed in one of two forms: A list of lists, or a
|
||||
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::
|
||||
|
||||
[["role:admin"], ["project_id:%(project_id)s", "role:projectadmin"]]
|
||||
|
||||
In the policy language, this becomes::
|
||||
|
||||
role:admin or (project_id:%(project_id)s and role:projectadmin)
|
||||
|
||||
The policy language also has the "not" operator, allowing a richer
|
||||
policy rule::
|
||||
|
||||
project_id:%(project_id)s and not role:dunce
|
||||
|
||||
Finally, two special policy checks should be mentioned; the policy
|
||||
check "@" will always accept an access, and the policy check "!" will
|
||||
always reject an access. (Note that if a rule is either the empty
|
||||
list ("[]") or the empty string, this is equivalent to the "@" policy
|
||||
check.) Of these, the "!" policy check is probably the most useful,
|
||||
as it allows particular rules to be explicitly disabled.
|
||||
"""
|
||||
|
||||
import abc
|
||||
import logging
|
||||
import re
|
||||
import urllib
|
||||
|
||||
import urllib2
|
||||
|
||||
from barbican.openstack.common.gettextutils import _
|
||||
from barbican.openstack.common import jsonutils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_rules = None
|
||||
_checks = {}
|
||||
|
||||
|
||||
class Rules(dict):
|
||||
"""
|
||||
A store for rules. Handles the default_rule setting directly.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def load_json(cls, data, default_rule=None):
|
||||
"""
|
||||
Allow loading of JSON rule data.
|
||||
"""
|
||||
|
||||
# Suck in the JSON data and parse the rules
|
||||
rules = dict((k, parse_rule(v)) for k, v in
|
||||
jsonutils.loads(data).items())
|
||||
|
||||
return cls(rules, default_rule)
|
||||
|
||||
def __init__(self, rules=None, default_rule=None):
|
||||
"""Initialize the Rules store."""
|
||||
|
||||
super(Rules, self).__init__(rules or {})
|
||||
self.default_rule = default_rule
|
||||
|
||||
def __missing__(self, key):
|
||||
"""Implements the default rule handling."""
|
||||
|
||||
# If the default rule isn't actually defined, do something
|
||||
# reasonably intelligent
|
||||
if not self.default_rule or self.default_rule not in self:
|
||||
raise KeyError(key)
|
||||
|
||||
return self[self.default_rule]
|
||||
|
||||
def __str__(self):
|
||||
"""Dumps a string representation of the rules."""
|
||||
|
||||
# Start by building the canonical strings for the rules
|
||||
out_rules = {}
|
||||
for key, value in self.items():
|
||||
# Use empty string for singleton TrueCheck instances
|
||||
if isinstance(value, TrueCheck):
|
||||
out_rules[key] = ''
|
||||
else:
|
||||
out_rules[key] = str(value)
|
||||
|
||||
# Dump a pretty-printed JSON representation
|
||||
return jsonutils.dumps(out_rules, indent=4)
|
||||
|
||||
|
||||
# Really have to figure out a way to deprecate this
|
||||
def set_rules(rules):
|
||||
"""Set the rules in use for policy checks."""
|
||||
|
||||
global _rules
|
||||
|
||||
_rules = rules
|
||||
|
||||
|
||||
# Ditto
|
||||
def reset():
|
||||
"""Clear the rules used for policy checks."""
|
||||
|
||||
global _rules
|
||||
|
||||
_rules = None
|
||||
|
||||
|
||||
def check(rule, target, creds, exc=None, *args, **kwargs):
|
||||
"""
|
||||
Checks authorization of a rule against the target and credentials.
|
||||
|
||||
:param rule: 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 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 exc is not provided, returns
|
||||
False.
|
||||
|
||||
: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.
|
||||
"""
|
||||
|
||||
# Allow the rule to be a Check tree
|
||||
if isinstance(rule, BaseCheck):
|
||||
result = rule(target, creds)
|
||||
elif not _rules:
|
||||
# No rules to reference means we're going to fail closed
|
||||
result = False
|
||||
else:
|
||||
try:
|
||||
# Evaluate the rule
|
||||
result = _rules[rule](target, creds)
|
||||
except KeyError:
|
||||
# If the rule doesn't exist, fail closed
|
||||
result = False
|
||||
|
||||
# If it is False, raise the exception if requested
|
||||
if exc and result is False:
|
||||
raise exc(*args, **kwargs)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class BaseCheck(object):
|
||||
"""
|
||||
Abstract base class for Check classes.
|
||||
"""
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def __str__(self):
|
||||
"""
|
||||
Retrieve a string representation of the Check tree rooted at
|
||||
this node.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(self, target, cred):
|
||||
"""
|
||||
Perform the check. Returns False to reject the access or a
|
||||
true value (not necessary True) to accept the access.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FalseCheck(BaseCheck):
|
||||
"""
|
||||
A policy check that always returns False (disallow).
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of this check."""
|
||||
|
||||
return "!"
|
||||
|
||||
def __call__(self, target, cred):
|
||||
"""Check the policy."""
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class TrueCheck(BaseCheck):
|
||||
"""
|
||||
A policy check that always returns True (allow).
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of this check."""
|
||||
|
||||
return "@"
|
||||
|
||||
def __call__(self, target, cred):
|
||||
"""Check the policy."""
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Check(BaseCheck):
|
||||
"""
|
||||
A base class to allow for user-defined policy checks.
|
||||
"""
|
||||
|
||||
def __init__(self, kind, match):
|
||||
"""
|
||||
:param kind: The kind of the check, i.e., the field before the
|
||||
':'.
|
||||
:param match: The match of the check, i.e., the field after
|
||||
the ':'.
|
||||
"""
|
||||
|
||||
self.kind = kind
|
||||
self.match = match
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of this check."""
|
||||
|
||||
return "%s:%s" % (self.kind, self.match)
|
||||
|
||||
|
||||
class NotCheck(BaseCheck):
|
||||
"""
|
||||
A policy check that inverts the result of another policy check.
|
||||
Implements the "not" operator.
|
||||
"""
|
||||
|
||||
def __init__(self, rule):
|
||||
"""
|
||||
Initialize the 'not' check.
|
||||
|
||||
:param rule: The rule to negate. Must be a Check.
|
||||
"""
|
||||
|
||||
self.rule = rule
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of this check."""
|
||||
|
||||
return "not %s" % self.rule
|
||||
|
||||
def __call__(self, target, cred):
|
||||
"""
|
||||
Check the policy. Returns the logical inverse of the wrapped
|
||||
check.
|
||||
"""
|
||||
|
||||
return not self.rule(target, cred)
|
||||
|
||||
|
||||
class AndCheck(BaseCheck):
|
||||
"""
|
||||
A policy check that requires that a list of other checks all
|
||||
return True. Implements the "and" operator.
|
||||
"""
|
||||
|
||||
def __init__(self, rules):
|
||||
"""
|
||||
Initialize the 'and' check.
|
||||
|
||||
:param rules: A list of rules that will be tested.
|
||||
"""
|
||||
|
||||
self.rules = rules
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of this check."""
|
||||
|
||||
return "(%s)" % ' and '.join(str(r) for r in self.rules)
|
||||
|
||||
def __call__(self, target, cred):
|
||||
"""
|
||||
Check the policy. Requires that all rules accept in order to
|
||||
return True.
|
||||
"""
|
||||
|
||||
for rule in self.rules:
|
||||
if not rule(target, cred):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def add_check(self, rule):
|
||||
"""
|
||||
Allows addition of another rule to the list of rules that will
|
||||
be tested. Returns the AndCheck object for convenience.
|
||||
"""
|
||||
|
||||
self.rules.append(rule)
|
||||
return self
|
||||
|
||||
|
||||
class OrCheck(BaseCheck):
|
||||
"""
|
||||
A policy check that requires that at least one of a list of other
|
||||
checks returns True. Implements the "or" operator.
|
||||
"""
|
||||
|
||||
def __init__(self, rules):
|
||||
"""
|
||||
Initialize the 'or' check.
|
||||
|
||||
:param rules: A list of rules that will be tested.
|
||||
"""
|
||||
|
||||
self.rules = rules
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string representation of this check."""
|
||||
|
||||
return "(%s)" % ' or '.join(str(r) for r in self.rules)
|
||||
|
||||
def __call__(self, target, cred):
|
||||
"""
|
||||
Check the policy. Requires that at least one rule accept in
|
||||
order to return True.
|
||||
"""
|
||||
|
||||
for rule in self.rules:
|
||||
if rule(target, cred):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def add_check(self, rule):
|
||||
"""
|
||||
Allows addition of another rule to the list of rules that will
|
||||
be tested. Returns the OrCheck object for convenience.
|
||||
"""
|
||||
|
||||
self.rules.append(rule)
|
||||
return self
|
||||
|
||||
|
||||
def _parse_check(rule):
|
||||
"""
|
||||
Parse a single base check rule into an appropriate Check object.
|
||||
"""
|
||||
|
||||
# Handle the special checks
|
||||
if rule == '!':
|
||||
return FalseCheck()
|
||||
elif rule == '@':
|
||||
return TrueCheck()
|
||||
|
||||
try:
|
||||
kind, match = rule.split(':', 1)
|
||||
except Exception:
|
||||
LOG.exception(_("Failed to understand rule %(rule)s") % locals())
|
||||
# If the rule is invalid, we'll fail closed
|
||||
return FalseCheck()
|
||||
|
||||
# Find what implements the check
|
||||
if kind in _checks:
|
||||
return _checks[kind](kind, match)
|
||||
elif None in _checks:
|
||||
return _checks[None](kind, match)
|
||||
else:
|
||||
LOG.error(_("No handler for matches of kind %s") % kind)
|
||||
return FalseCheck()
|
||||
|
||||
|
||||
def _parse_list_rule(rule):
|
||||
"""
|
||||
Provided for backwards compatibility. Translates the old
|
||||
list-of-lists syntax into a tree of Check objects.
|
||||
"""
|
||||
|
||||
# Empty rule defaults to True
|
||||
if not rule:
|
||||
return TrueCheck()
|
||||
|
||||
# Outer list is joined by "or"; inner list by "and"
|
||||
or_list = []
|
||||
for inner_rule in rule:
|
||||
# Elide empty inner lists
|
||||
if not inner_rule:
|
||||
continue
|
||||
|
||||
# Handle bare strings
|
||||
if isinstance(inner_rule, basestring):
|
||||
inner_rule = [inner_rule]
|
||||
|
||||
# Parse the inner rules into Check objects
|
||||
and_list = [_parse_check(r) for r in inner_rule]
|
||||
|
||||
# Append the appropriate check to the or_list
|
||||
if len(and_list) == 1:
|
||||
or_list.append(and_list[0])
|
||||
else:
|
||||
or_list.append(AndCheck(and_list))
|
||||
|
||||
# If we have only one check, omit the "or"
|
||||
if len(or_list) == 0:
|
||||
return FalseCheck()
|
||||
elif len(or_list) == 1:
|
||||
return or_list[0]
|
||||
|
||||
return OrCheck(or_list)
|
||||
|
||||
|
||||
# Used for tokenizing the policy language
|
||||
_tokenize_re = re.compile(r'\s+')
|
||||
|
||||
|
||||
def _parse_tokenize(rule):
|
||||
"""
|
||||
Tokenizer for the policy language.
|
||||
|
||||
Most of the single-character tokens are specified in the
|
||||
_tokenize_re; however, parentheses need to be handled specially,
|
||||
because they can appear inside a check string. Thankfully, those
|
||||
parentheses that appear inside a check string can never occur at
|
||||
the very beginning or end ("%(variable)s" is the correct syntax).
|
||||
"""
|
||||
|
||||
for tok in _tokenize_re.split(rule):
|
||||
# Skip empty tokens
|
||||
if not tok or tok.isspace():
|
||||
continue
|
||||
|
||||
# Handle leading parens on the token
|
||||
clean = tok.lstrip('(')
|
||||
for i in range(len(tok) - len(clean)):
|
||||
yield '(', '('
|
||||
|
||||
# If it was only parentheses, continue
|
||||
if not clean:
|
||||
continue
|
||||
else:
|
||||
tok = clean
|
||||
|
||||
# Handle trailing parens on the token
|
||||
clean = tok.rstrip(')')
|
||||
trail = len(tok) - len(clean)
|
||||
|
||||
# Yield the cleaned token
|
||||
lowered = clean.lower()
|
||||
if lowered in ('and', 'or', 'not'):
|
||||
# Special tokens
|
||||
yield lowered, clean
|
||||
elif clean:
|
||||
# Not a special token, but not composed solely of ')'
|
||||
if len(tok) >= 2 and ((tok[0], tok[-1]) in
|
||||
[('"', '"'), ("'", "'")]):
|
||||
# It's a quoted string
|
||||
yield 'string', tok[1:-1]
|
||||
else:
|
||||
yield 'check', _parse_check(clean)
|
||||
|
||||
# Yield the trailing parens
|
||||
for i in range(trail):
|
||||
yield ')', ')'
|
||||
|
||||
|
||||
class ParseStateMeta(type):
|
||||
"""
|
||||
Metaclass for the ParseState class. Facilitates identifying
|
||||
reduction methods.
|
||||
"""
|
||||
|
||||
def __new__(mcs, name, bases, cls_dict):
|
||||
"""
|
||||
Create the class. Injects the 'reducers' list, a list of
|
||||
tuples matching token sequences to the names of the
|
||||
corresponding reduction methods.
|
||||
"""
|
||||
|
||||
reducers = []
|
||||
|
||||
for key, value in cls_dict.items():
|
||||
if not hasattr(value, 'reducers'):
|
||||
continue
|
||||
for reduction in value.reducers:
|
||||
reducers.append((reduction, key))
|
||||
|
||||
cls_dict['reducers'] = reducers
|
||||
|
||||
return super(ParseStateMeta, mcs).__new__(mcs, name, bases, cls_dict)
|
||||
|
||||
|
||||
def reducer(*tokens):
|
||||
"""
|
||||
Decorator for reduction methods. Arguments are a sequence of
|
||||
tokens, in order, which should trigger running this reduction
|
||||
method.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
# Make sure we have a list of reducer sequences
|
||||
if not hasattr(func, 'reducers'):
|
||||
func.reducers = []
|
||||
|
||||
# Add the tokens to the list of reducer sequences
|
||||
func.reducers.append(list(tokens))
|
||||
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class ParseState(object):
|
||||
"""
|
||||
Implement the core of parsing the policy language. Uses a greedy
|
||||
reduction algorithm to reduce a sequence of tokens into a single
|
||||
terminal, the value of which will be the root of the Check tree.
|
||||
|
||||
Note: error reporting is rather lacking. The best we can get with
|
||||
this parser formulation is an overall "parse failed" error.
|
||||
Fortunately, the policy language is simple enough that this
|
||||
shouldn't be that big a problem.
|
||||
"""
|
||||
|
||||
__metaclass__ = ParseStateMeta
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the ParseState."""
|
||||
|
||||
self.tokens = []
|
||||
self.values = []
|
||||
|
||||
def reduce(self):
|
||||
"""
|
||||
Perform a greedy reduction of the token stream. If a reducer
|
||||
method matches, it will be executed, then the reduce() method
|
||||
will be called recursively to search for any more possible
|
||||
reductions.
|
||||
"""
|
||||
|
||||
for reduction, methname in self.reducers:
|
||||
if (len(self.tokens) >= len(reduction) and
|
||||
self.tokens[-len(reduction):] == reduction):
|
||||
# Get the reduction method
|
||||
meth = getattr(self, methname)
|
||||
|
||||
# Reduce the token stream
|
||||
results = meth(*self.values[-len(reduction):])
|
||||
|
||||
# Update the tokens and values
|
||||
self.tokens[-len(reduction):] = [r[0] for r in results]
|
||||
self.values[-len(reduction):] = [r[1] for r in results]
|
||||
|
||||
# Check for any more reductions
|
||||
return self.reduce()
|
||||
|
||||
def shift(self, tok, value):
|
||||
"""Adds one more token to the state. Calls reduce()."""
|
||||
|
||||
self.tokens.append(tok)
|
||||
self.values.append(value)
|
||||
|
||||
# Do a greedy reduce...
|
||||
self.reduce()
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
"""
|
||||
Obtain the final result of the parse. Raises ValueError if
|
||||
the parse failed to reduce to a single result.
|
||||
"""
|
||||
|
||||
if len(self.values) != 1:
|
||||
raise ValueError("Could not parse rule")
|
||||
return self.values[0]
|
||||
|
||||
@reducer('(', 'check', ')')
|
||||
@reducer('(', 'and_expr', ')')
|
||||
@reducer('(', 'or_expr', ')')
|
||||
def _wrap_check(self, _p1, check, _p2):
|
||||
"""Turn parenthesized expressions into a 'check' token."""
|
||||
|
||||
return [('check', check)]
|
||||
|
||||
@reducer('check', 'and', 'check')
|
||||
def _make_and_expr(self, check1, _and, check2):
|
||||
"""
|
||||
Create an 'and_expr' from two checks joined by the 'and'
|
||||
operator.
|
||||
"""
|
||||
|
||||
return [('and_expr', AndCheck([check1, check2]))]
|
||||
|
||||
@reducer('and_expr', 'and', 'check')
|
||||
def _extend_and_expr(self, and_expr, _and, check):
|
||||
"""
|
||||
Extend an 'and_expr' by adding one more check.
|
||||
"""
|
||||
|
||||
return [('and_expr', and_expr.add_check(check))]
|
||||
|
||||
@reducer('check', 'or', 'check')
|
||||
def _make_or_expr(self, check1, _or, check2):
|
||||
"""
|
||||
Create an 'or_expr' from two checks joined by the 'or'
|
||||
operator.
|
||||
"""
|
||||
|
||||
return [('or_expr', OrCheck([check1, check2]))]
|
||||
|
||||
@reducer('or_expr', 'or', 'check')
|
||||
def _extend_or_expr(self, or_expr, _or, check):
|
||||
"""
|
||||
Extend an 'or_expr' by adding one more check.
|
||||
"""
|
||||
|
||||
return [('or_expr', or_expr.add_check(check))]
|
||||
|
||||
@reducer('not', 'check')
|
||||
def _make_not_expr(self, _not, check):
|
||||
"""Invert the result of another check."""
|
||||
|
||||
return [('check', NotCheck(check))]
|
||||
|
||||
|
||||
def _parse_text_rule(rule):
|
||||
"""
|
||||
Translates a policy written in the policy language into a tree of
|
||||
Check objects.
|
||||
"""
|
||||
|
||||
# Empty rule means always accept
|
||||
if not rule:
|
||||
return TrueCheck()
|
||||
|
||||
# Parse the token stream
|
||||
state = ParseState()
|
||||
for tok, value in _parse_tokenize(rule):
|
||||
state.shift(tok, value)
|
||||
|
||||
try:
|
||||
return state.result
|
||||
except ValueError:
|
||||
# Couldn't parse the rule
|
||||
LOG.exception(_("Failed to understand rule %(rule)r") % locals())
|
||||
|
||||
# Fail closed
|
||||
return FalseCheck()
|
||||
|
||||
|
||||
def parse_rule(rule):
|
||||
"""
|
||||
Parses a policy rule into a tree of Check objects.
|
||||
"""
|
||||
|
||||
# If the rule is a string, it's in the policy language
|
||||
if isinstance(rule, basestring):
|
||||
return _parse_text_rule(rule)
|
||||
return _parse_list_rule(rule)
|
||||
|
||||
|
||||
def register(name, func=None):
|
||||
"""
|
||||
Register a function or Check class as a policy check.
|
||||
|
||||
:param name: Gives the name of the check type, e.g., 'rule',
|
||||
'role', etc. If name is None, a default check type
|
||||
will be registered.
|
||||
:param func: If given, provides the function or class to register.
|
||||
If not given, returns a function taking one argument
|
||||
to specify the function or class to register,
|
||||
allowing use as a decorator.
|
||||
"""
|
||||
|
||||
# Perform the actual decoration by registering the function or
|
||||
# class. Returns the function or class for compliance with the
|
||||
# decorator interface.
|
||||
def decorator(func):
|
||||
_checks[name] = func
|
||||
return func
|
||||
|
||||
# If the function or class is given, do the registration
|
||||
if func:
|
||||
return decorator(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@register("rule")
|
||||
class RuleCheck(Check):
|
||||
def __call__(self, target, creds):
|
||||
"""
|
||||
Recursively checks credentials based on the defined rules.
|
||||
"""
|
||||
|
||||
try:
|
||||
return _rules[self.match](target, creds)
|
||||
except KeyError:
|
||||
# We don't have any matching rule; fail closed
|
||||
return False
|
||||
|
||||
|
||||
@register("role")
|
||||
class RoleCheck(Check):
|
||||
def __call__(self, target, creds):
|
||||
"""Check that there is a matching role in the cred dict."""
|
||||
|
||||
return self.match.lower() in [x.lower() for x in creds['roles']]
|
||||
|
||||
|
||||
@register('http')
|
||||
class HttpCheck(Check):
|
||||
def __call__(self, target, creds):
|
||||
"""
|
||||
Check http: rules by calling to a remote server.
|
||||
|
||||
This example implementation simply verifies that the response
|
||||
is exactly 'True'.
|
||||
"""
|
||||
|
||||
url = ('http:' + self.match) % target
|
||||
data = {'target': jsonutils.dumps(target),
|
||||
'credentials': jsonutils.dumps(creds)}
|
||||
post_data = urllib.urlencode(data)
|
||||
f = urllib2.urlopen(url, post_data)
|
||||
return f.read() == "True"
|
||||
|
||||
|
||||
@register(None)
|
||||
class GenericCheck(Check):
|
||||
def __call__(self, target, creds):
|
||||
"""
|
||||
Check an individual match.
|
||||
|
||||
Matches look like:
|
||||
|
||||
tenant:%(tenant_id)s
|
||||
role:compute:admin
|
||||
"""
|
||||
|
||||
# TODO(termie): do dict inspection via dot syntax
|
||||
match = self.match % target
|
||||
if self.kind in creds:
|
||||
return match == unicode(creds[self.kind])
|
||||
return False
|
@ -9,8 +9,11 @@ VENV=.venv
|
||||
VENV_PYTHON=./$VENV/lib/python2.7/site-packages
|
||||
|
||||
PATH=/opt/uwsgi:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/etc/$PKG:$PATH
|
||||
CONF_FILE=/etc/$PKG/barbican-api.ini
|
||||
PASTE_FILE=/etc/$PKG/barbican-api-paste.ini
|
||||
PKD_DIR=/etc/$PKG
|
||||
CONF_FILE=$PKG_DIR/barbican-api.ini
|
||||
PASTE_FILE=$PKG_DIR/barbican-api-paste.ini
|
||||
POLICY_FILE=$PKG_DIR/policy.json
|
||||
SIGNING_DIR=$PKG_DIR/cache/
|
||||
OPTS=''
|
||||
|
||||
# Configure for a local deployment environment:
|
||||
@ -29,5 +32,18 @@ then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [! -f $POLICY_FILE ];
|
||||
then
|
||||
LOCAL_POLICY_FILE=./etc/$PKG/policy.json
|
||||
mkdir -p $PKG_DIR
|
||||
sudo cp $LOCAL_POLICY_FILE POLICY_FILE
|
||||
fi
|
||||
|
||||
if [! -f $SIGNING_DIR ];
|
||||
then
|
||||
echo "making "$SIGNING_DIR
|
||||
sudo mkdir -p $SIGNING_DIR
|
||||
fi
|
||||
|
||||
echo 'Running barbican-api uwsgi process, using init file here: ' $CONF_FILE
|
||||
uwsgi --paste config:$PASTE_FILE --ini $CONF_FILE $OPTS
|
||||
|
78
bin/keystone_data.sh
Executable file
78
bin/keystone_data.sh
Executable file
@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
#------------------------------------
|
||||
# the devstack way
|
||||
# cd <devstack-home>
|
||||
# source openrc nova service
|
||||
# This sets up an admin user and the service tenant and passport in environment
|
||||
#------------------------------------
|
||||
# alternately export values for
|
||||
export OS_AUTH_URL="http://localhost:5000/v2.0"
|
||||
# your secret password
|
||||
export OS_PASSWORD="orange"
|
||||
export OS_TENANT_NAME="service"
|
||||
export OS_USERNAME="nova"
|
||||
|
||||
# --------------------------------
|
||||
# alternately service_token and endpoint
|
||||
|
||||
#export OS_SERVICE_TOKEN=orange
|
||||
#export OS_SERVICE_ENDPOINT=http://localhost:35357/v2.0
|
||||
# ========================================
|
||||
|
||||
echo " OS_SERVICE_ENDPOINT="$OS_SEVICE_ENDPOINT
|
||||
echo " SERVICE_TOKEN="$SERVICE_TOKEN
|
||||
echo " OS_TENANT_NAME="$OS_TENANT_NAME
|
||||
echo " OS_USERNAME="$OS_USERNAME
|
||||
echo " OS_PASSWORD="$OS_PASSWORD
|
||||
echo " OS_AUTH_URL="$OS_AUTH_URL
|
||||
|
||||
#test with
|
||||
keystone tenant-list
|
||||
|
||||
function get_id () {
|
||||
echo `"$@" | awk '/ id / { print $4 }'`
|
||||
}
|
||||
|
||||
#------------------------------------------------------------
|
||||
# Adding the Key Manager Service: barbican
|
||||
#------------------------------------------------------------
|
||||
|
||||
ENABLED_SERVICES="barbican"
|
||||
SERVICE_PASSWORD="orange"
|
||||
SERVICE_HOST="localhost"
|
||||
SERVICE_TENANT_NAME="service"
|
||||
|
||||
#============================
|
||||
# Lookups
|
||||
SERVICE_TENANT=$(keystone tenant-list | awk "/ $SERVICE_TENANT_NAME / { print \$2 }")
|
||||
ADMIN_ROLE=$(keystone role-list | awk "/ admin / { print \$2 }")
|
||||
MEMBER_ROLE=$(keystone role-list | awk "/ Member / { print \$2 }")
|
||||
|
||||
# Ports to avoid: 3333, 5000, 8773, 8774, 8776, 9292, 9696, 35357
|
||||
# Barbican
|
||||
if [[ "$ENABLED_SERVICES" =~ "barbican" ]]; then
|
||||
BARBICAN_USER=$(get_id keystone user-create \
|
||||
--name=barbican \
|
||||
--pass="$SERVICE_PASSWORD" \
|
||||
--tenant_id $SERVICE_TENANT \
|
||||
--email=barbican@example.com)
|
||||
keystone user-role-add \
|
||||
--tenant_id $SERVICE_TENANT \
|
||||
--user_id $BARBICAN_USER \
|
||||
--role_id $ADMIN_ROLE
|
||||
if [[ "$KEYSTONE_CATALOG_BACKEND" = 'sql' ]]; then
|
||||
BARBICAN_SERVICE=$(get_id keystone service-create \
|
||||
--name=barbican \
|
||||
--type="key store" \
|
||||
--description="Barbican Key Management Service")
|
||||
keystone endpoint-create \
|
||||
--region RegionOne \
|
||||
--service_id $BARBICAN_SERVICE \
|
||||
--publicurl "http://$SERVICE_HOST:9311" \
|
||||
--adminurl "http://$SERVICE_HOST:9312" \
|
||||
--internalurl "http://$SERVICE_HOST:9313"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
[pipeline:main]
|
||||
pipeline = simple apiapp
|
||||
|
||||
#Use this pipeline for keystone auth [pipeline:barbican-api-keystone]
|
||||
#Use this pipeline for keystone auth
|
||||
#[pipeline:barbican-api-keystone]
|
||||
#pipeline = keystone_authtoken apiapp
|
||||
|
||||
[app:apiapp]
|
||||
@ -16,14 +17,14 @@ paste.filter_factory = barbican.api.middleware.context:ContextMiddleware.factory
|
||||
|
||||
[filter:keystone_authtoken]
|
||||
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
|
||||
signing_dir = .
|
||||
signing_dir = /tmp/barbican/cache
|
||||
auth_host = localhost
|
||||
auth_port = 5000
|
||||
#need ability to re-auth a token, thus admin url
|
||||
auth_port = 35357
|
||||
auth_protocol = http
|
||||
admin_tenant_name = service
|
||||
admin_user = barbican
|
||||
admin_password = orange
|
||||
auth_version = v2.0
|
||||
#do we want to delay failing to log the unauthorized request in barbican?
|
||||
#delay_auth_decision = true
|
||||
|
||||
#delay failing perhaps to log the unauthorized request in barbican ..
|
||||
#delay_auth_decision = true
|
||||
|
@ -9,7 +9,7 @@ debug = True
|
||||
bind_host = 0.0.0.0
|
||||
|
||||
# Port the bind the API server to
|
||||
bind_port = 9292
|
||||
bind_port = 9311
|
||||
|
||||
# Log to this file. Make sure you do not set the same log
|
||||
# file for both the API and registry servers!
|
||||
@ -25,9 +25,10 @@ backlog = 4096
|
||||
# SQLAlchemy connection string for the reference implementation
|
||||
# registry server. Any valid SQLAlchemy connection string is fine.
|
||||
# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
|
||||
# Uncomment this for local dev, putting db in project directory: sql_connection = sqlite:///barbican.sqlite
|
||||
# Uncomment this for local dev, putting db in project directory:
|
||||
sql_connection = sqlite:///barbican.sqlite
|
||||
# Note: For absolute addresses, use '////' slashes after 'sqlite:'
|
||||
sql_connection = sqlite:////var/lib/barbican/barbican.sqlite
|
||||
#sql_connection = sqlite:////var/lib/barbican/barbican.sqlite
|
||||
|
||||
# Period in seconds after which SQLAlchemy should reestablish its connection
|
||||
# to the database.
|
||||
@ -104,3 +105,12 @@ broker = amqp://guest@localhost/
|
||||
|
||||
# Module includes
|
||||
include = barbican.queue.celery.resources
|
||||
|
||||
|
||||
# ======== OpenStack policy integration
|
||||
# JSON file representing policy (string value)
|
||||
policy_file=/etc/barbican/policy.json
|
||||
|
||||
# Rule checked when requested rule is not found (string value)
|
||||
policy_default_rule=default
|
||||
|
||||
|
4
etc/barbican/policy.json
Normal file
4
etc/barbican/policy.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"default": "",
|
||||
"manage_key_recycle": "role:admin"
|
||||
}
|
Loading…
Reference in New Issue
Block a user