Update policies
Merge in update openstack-common policy code. Updates Nova-specific policy glue code to eliminate deprecated openstack-common policy interfaces. Also cleans up policy code to allow for returning fine-grained policy values. As a side effect, fixes bug 1039132. Change-Id: I2951a0de3751bd2ec868e7a661070fed624e4af2
This commit is contained in:
parent
da94e9fb69
commit
6acc9f6b19
@ -1,6 +1,6 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2011 OpenStack, LLC.
|
||||
# Copyright (c) 2012 OpenStack, LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -15,10 +15,52 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Common Policy Engine Implementation"""
|
||||
"""
|
||||
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 nova.openstack.common.gettextutils import _
|
||||
@ -28,217 +70,650 @@ from nova.openstack.common import jsonutils
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_BRAIN = None
|
||||
_rules = None
|
||||
_checks = {}
|
||||
|
||||
|
||||
def set_brain(brain):
|
||||
"""Set the brain used by enforce().
|
||||
|
||||
Defaults use Brain() if not set.
|
||||
|
||||
class Rules(dict):
|
||||
"""
|
||||
global _BRAIN
|
||||
_BRAIN = brain
|
||||
|
||||
|
||||
def reset():
|
||||
"""Clear the brain used by enforce()."""
|
||||
global _BRAIN
|
||||
_BRAIN = None
|
||||
|
||||
|
||||
def enforce(match_list, target_dict, credentials_dict, exc=None,
|
||||
*args, **kwargs):
|
||||
"""Enforces authorization of some rules against credentials.
|
||||
|
||||
:param match_list: nested tuples of data to match against
|
||||
|
||||
The basic brain supports three types of match lists:
|
||||
|
||||
1) rules
|
||||
|
||||
looks like: ``('rule:compute:get_instance',)``
|
||||
|
||||
Retrieves the named rule from the rules dict and recursively
|
||||
checks against the contents of the rule.
|
||||
|
||||
2) roles
|
||||
|
||||
looks like: ``('role:compute:admin',)``
|
||||
|
||||
Matches if the specified role is in credentials_dict['roles'].
|
||||
|
||||
3) generic
|
||||
|
||||
looks like: ``('tenant_id:%(tenant_id)s',)``
|
||||
|
||||
Substitutes values from the target dict into the match using
|
||||
the % operator and matches them against the creds dict.
|
||||
|
||||
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 perform simple boolean logic. For
|
||||
example, the following rule would return True if the creds
|
||||
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": (
|
||||
'role:admin',
|
||||
('tenant_id:%(tenant_id)s', 'role:compute_sysadmin')
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
also add new reserved words. For example, the HttpBrain adds http as a
|
||||
reserved word.
|
||||
|
||||
:param target_dict: dict of object properties
|
||||
|
||||
Target dicts contain as much information as we can about the object being
|
||||
operated on.
|
||||
|
||||
:param credentials_dict: dict of actor properties
|
||||
|
||||
Credentials dicts contain as much information as we can about the user
|
||||
performing the action.
|
||||
|
||||
: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
|
||||
A store for rules. Handles the default_rule setting directly.
|
||||
"""
|
||||
global _BRAIN
|
||||
if not _BRAIN:
|
||||
_BRAIN = Brain()
|
||||
if not _BRAIN.check(match_list, target_dict, credentials_dict):
|
||||
if exc:
|
||||
raise exc(*args, **kwargs)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class Brain(object):
|
||||
"""Implements policy checking."""
|
||||
|
||||
_checks = {}
|
||||
|
||||
@classmethod
|
||||
def _register(cls, name, func):
|
||||
cls._checks[name] = func
|
||||
|
||||
@classmethod
|
||||
def load_json(cls, data, default_rule=None):
|
||||
"""Init a brain using json instead of a rules dictionary."""
|
||||
rules_dict = jsonutils.loads(data)
|
||||
return cls(rules=rules_dict, default_rule=default_rule)
|
||||
"""
|
||||
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):
|
||||
if self.__class__ != Brain:
|
||||
LOG.warning(_("Inheritance-based rules are deprecated; use "
|
||||
"the default brain instead of %s.") %
|
||||
self.__class__.__name__)
|
||||
"""Initialize the Rules store."""
|
||||
|
||||
self.rules = rules or {}
|
||||
super(Rules, self).__init__(rules or {})
|
||||
self.default_rule = default_rule
|
||||
|
||||
def add_rule(self, key, match):
|
||||
self.rules[key] = match
|
||||
def __missing__(self, key):
|
||||
"""Implements the default rule handling."""
|
||||
|
||||
def _check(self, match, target_dict, cred_dict):
|
||||
try:
|
||||
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
|
||||
# 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)
|
||||
|
||||
func = None
|
||||
try:
|
||||
old_func = getattr(self, '_check_%s' % match_kind)
|
||||
except AttributeError:
|
||||
func = self._checks.get(match_kind, self._checks.get(None, None))
|
||||
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:
|
||||
LOG.warning(_("Inheritance-based rules are deprecated; update "
|
||||
"_check_%s") % match_kind)
|
||||
func = (lambda brain, kind, value, target, cred:
|
||||
old_func(value, target, cred))
|
||||
out_rules[key] = str(value)
|
||||
|
||||
if not func:
|
||||
LOG.error(_("No handler for matches of kind %s") % match_kind)
|
||||
# Fail closed
|
||||
return False
|
||||
# Dump a pretty-printed JSON representation
|
||||
return jsonutils.dumps(out_rules, indent=4)
|
||||
|
||||
return func(self, match_kind, match_value, target_dict, cred_dict)
|
||||
|
||||
def check(self, match_list, target_dict, cred_dict):
|
||||
"""Checks authorization of some rules against credentials.
|
||||
# Really have to figure out a way to deprecate this
|
||||
def set_rules(rules):
|
||||
"""Set the rules in use for policy checks."""
|
||||
|
||||
Detailed description of the check with examples in policy.enforce().
|
||||
global _rules
|
||||
|
||||
:param match_list: nested tuples of data to match against
|
||||
:param target_dict: dict of object properties
|
||||
:param credentials_dict: dict of actor properties
|
||||
_rules = rules
|
||||
|
||||
:returns: True if the check passes
|
||||
|
||||
# Ditto
|
||||
def reset():
|
||||
"""Clear the rules used for policy checks."""
|
||||
|
||||
global _rules
|
||||
|
||||
_rules = None
|
||||
|
||||
|
||||
def check(rule, target, creds, exc=None, *args, **kwargs):
|
||||
"""
|
||||
if not match_list:
|
||||
return True
|
||||
for and_list in match_list:
|
||||
if isinstance(and_list, basestring):
|
||||
and_list = (and_list,)
|
||||
if all([self._check(item, target_dict, cred_dict)
|
||||
for item in and_list]):
|
||||
return True
|
||||
return False
|
||||
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 HttpBrain(Brain):
|
||||
"""A brain that can check external urls for policy.
|
||||
class BaseCheck(object):
|
||||
"""
|
||||
Abstract base class for Check classes.
|
||||
"""
|
||||
|
||||
Posts json blobs for target and credentials.
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
Note that this brain is deprecated; the http check is registered
|
||||
by default.
|
||||
@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
|
||||
|
||||
|
||||
def register(name, func=None):
|
||||
class FalseCheck(BaseCheck):
|
||||
"""
|
||||
Register a function as a policy check.
|
||||
|
||||
:param name: Gives the name of the check type, e.g., 'rule',
|
||||
'role', etc. If name is None, a default function
|
||||
will be registered.
|
||||
:param func: If given, provides the function to register. If not
|
||||
given, returns a function taking one argument to
|
||||
specify the function to register, allowing use as a
|
||||
decorator.
|
||||
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.
|
||||
"""
|
||||
|
||||
# Perform the actual decoration by registering the function.
|
||||
# Returns the function for compliance with the decorator
|
||||
# interface.
|
||||
def decorator(func):
|
||||
# Register the function
|
||||
Brain._register(name, 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
|
||||
|
||||
# If the function is given, do the registration
|
||||
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)
|
||||
|
||||
@ -246,55 +721,59 @@ def register(name, func=None):
|
||||
|
||||
|
||||
@register("rule")
|
||||
def _check_rule(brain, match_kind, match, target_dict, cred_dict):
|
||||
"""Recursively checks credentials based on the brains rules."""
|
||||
try:
|
||||
new_match_list = brain.rules[match]
|
||||
except KeyError:
|
||||
if brain.default_rule and match != brain.default_rule:
|
||||
new_match_list = ('rule:%s' % brain.default_rule,)
|
||||
else:
|
||||
return False
|
||||
class RuleCheck(Check):
|
||||
def __call__(self, target, creds):
|
||||
"""
|
||||
Recursively checks credentials based on the defined rules.
|
||||
"""
|
||||
|
||||
return brain.check(new_match_list, target_dict, cred_dict)
|
||||
try:
|
||||
return _rules[self.match](target, creds)
|
||||
except KeyError:
|
||||
# We don't have any matching rule; fail closed
|
||||
return False
|
||||
|
||||
|
||||
@register("role")
|
||||
def _check_role(brain, match_kind, match, target_dict, cred_dict):
|
||||
class RoleCheck(Check):
|
||||
def __call__(self, target, creds):
|
||||
"""Check that there is a matching role in the cred dict."""
|
||||
return match.lower() in [x.lower() for x in cred_dict['roles']]
|
||||
|
||||
return self.match.lower() in [x.lower() for x in creds['roles']]
|
||||
|
||||
|
||||
@register('http')
|
||||
def _check_http(brain, match_kind, 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.
|
||||
|
||||
class HttpCheck(Check):
|
||||
def __call__(self, target, creds):
|
||||
"""
|
||||
url = 'http:' + (match % target_dict)
|
||||
data = {'target': jsonutils.dumps(target_dict),
|
||||
'credentials': jsonutils.dumps(cred_dict)}
|
||||
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)
|
||||
def _check_generic(brain, match_kind, match, target_dict, cred_dict):
|
||||
"""Check an individual match.
|
||||
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 = match % target_dict
|
||||
if match_kind in cred_dict:
|
||||
return match == unicode(cred_dict[match_kind])
|
||||
match = self.match % target
|
||||
if self.kind in creds:
|
||||
return match == unicode(creds[self.kind])
|
||||
return False
|
||||
|
@ -60,15 +60,15 @@ def init():
|
||||
if not _POLICY_PATH:
|
||||
raise exception.ConfigNotFound(path=FLAGS.policy_file)
|
||||
utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE,
|
||||
reload_func=_set_brain)
|
||||
reload_func=_set_rules)
|
||||
|
||||
|
||||
def _set_brain(data):
|
||||
def _set_rules(data):
|
||||
default_rule = FLAGS.policy_default_rule
|
||||
policy.set_brain(policy.Brain.load_json(data, default_rule))
|
||||
policy.set_rules(policy.Rules.load_json(data, default_rule))
|
||||
|
||||
|
||||
def enforce(context, action, target):
|
||||
def enforce(context, action, target, do_raise=True):
|
||||
"""Verifies that the action is valid on the target in this context.
|
||||
|
||||
:param context: nova context
|
||||
@ -77,26 +77,29 @@ def enforce(context, action, target):
|
||||
i.e. ``compute:create_instance``,
|
||||
``compute:attach_volume``,
|
||||
``volume:attach_volume``
|
||||
|
||||
:param object: dictionary representing the object of the action
|
||||
:param target: dictionary representing the object of the action
|
||||
for object creation this should be a dictionary representing the
|
||||
location of the object e.g. ``{'project_id': context.project_id}``
|
||||
:param do_raise: if True (the default), raises PolicyNotAuthorized;
|
||||
if False, returns False
|
||||
|
||||
:raises nova.exception.PolicyNotAuthorized: if verification fails.
|
||||
:raises nova.exception.PolicyNotAuthorized: if verification fails
|
||||
and do_raise is True.
|
||||
|
||||
:return: returns a non-False value (not necessarily "True") if
|
||||
authorized, and the exact value False if not authorized and
|
||||
do_raise is False.
|
||||
"""
|
||||
init()
|
||||
|
||||
match_list = ('rule:%s' % action,)
|
||||
credentials = context.to_dict()
|
||||
|
||||
# NOTE(vish): This is to work around the following launchpad bug:
|
||||
# https://bugs.launchpad.net/openstack-common/+bug/1039132
|
||||
# It can be removed when that bug is fixed.
|
||||
credentials['is_admin'] = unicode(credentials['is_admin'])
|
||||
# Add the exception arguments if asked to do a raise
|
||||
extra = {}
|
||||
if do_raise:
|
||||
extra.update(exc=exception.PolicyNotAuthorized, action=action)
|
||||
|
||||
policy.enforce(match_list, target, credentials,
|
||||
exception.PolicyNotAuthorized, action=action)
|
||||
return policy.check(action, target, credentials, **extra)
|
||||
|
||||
|
||||
def check_is_admin(roles):
|
||||
@ -105,15 +108,24 @@ def check_is_admin(roles):
|
||||
"""
|
||||
init()
|
||||
|
||||
action = 'context_is_admin'
|
||||
match_list = ('rule:%s' % action,)
|
||||
target = {}
|
||||
credentials = {'roles': roles}
|
||||
|
||||
try:
|
||||
policy.enforce(match_list, target, credentials,
|
||||
exception.PolicyNotAuthorized, action=action)
|
||||
except exception.PolicyNotAuthorized:
|
||||
return False
|
||||
return policy.check('context_is_admin', target, credentials)
|
||||
|
||||
return True
|
||||
|
||||
@policy.register('is_admin')
|
||||
class IsAdminCheck(policy.Check):
|
||||
"""An explicit check for is_admin."""
|
||||
|
||||
def __init__(self, kind, match):
|
||||
"""Initialize the check."""
|
||||
|
||||
self.expected = (match.lower() == 'true')
|
||||
|
||||
super(IsAdminCheck, self).__init__(kind, str(self.expected))
|
||||
|
||||
def __call__(self, target, creds):
|
||||
"""Determine whether is_admin matches the requested value."""
|
||||
|
||||
return creds['is_admin'] == self.expected
|
||||
|
@ -1,198 +1,198 @@
|
||||
{
|
||||
"admin_api": [["role:admin"]],
|
||||
"admin_api": "role:admin",
|
||||
|
||||
"context_is_admin": [["role:admin"], ["role:administrator"]],
|
||||
"compute:create": [],
|
||||
"compute:create:attach_network": [],
|
||||
"compute:create:attach_volume": [],
|
||||
"context_is_admin": "role:admin or role:administrator",
|
||||
"compute:create": "",
|
||||
"compute:create:attach_network": "",
|
||||
"compute:create:attach_volume": "",
|
||||
|
||||
"compute:get": [],
|
||||
"compute:get_all": [],
|
||||
"compute:get": "",
|
||||
"compute:get_all": "",
|
||||
|
||||
"compute:update": [],
|
||||
"compute:update": "",
|
||||
|
||||
"compute:get_instance_metadata": [],
|
||||
"compute:update_instance_metadata": [],
|
||||
"compute:delete_instance_metadata": [],
|
||||
"compute:get_instance_metadata": "",
|
||||
"compute:update_instance_metadata": "",
|
||||
"compute:delete_instance_metadata": "",
|
||||
|
||||
"compute:get_instance_faults": [],
|
||||
"compute:get_diagnostics": [],
|
||||
"compute:get_instance_faults": "",
|
||||
"compute:get_diagnostics": "",
|
||||
|
||||
"compute:get_lock": [],
|
||||
"compute:lock": [],
|
||||
"compute:unlock": [],
|
||||
"compute:get_lock": "",
|
||||
"compute:lock": "",
|
||||
"compute:unlock": "",
|
||||
|
||||
"compute:get_vnc_console": [],
|
||||
"compute:get_console_output": [],
|
||||
"compute:get_vnc_console": "",
|
||||
"compute:get_console_output": "",
|
||||
|
||||
"compute:associate_floating_ip": [],
|
||||
"compute:reset_network": [],
|
||||
"compute:inject_network_info": [],
|
||||
"compute:add_fixed_ip": [],
|
||||
"compute:remove_fixed_ip": [],
|
||||
"compute:associate_floating_ip": "",
|
||||
"compute:reset_network": "",
|
||||
"compute:inject_network_info": "",
|
||||
"compute:add_fixed_ip": "",
|
||||
"compute:remove_fixed_ip": "",
|
||||
|
||||
"compute:attach_volume": [],
|
||||
"compute:detach_volume": [],
|
||||
"compute:attach_volume": "",
|
||||
"compute:detach_volume": "",
|
||||
|
||||
"compute:inject_file": [],
|
||||
"compute:inject_file": "",
|
||||
|
||||
"compute:set_admin_password": [],
|
||||
"compute:set_admin_password": "",
|
||||
|
||||
"compute:rescue": [],
|
||||
"compute:unrescue": [],
|
||||
"compute:rescue": "",
|
||||
"compute:unrescue": "",
|
||||
|
||||
"compute:suspend": [],
|
||||
"compute:resume": [],
|
||||
"compute:suspend": "",
|
||||
"compute:resume": "",
|
||||
|
||||
"compute:pause": [],
|
||||
"compute:unpause": [],
|
||||
"compute:pause": "",
|
||||
"compute:unpause": "",
|
||||
|
||||
"compute:start": [],
|
||||
"compute:stop": [],
|
||||
"compute:start": "",
|
||||
"compute:stop": "",
|
||||
|
||||
"compute:resize": [],
|
||||
"compute:confirm_resize": [],
|
||||
"compute:revert_resize": [],
|
||||
"compute:resize": "",
|
||||
"compute:confirm_resize": "",
|
||||
"compute:revert_resize": "",
|
||||
|
||||
"compute:rebuild": [],
|
||||
"compute:rebuild": "",
|
||||
|
||||
"compute:reboot": [],
|
||||
"compute:reboot": "",
|
||||
|
||||
"compute:snapshot": [],
|
||||
"compute:backup": [],
|
||||
"compute:snapshot": "",
|
||||
"compute:backup": "",
|
||||
|
||||
"compute:security_groups:add_to_instance": [],
|
||||
"compute:security_groups:remove_from_instance": [],
|
||||
"compute:security_groups:add_to_instance": "",
|
||||
"compute:security_groups:remove_from_instance": "",
|
||||
|
||||
"compute:delete": [],
|
||||
"compute:soft_delete": [],
|
||||
"compute:force_delete": [],
|
||||
"compute:restore": [],
|
||||
"compute:delete": "",
|
||||
"compute:soft_delete": "",
|
||||
"compute:force_delete": "",
|
||||
"compute:restore": "",
|
||||
|
||||
|
||||
"compute_extension:accounts": [],
|
||||
"compute_extension:admin_actions:pause": [],
|
||||
"compute_extension:admin_actions:unpause": [],
|
||||
"compute_extension:admin_actions:suspend": [],
|
||||
"compute_extension:admin_actions:resume": [],
|
||||
"compute_extension:admin_actions:lock": [],
|
||||
"compute_extension:admin_actions:unlock": [],
|
||||
"compute_extension:admin_actions:resetNetwork": [],
|
||||
"compute_extension:admin_actions:injectNetworkInfo": [],
|
||||
"compute_extension:admin_actions:createBackup": [],
|
||||
"compute_extension:admin_actions:migrateLive": [],
|
||||
"compute_extension:admin_actions:resetState": [],
|
||||
"compute_extension:admin_actions:migrate": [],
|
||||
"compute_extension:aggregates": [],
|
||||
"compute_extension:certificates": [],
|
||||
"compute_extension:cloudpipe": [],
|
||||
"compute_extension:config_drive": [],
|
||||
"compute_extension:console_output": [],
|
||||
"compute_extension:consoles": [],
|
||||
"compute_extension:createserverext": [],
|
||||
"compute_extension:deferred_delete": [],
|
||||
"compute_extension:disk_config": [],
|
||||
"compute_extension:extended_server_attributes": [],
|
||||
"compute_extension:extended_status": [],
|
||||
"compute_extension:flavor_access": [],
|
||||
"compute_extension:flavor_disabled": [],
|
||||
"compute_extension:flavor_rxtx": [],
|
||||
"compute_extension:flavor_swap": [],
|
||||
"compute_extension:flavorextradata": [],
|
||||
"compute_extension:flavorextraspecs": [],
|
||||
"compute_extension:flavormanage": [],
|
||||
"compute_extension:floating_ip_dns": [],
|
||||
"compute_extension:floating_ip_pools": [],
|
||||
"compute_extension:floating_ips": [],
|
||||
"compute_extension:hosts": [],
|
||||
"compute_extension:hypervisors": [],
|
||||
"compute_extension:instance_usage_audit_log": [],
|
||||
"compute_extension:keypairs": [],
|
||||
"compute_extension:multinic": [],
|
||||
"compute_extension:networks": [],
|
||||
"compute_extension:networks:view": [],
|
||||
"compute_extension:quotas:show": [],
|
||||
"compute_extension:quotas:update": [],
|
||||
"compute_extension:quota_classes": [],
|
||||
"compute_extension:rescue": [],
|
||||
"compute_extension:security_groups": [],
|
||||
"compute_extension:server_diagnostics": [],
|
||||
"compute_extension:simple_tenant_usage:show": [],
|
||||
"compute_extension:simple_tenant_usage:list": [],
|
||||
"compute_extension:users": [],
|
||||
"compute_extension:virtual_interfaces": [],
|
||||
"compute_extension:virtual_storage_arrays": [],
|
||||
"compute_extension:volumes": [],
|
||||
"compute_extension:volumetypes": [],
|
||||
"compute_extension:zones": [],
|
||||
"compute_extension:accounts": "",
|
||||
"compute_extension:admin_actions:pause": "",
|
||||
"compute_extension:admin_actions:unpause": "",
|
||||
"compute_extension:admin_actions:suspend": "",
|
||||
"compute_extension:admin_actions:resume": "",
|
||||
"compute_extension:admin_actions:lock": "",
|
||||
"compute_extension:admin_actions:unlock": "",
|
||||
"compute_extension:admin_actions:resetNetwork": "",
|
||||
"compute_extension:admin_actions:injectNetworkInfo": "",
|
||||
"compute_extension:admin_actions:createBackup": "",
|
||||
"compute_extension:admin_actions:migrateLive": "",
|
||||
"compute_extension:admin_actions:resetState": "",
|
||||
"compute_extension:admin_actions:migrate": "",
|
||||
"compute_extension:aggregates": "",
|
||||
"compute_extension:certificates": "",
|
||||
"compute_extension:cloudpipe": "",
|
||||
"compute_extension:config_drive": "",
|
||||
"compute_extension:console_output": "",
|
||||
"compute_extension:consoles": "",
|
||||
"compute_extension:createserverext": "",
|
||||
"compute_extension:deferred_delete": "",
|
||||
"compute_extension:disk_config": "",
|
||||
"compute_extension:extended_server_attributes": "",
|
||||
"compute_extension:extended_status": "",
|
||||
"compute_extension:flavor_access": "",
|
||||
"compute_extension:flavor_disabled": "",
|
||||
"compute_extension:flavor_rxtx": "",
|
||||
"compute_extension:flavor_swap": "",
|
||||
"compute_extension:flavorextradata": "",
|
||||
"compute_extension:flavorextraspecs": "",
|
||||
"compute_extension:flavormanage": "",
|
||||
"compute_extension:floating_ip_dns": "",
|
||||
"compute_extension:floating_ip_pools": "",
|
||||
"compute_extension:floating_ips": "",
|
||||
"compute_extension:hosts": "",
|
||||
"compute_extension:hypervisors": "",
|
||||
"compute_extension:instance_usage_audit_log": "",
|
||||
"compute_extension:keypairs": "",
|
||||
"compute_extension:multinic": "",
|
||||
"compute_extension:networks": "",
|
||||
"compute_extension:networks:view": "",
|
||||
"compute_extension:quotas:show": "",
|
||||
"compute_extension:quotas:update": "",
|
||||
"compute_extension:quota_classes": "",
|
||||
"compute_extension:rescue": "",
|
||||
"compute_extension:security_groups": "",
|
||||
"compute_extension:server_diagnostics": "",
|
||||
"compute_extension:simple_tenant_usage:show": "",
|
||||
"compute_extension:simple_tenant_usage:list": "",
|
||||
"compute_extension:users": "",
|
||||
"compute_extension:virtual_interfaces": "",
|
||||
"compute_extension:virtual_storage_arrays": "",
|
||||
"compute_extension:volumes": "",
|
||||
"compute_extension:volumetypes": "",
|
||||
"compute_extension:zones": "",
|
||||
|
||||
|
||||
"volume:create": [],
|
||||
"volume:get": [],
|
||||
"volume:get_all": [],
|
||||
"volume:get_volume_metadata": [],
|
||||
"volume:delete": [],
|
||||
"volume:update": [],
|
||||
"volume:delete_volume_metadata": [],
|
||||
"volume:update_volume_metadata": [],
|
||||
"volume:attach": [],
|
||||
"volume:detach": [],
|
||||
"volume:reserve_volume": [],
|
||||
"volume:unreserve_volume": [],
|
||||
"volume:begin_detaching": [],
|
||||
"volume:roll_detaching": [],
|
||||
"volume:check_attach": [],
|
||||
"volume:check_detach": [],
|
||||
"volume:initialize_connection": [],
|
||||
"volume:terminate_connection": [],
|
||||
"volume:create_snapshot": [],
|
||||
"volume:delete_snapshot": [],
|
||||
"volume:get_snapshot": [],
|
||||
"volume:get_all_snapshots": [],
|
||||
"volume:create": "",
|
||||
"volume:get": "",
|
||||
"volume:get_all": "",
|
||||
"volume:get_volume_metadata": "",
|
||||
"volume:delete": "",
|
||||
"volume:update": "",
|
||||
"volume:delete_volume_metadata": "",
|
||||
"volume:update_volume_metadata": "",
|
||||
"volume:attach": "",
|
||||
"volume:detach": "",
|
||||
"volume:reserve_volume": "",
|
||||
"volume:unreserve_volume": "",
|
||||
"volume:begin_detaching": "",
|
||||
"volume:roll_detaching": "",
|
||||
"volume:check_attach": "",
|
||||
"volume:check_detach": "",
|
||||
"volume:initialize_connection": "",
|
||||
"volume:terminate_connection": "",
|
||||
"volume:create_snapshot": "",
|
||||
"volume:delete_snapshot": "",
|
||||
"volume:get_snapshot": "",
|
||||
"volume:get_all_snapshots": "",
|
||||
|
||||
|
||||
"volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]],
|
||||
"volume_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]],
|
||||
"volume_extension:volume_admin_actions:force_delete": [["rule:admin_api"]],
|
||||
"volume_extension:volume_actions:upload_image": [],
|
||||
"volume_extension:types_manage": [],
|
||||
"volume_extension:types_extra_specs": [],
|
||||
"volume_extension:volume_admin_actions:reset_status": "rule:admin_api",
|
||||
"volume_extension:snapshot_admin_actions:reset_status": "rule:admin_api",
|
||||
"volume_extension:volume_admin_actions:force_delete": "rule:admin_api",
|
||||
"volume_extension:volume_actions:upload_image": "",
|
||||
"volume_extension:types_manage": "",
|
||||
"volume_extension:types_extra_specs": "",
|
||||
|
||||
|
||||
"network:get_all_networks": [],
|
||||
"network:get_network": [],
|
||||
"network:delete_network": [],
|
||||
"network:disassociate_network": [],
|
||||
"network:get_vifs_by_instance": [],
|
||||
"network:allocate_for_instance": [],
|
||||
"network:deallocate_for_instance": [],
|
||||
"network:validate_networks": [],
|
||||
"network:get_instance_uuids_by_ip_filter": [],
|
||||
"network:get_all_networks": "",
|
||||
"network:get_network": "",
|
||||
"network:delete_network": "",
|
||||
"network:disassociate_network": "",
|
||||
"network:get_vifs_by_instance": "",
|
||||
"network:allocate_for_instance": "",
|
||||
"network:deallocate_for_instance": "",
|
||||
"network:validate_networks": "",
|
||||
"network:get_instance_uuids_by_ip_filter": "",
|
||||
|
||||
"network:get_floating_ip": [],
|
||||
"network:get_floating_ip_pools": [],
|
||||
"network:get_floating_ip_by_address": [],
|
||||
"network:get_floating_ips_by_project": [],
|
||||
"network:get_floating_ips_by_fixed_address": [],
|
||||
"network:allocate_floating_ip": [],
|
||||
"network:deallocate_floating_ip": [],
|
||||
"network:associate_floating_ip": [],
|
||||
"network:disassociate_floating_ip": [],
|
||||
"network:get_floating_ip": "",
|
||||
"network:get_floating_ip_pools": "",
|
||||
"network:get_floating_ip_by_address": "",
|
||||
"network:get_floating_ips_by_project": "",
|
||||
"network:get_floating_ips_by_fixed_address": "",
|
||||
"network:allocate_floating_ip": "",
|
||||
"network:deallocate_floating_ip": "",
|
||||
"network:associate_floating_ip": "",
|
||||
"network:disassociate_floating_ip": "",
|
||||
|
||||
"network:get_fixed_ip": [],
|
||||
"network:get_fixed_ip_by_address": [],
|
||||
"network:add_fixed_ip_to_instance": [],
|
||||
"network:remove_fixed_ip_from_instance": [],
|
||||
"network:add_network_to_project": [],
|
||||
"network:get_instance_nw_info": [],
|
||||
"network:get_fixed_ip": "",
|
||||
"network:get_fixed_ip_by_address": "",
|
||||
"network:add_fixed_ip_to_instance": "",
|
||||
"network:remove_fixed_ip_from_instance": "",
|
||||
"network:add_network_to_project": "",
|
||||
"network:get_instance_nw_info": "",
|
||||
|
||||
"network:get_dns_domains": [],
|
||||
"network:add_dns_entry": [],
|
||||
"network:modify_dns_entry": [],
|
||||
"network:delete_dns_entry": [],
|
||||
"network:get_dns_entries_by_address": [],
|
||||
"network:get_dns_entries_by_name": [],
|
||||
"network:create_private_dns_domain": [],
|
||||
"network:create_public_dns_domain": [],
|
||||
"network:delete_dns_domain": []
|
||||
"network:get_dns_domains": "",
|
||||
"network:add_dns_entry": "",
|
||||
"network:modify_dns_entry": "",
|
||||
"network:delete_dns_entry": "",
|
||||
"network:get_dns_entries_by_address": "",
|
||||
"network:get_dns_entries_by_name": "",
|
||||
"network:create_private_dns_domain": "",
|
||||
"network:create_public_dns_domain": "",
|
||||
"network:delete_dns_domain": ""
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user