Skip to content

Commit

Permalink
Hack in prefix matching for project_id in nova
Browse files Browse the repository at this point in the history
This is example code to show how a nested scope would allow for
multitenancy in nova. The basic idea is to store the fully nested
scope of an object in the project_id field. In other words, the
owner of a given instance would be:

company.unit.project

For listing/actions we can do prefix matching of the current ownership
scope. This patch expects the full scope to be passed in by project_id.
The real version of this should probably pass scope
in a new keystone context field.

Note that this also hacks in a new policy matcher which allows for
a prefix match from the creds dictionary against the target object.
It looks like:

prefix:project_id:%(project_id)s

This says: make sure the context project_id is a prefix of project_id
in the target object. If we had a scoping field from keystone called
ownership, this could be something like:

prefix:ownership:%(project_id)s

This will obviously need to be added to oslo in the full version.

Change-Id: I0ab6f65d55608603499b48ac74d2a467ffcaa93b
  • Loading branch information
vishvananda committed Feb 3, 2014
1 parent 4b2bd32 commit ae4de19
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 4 deletions.
2 changes: 1 addition & 1 deletion etc/nova/policy.json
@@ -1,6 +1,6 @@
{
"context_is_admin": "role:admin",
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
"admin_or_owner": "is_admin:True or prefix:project_id:%(project_id)s",
"default": "rule:admin_or_owner",

"cells_scheduler_filter:TargetCellFilter": "is_admin:True",
Expand Down
32 changes: 29 additions & 3 deletions nova/db/sqlalchemy/api.py
Expand Up @@ -224,13 +224,14 @@ def issubclassof_nova_base(obj):
raise Exception(_("Unrecognized read_deleted value '%s'")
% read_deleted)

like = '%s%%' % context.project_id
if nova.context.is_user_context(context) and project_only:
if project_only == 'allow_none':
query = query.\
filter(or_(base_model.project_id == context.project_id,
filter(or_(base_model.project_id.like(like),
base_model.project_id == None))
else:
query = query.filter_by(project_id=context.project_id)
query = query.filter(base_model.project_id.like(like))

return query

Expand Down Expand Up @@ -1931,11 +1932,14 @@ def instance_get_all_by_filters(context, filters, sort_key, sort_dir,

# Filters for exact matches that we can do along with the SQL query...
# For other filters that don't match this, we will do regexp matching
exact_match_filter_names = ['project_id', 'user_id', 'image_ref',
exact_match_filter_names = ['user_id', 'image_ref',
'vm_state', 'instance_type_id', 'uuid',
'metadata', 'host', 'task_state',
'system_metadata']

query_prefix = prefix_filter(query_prefix, models.Instance,
{'project_id': filters['project_id']})
filters.pop('project_id')
# Filter the query
query_prefix = exact_filter(query_prefix, models.Instance,
filters, exact_match_filter_names)
Expand Down Expand Up @@ -2051,6 +2055,28 @@ def regex_filter(query, model, filters):
str(filters[filter_name])))
return query

def prefix_filter(query, model, filters):
"""Applies prefix filtering to a query.
Returns the updated query.
:param query: query to apply filters to
:param model: model object the query applies to
:param filters: dictionary of filters with string prefixes
"""

for filter_name in filters.iterkeys():
try:
column_attr = getattr(model, filter_name)
except AttributeError:
continue
if 'property' == type(column_attr).__name__:
continue
like = '%s%%' % filters[filter_name]
query = query.filter(column_attr.like(like))
return query



@require_context
def instance_get_active_by_window_joined(context, begin, end=None,
Expand Down
11 changes: 11 additions & 0 deletions nova/openstack/common/policy.py
Expand Up @@ -742,6 +742,17 @@ def __call__(self, target, creds):
return self.match.lower() in [x.lower() for x in creds['roles']]


@register("prefix")
class PrefixCheck(Check):
def __call__(self, target, creds):
"""Check that the target starts with item in the creds dict."""
source, _, match = self.match.partition(':')
match = match % target
if source in creds:
return match.startswith(unicode(creds[source]))
return False


@register('http')
class HttpCheck(Check):
def __call__(self, target, creds):
Expand Down

0 comments on commit ae4de19

Please sign in to comment.