diff --git a/barbican/__init__.py b/barbican/__init__.py index 3e2ee2194..22a7b893d 100644 --- a/barbican/__init__.py +++ b/barbican/__init__.py @@ -16,3 +16,7 @@ """ Cloudkeep's Barbican module root """ + +import gettext + +gettext.install('barbican', unicode=1) diff --git a/barbican/api/policy.py b/barbican/api/policy.py new file mode 100644 index 000000000..5068f1e91 --- /dev/null +++ b/barbican/api/policy.py @@ -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) + diff --git a/barbican/api/resources.py b/barbican/api/resources.py index 7267549ad..ba8b2f920 100644 --- a/barbican/api/resources.py +++ b/barbican/api/resources.py @@ -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}) diff --git a/barbican/openstack/common/policy.py b/barbican/openstack/common/policy.py new file mode 100644 index 000000000..bef488c3c --- /dev/null +++ b/barbican/openstack/common/policy.py @@ -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 diff --git a/bin/barbican-api b/bin/barbican-api index c78f7cb9d..96889908b 100755 --- a/bin/barbican-api +++ b/bin/barbican-api @@ -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 diff --git a/bin/keystone_data.sh b/bin/keystone_data.sh new file mode 100755 index 000000000..a5f8543d9 --- /dev/null +++ b/bin/keystone_data.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +#------------------------------------ +# the devstack way +# cd +# 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 + + diff --git a/etc/barbican/barbican-api-paste.ini b/etc/barbican/barbican-api-paste.ini index 67964f013..63ac4b17f 100644 --- a/etc/barbican/barbican-api-paste.ini +++ b/etc/barbican/barbican-api-paste.ini @@ -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 diff --git a/etc/barbican/barbican-api.conf b/etc/barbican/barbican-api.conf index bc045c8c1..e58ec8a8f 100644 --- a/etc/barbican/barbican-api.conf +++ b/etc/barbican/barbican-api.conf @@ -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 + diff --git a/etc/barbican/policy.json b/etc/barbican/policy.json new file mode 100644 index 000000000..e53fd9d36 --- /dev/null +++ b/etc/barbican/policy.json @@ -0,0 +1,4 @@ +{ + "default": "", + "manage_key_recycle": "role:admin" +} \ No newline at end of file