summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorMichael Gundlach <michael.gundlach@rackspace.com>2010-09-01 12:42:06 -0400
committerMichael Gundlach <michael.gundlach@rackspace.com>2010-09-01 12:42:06 -0400
commit83df968cfb050bdb6bac981dfcc2d0b1c3dd80db (patch)
treedd8763a3fe358c5bca2c852c3b31bbf18f33a079 /nova
parent8de182446993ac24e7b8fba12342f8adb3e179d4 (diff)
downloadnova-83df968cfb050bdb6bac981dfcc2d0b1c3dd80db.tar.gz
nova-83df968cfb050bdb6bac981dfcc2d0b1c3dd80db.tar.xz
nova-83df968cfb050bdb6bac981dfcc2d0b1c3dd80db.zip
Delete rbac.py, moving @rbac decorator knowledge into api.ec2.Authorizer WSGI middleware.
Diffstat (limited to 'nova')
-rw-r--r--nova/api/ec2/__init__.py64
-rw-r--r--nova/api/ec2/admin.py31
-rw-r--r--nova/api/ec2/cloud.py30
-rw-r--r--nova/auth/rbac.py69
4 files changed, 60 insertions, 134 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 87a72ca7c..aee9915d0 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -122,14 +122,70 @@ class Router(wsgi.Application):
class Authorization(wsgi.Middleware):
"""
- Verify that ec2.controller and ec2.action in WSGI environ may be executed
- in ec2.context.
+ Return a 401 if ec2.controller and ec2.action in WSGI environ may not be
+ executed in ec2.context.
"""
+ def __init__(self, application):
+ super(Authorization, self).__init__(application)
+ self.action_roles = {
+ 'CloudController': {
+ 'DescribeAvailabilityzones': ['all'],
+ 'DescribeRegions': ['all'],
+ 'DescribeSnapshots': ['all'],
+ 'DescribeKeyPairs': ['all'],
+ 'CreateKeyPair': ['all'],
+ 'DeleteKeyPair': ['all'],
+ 'DescribeSecurityGroups': ['all'],
+ 'CreateSecurityGroup': ['netadmin'],
+ 'DeleteSecurityGroup': ['netadmin'],
+ 'GetConsoleOutput': ['projectmanager', 'sysadmin'],
+ 'DescribeVolumes': ['projectmanager', 'sysadmin'],
+ 'CreateVolume': ['projectmanager', 'sysadmin'],
+ 'AttachVolume': ['projectmanager', 'sysadmin'],
+ 'DetachVolume': ['projectmanager', 'sysadmin'],
+ 'DescribeInstances': ['all'],
+ 'DescribeAddresses': ['all'],
+ 'AllocateAddress': ['netadmin'],
+ 'ReleaseAddress': ['netadmin'],
+ 'AssociateAddress': ['netadmin'],
+ 'DisassociateAddress': ['netadmin'],
+ 'RunInstances': ['projectmanager', 'sysadmin'],
+ 'TerminateInstances': ['projectmanager', 'sysadmin'],
+ 'RebootInstances': ['projectmanager', 'sysadmin'],
+ 'DeleteVolume': ['projectmanager', 'sysadmin'],
+ 'DescribeImages': ['all'],
+ 'DeregisterImage': ['projectmanager', 'sysadmin'],
+ 'RegisterImage': ['projectmanager', 'sysadmin'],
+ 'DescribeImageAttribute': ['all'],
+ 'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
+ },
+ 'AdminController': {
+ # All actions have the same permission: [] (the default)
+ # admins will be allowed to run them
+ # all others will get HTTPUnauthorized.
+ },
+ }
+
@webob.dec.wsgify
def __call__(self, req):
- #TODO(gundlach): put rbac information here.
- return self.application
+ context = req.environ['ec2.context']
+ controller_name = req.environ['ec2.controller'].__name__
+ action = req.environ['ec2.action']
+ allowed_roles = self.action_roles[controller_name].get(action, [])
+ if self._matches_any_role(context, allowed_roles):
+ return self.application
+ else:
+ raise webob.exc.HTTPUnauthorized()
+
+ def _matches_any_role(self, context, roles):
+ """Return True if any role in roles is allowed in context."""
+ if 'all' in roles:
+ return True
+ if 'none' in roles:
+ return False
+ return any(context.project.has_role(context.user.id, role)
+ for role in roles)
class Executor(wsg.Application):
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index d6f622755..f0c643bbd 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -57,46 +57,27 @@ def host_dict(host):
return {}
-def admin_only(target):
- """Decorator for admin-only API calls"""
- def wrapper(*args, **kwargs):
- """Internal wrapper method for admin-only API calls"""
- context = args[1]
- if context.user.is_admin():
- return target(*args, **kwargs)
- else:
- return {}
-
- return wrapper
-
-
class AdminController(object):
"""
API Controller for users, hosts, nodes, and workers.
- Trivial admin_only wrapper will be replaced with RBAC,
- allowing project managers to administer project users.
"""
def __str__(self):
return 'AdminController'
- @admin_only
def describe_user(self, _context, name, **_kwargs):
"""Returns user data, including access and secret keys."""
return user_dict(manager.AuthManager().get_user(name))
- @admin_only
def describe_users(self, _context, **_kwargs):
"""Returns all users - should be changed to deal with a list."""
return {'userSet':
[user_dict(u) for u in manager.AuthManager().get_users()] }
- @admin_only
def register_user(self, _context, name, **_kwargs):
"""Creates a new user, and returns generated credentials."""
return user_dict(manager.AuthManager().create_user(name))
- @admin_only
def deregister_user(self, _context, name, **_kwargs):
"""Deletes a single user (NOT undoable.)
Should throw an exception if the user has instances,
@@ -106,13 +87,11 @@ class AdminController(object):
return True
- @admin_only
def describe_roles(self, context, project_roles=True, **kwargs):
"""Returns a list of allowed roles."""
roles = manager.AuthManager().get_roles(project_roles)
return { 'roles': [{'role': r} for r in roles]}
- @admin_only
def describe_user_roles(self, context, user, project=None, **kwargs):
"""Returns a list of roles for the given user.
Omitting project will return any global roles that the user has.
@@ -121,7 +100,6 @@ class AdminController(object):
roles = manager.AuthManager().get_user_roles(user, project=project)
return { 'roles': [{'role': r} for r in roles]}
- @admin_only
def modify_user_role(self, context, user, role, project=None,
operation='add', **kwargs):
"""Add or remove a role for a user and project."""
@@ -134,7 +112,6 @@ class AdminController(object):
return True
- @admin_only
def generate_x509_for_user(self, _context, name, project=None, **kwargs):
"""Generates and returns an x509 certificate for a single user.
Is usually called from a client that will wrap this with
@@ -146,19 +123,16 @@ class AdminController(object):
user = manager.AuthManager().get_user(name)
return user_dict(user, base64.b64encode(project.get_credentials(user)))
- @admin_only
def describe_project(self, context, name, **kwargs):
"""Returns project data, including member ids."""
return project_dict(manager.AuthManager().get_project(name))
- @admin_only
def describe_projects(self, context, user=None, **kwargs):
"""Returns all projects - should be changed to deal with a list."""
return {'projectSet':
[project_dict(u) for u in
manager.AuthManager().get_projects(user=user)]}
- @admin_only
def register_project(self, context, name, manager_user, description=None,
member_users=None, **kwargs):
"""Creates a new project"""
@@ -169,20 +143,17 @@ class AdminController(object):
description=None,
member_users=None))
- @admin_only
def deregister_project(self, context, name):
"""Permanently deletes a project."""
manager.AuthManager().delete_project(name)
return True
- @admin_only
def describe_project_members(self, context, name, **kwargs):
project = manager.AuthManager().get_project(name)
result = {
'members': [{'member': m} for m in project.member_ids]}
return result
- @admin_only
def modify_project_member(self, context, user, project, operation, **kwargs):
"""Add or remove a user from a project."""
if operation =='add':
@@ -193,7 +164,6 @@ class AdminController(object):
raise exception.ApiError('operation must be add or remove')
return True
- @admin_only
def describe_hosts(self, _context, **_kwargs):
"""Returns status info for all nodes. Includes:
* Disk Space
@@ -205,7 +175,6 @@ class AdminController(object):
"""
return {'hostSet': [host_dict(h) for h in model.Host.all()]}
- @admin_only
def describe_host(self, _context, name, **_kwargs):
"""Returns status info for single node."""
return host_dict(model.Host.lookup(name))
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 05fbf3861..566887c1a 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -32,7 +32,6 @@ from nova import exception
from nova import flags
from nova import rpc
from nova import utils
-from nova.auth import rbac
from nova.auth import manager
from nova.compute import model
from nova.compute.instance_types import INSTANCE_TYPES
@@ -163,18 +162,15 @@ class CloudController(object):
data['product-codes'] = i['product_codes']
return data
- @rbac.allow('all')
def describe_availability_zones(self, context, **kwargs):
return {'availabilityZoneInfo': [{'zoneName': 'nova',
'zoneState': 'available'}]}
- @rbac.allow('all')
def describe_regions(self, context, region_name=None, **kwargs):
# TODO(vish): region_name is an array. Support filtering
return {'regionInfo': [{'regionName': 'nova',
'regionUrl': FLAGS.ec2_url}]}
- @rbac.allow('all')
def describe_snapshots(self,
context,
snapshot_id=None,
@@ -190,7 +186,6 @@ class CloudController(object):
'volumeSize': 0,
'description': 'fixme'}]}
- @rbac.allow('all')
def describe_key_pairs(self, context, key_name=None, **kwargs):
key_pairs = context.user.get_key_pairs()
if not key_name is None:
@@ -208,35 +203,29 @@ class CloudController(object):
return {'keypairsSet': result}
- @rbac.allow('all')
def create_key_pair(self, context, key_name, **kwargs):
data = _gen_key(context.user.id, key_name)
return {'keyName': key_name,
'keyFingerprint': data['fingerprint'],
'keyMaterial': data['private_key']}
- @rbac.allow('all')
def delete_key_pair(self, context, key_name, **kwargs):
context.user.delete_key_pair(key_name)
# aws returns true even if the key doens't exist
return True
- @rbac.allow('all')
def describe_security_groups(self, context, group_names, **kwargs):
groups = {'securityGroupSet': []}
# Stubbed for now to unblock other things.
return groups
- @rbac.allow('netadmin')
def create_security_group(self, context, group_name, **kwargs):
return True
- @rbac.allow('netadmin')
def delete_security_group(self, context, group_name, **kwargs):
return True
- @rbac.allow('projectmanager', 'sysadmin')
def get_console_output(self, context, instance_id, **kwargs):
# instance_id is passed in as a list of instances
instance = self._get_instance(context, instance_id[0])
@@ -250,7 +239,6 @@ class CloudController(object):
else:
return None
- @rbac.allow('projectmanager', 'sysadmin')
def describe_volumes(self, context, **kwargs):
volumes = []
for volume in self.volumes:
@@ -284,7 +272,6 @@ class CloudController(object):
v['attachmentSet'] = [{}]
return v
- @rbac.allow('projectmanager', 'sysadmin')
def create_volume(self, context, size, **kwargs):
# TODO(vish): refactor this to create the volume object here and tell service to create it
result = rpc.call(FLAGS.volume_topic, {"method": "create_volume",
@@ -324,7 +311,6 @@ class CloudController(object):
return volume
raise exception.NotFound('Volume %s could not be found' % volume_id)
- @rbac.allow('projectmanager', 'sysadmin')
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
volume = self._get_volume(context, volume_id)
if volume['status'] == "attached":
@@ -348,7 +334,6 @@ class CloudController(object):
'status': volume['attach_status'],
'volumeId': volume_id}
- @rbac.allow('projectmanager', 'sysadmin')
def detach_volume(self, context, volume_id, **kwargs):
volume = self._get_volume(context, volume_id)
instance_id = volume.get('instance_id', None)
@@ -381,7 +366,6 @@ class CloudController(object):
lst = [lst]
return [{label: x} for x in lst]
- @rbac.allow('all')
def describe_instances(self, context, **kwargs):
return self._format_describe_instances(context)
@@ -442,7 +426,6 @@ class CloudController(object):
return list(reservations.values())
- @rbac.allow('all')
def describe_addresses(self, context, **kwargs):
return self.format_addresses(context)
@@ -465,7 +448,6 @@ class CloudController(object):
addresses.append(address_rv)
return {'addressesSet': addresses}
- @rbac.allow('netadmin')
def allocate_address(self, context, **kwargs):
network_topic = self._get_network_topic(context)
public_ip = rpc.call(network_topic,
@@ -474,7 +456,6 @@ class CloudController(object):
"project_id": context.project.id}})
return {'addressSet': [{'publicIp': public_ip}]}
- @rbac.allow('netadmin')
def release_address(self, context, public_ip, **kwargs):
# NOTE(vish): Should we make sure this works?
network_topic = self._get_network_topic(context)
@@ -483,7 +464,6 @@ class CloudController(object):
"args": {"elastic_ip": public_ip}})
return {'releaseResponse': ["Address released."]}
- @rbac.allow('netadmin')
def associate_address(self, context, instance_id, public_ip, **kwargs):
instance = self._get_instance(context, instance_id)
address = self._get_address(context, public_ip)
@@ -495,7 +475,6 @@ class CloudController(object):
"instance_id": instance['instance_id']}})
return {'associateResponse': ["Address associated."]}
- @rbac.allow('netadmin')
def disassociate_address(self, context, public_ip, **kwargs):
address = self._get_address(context, public_ip)
network_topic = self._get_network_topic(context)
@@ -514,7 +493,6 @@ class CloudController(object):
"project_id": context.project.id}})
return '%s.%s' %(FLAGS.network_topic, host)
- @rbac.allow('projectmanager', 'sysadmin')
def run_instances(self, context, **kwargs):
# make sure user can access the image
# vpn image is private so it doesn't show up on lists
@@ -587,7 +565,6 @@ class CloudController(object):
# TODO: Make Network figure out the network name from ip.
return self._format_run_instances(context, reservation_id)
- @rbac.allow('projectmanager', 'sysadmin')
def terminate_instances(self, context, instance_id, **kwargs):
logging.debug("Going to start terminating instances")
network_topic = self._get_network_topic(context)
@@ -628,7 +605,6 @@ class CloudController(object):
instance.destroy()
return True
- @rbac.allow('projectmanager', 'sysadmin')
def reboot_instances(self, context, instance_id, **kwargs):
"""instance_id is a list of instance ids"""
for i in instance_id:
@@ -638,7 +614,6 @@ class CloudController(object):
"args": {"instance_id": i}})
return True
- @rbac.allow('projectmanager', 'sysadmin')
def delete_volume(self, context, volume_id, **kwargs):
# TODO: return error if not authorized
volume = self._get_volume(context, volume_id)
@@ -648,19 +623,16 @@ class CloudController(object):
"args": {"volume_id": volume_id}})
return True
- @rbac.allow('all')
def describe_images(self, context, image_id=None, **kwargs):
# The objectstore does its own authorization for describe
imageSet = images.list(context, image_id)
return {'imagesSet': imageSet}
- @rbac.allow('projectmanager', 'sysadmin')
def deregister_image(self, context, image_id, **kwargs):
# FIXME: should the objectstore be doing these authorization checks?
images.deregister(context, image_id)
return {'imageId': image_id}
- @rbac.allow('projectmanager', 'sysadmin')
def register_image(self, context, image_location=None, **kwargs):
# FIXME: should the objectstore be doing these authorization checks?
if image_location is None and kwargs.has_key('name'):
@@ -670,7 +642,6 @@ class CloudController(object):
return {'imageId': image_id}
- @rbac.allow('all')
def describe_image_attribute(self, context, image_id, attribute, **kwargs):
if attribute != 'launchPermission':
raise exception.ApiError('attribute not supported: %s' % attribute)
@@ -683,7 +654,6 @@ class CloudController(object):
result['launchPermission'].append({'group': 'all'})
return result
- @rbac.allow('projectmanager', 'sysadmin')
def modify_image_attribute(self, context, image_id, attribute, operation_type, **kwargs):
# TODO(devcamcar): Support users and groups other than 'all'.
if attribute != 'launchPermission':
diff --git a/nova/auth/rbac.py b/nova/auth/rbac.py
deleted file mode 100644
index d157f44b3..000000000
--- a/nova/auth/rbac.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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.
-
-"""Role-based access control decorators to use fpr wrapping other
-methods with."""
-
-from nova import exception
-
-
-def allow(*roles):
- """Allow the given roles access the wrapped function."""
-
- def wrap(func): # pylint: disable-msg=C0111
-
- def wrapped_func(self, context, *args,
- **kwargs): # pylint: disable-msg=C0111
- if context.user.is_superuser():
- return func(self, context, *args, **kwargs)
- for role in roles:
- if __matches_role(context, role):
- return func(self, context, *args, **kwargs)
- raise exception.NotAuthorized()
-
- return wrapped_func
-
- return wrap
-
-
-def deny(*roles):
- """Deny the given roles access the wrapped function."""
-
- def wrap(func): # pylint: disable-msg=C0111
-
- def wrapped_func(self, context, *args,
- **kwargs): # pylint: disable-msg=C0111
- if context.user.is_superuser():
- return func(self, context, *args, **kwargs)
- for role in roles:
- if __matches_role(context, role):
- raise exception.NotAuthorized()
- return func(self, context, *args, **kwargs)
-
- return wrapped_func
-
- return wrap
-
-
-def __matches_role(context, role):
- """Check if a role is allowed."""
- if role == 'all':
- return True
- if role == 'none':
- return False
- return context.project.has_role(context.user.id, role)