Add policy checks to Volume.API

Change-Id: If4b37c1041a10c3c0697724281aadb9a17b51373
This commit is contained in:
Brian Waldon 2012-01-12 13:00:45 -08:00
parent 46f90f7cb7
commit ebc06cf9de
4 changed files with 136 additions and 2 deletions

View File

@ -69,5 +69,27 @@
"compute:delete": [["rule:admin_or_owner"]], "compute:delete": [["rule:admin_or_owner"]],
"compute:soft_delete": [["rule:admin_or_owner"]], "compute:soft_delete": [["rule:admin_or_owner"]],
"compute:force_delete": [["rule:admin_or_owner"]], "compute:force_delete": [["rule:admin_or_owner"]],
"compute:restore": [["rule:admin_or_owner"]] "compute:restore": [["rule:admin_or_owner"]],
"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:check_attach": [],
"volume:check_detach": [],
"volume:initialize_connection": [],
"volume:terminate_connection": [],
"volume:create_snapshot": [],
"volume:delete_snapshot": [],
"volume:get_snapshot": [],
"volume:get_all_snapshots": []
} }

View File

@ -66,5 +66,27 @@
"compute:delete": [], "compute:delete": [],
"compute:soft_delete": [], "compute:soft_delete": [],
"compute:force_delete": [], "compute:force_delete": [],
"compute:restore": [] "compute:restore": [],
"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:check_attach": [],
"volume:check_detach": [],
"volume:initialize_connection": [],
"volume:terminate_connection": [],
"volume:create_snapshot": [],
"volume:delete_snapshot": [],
"volume:get_snapshot": [],
"volume:get_all_snapshots": []
} }

View File

@ -27,6 +27,7 @@ from nova import exception
from nova import db from nova import db
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
import nova.policy
from nova import rpc from nova import rpc
from nova import test from nova import test
from nova import utils from nova import utils
@ -399,3 +400,47 @@ class ISCSITestCase(DriverTestCase):
self.mox.UnsetStubs() self.mox.UnsetStubs()
self._detach_volume(volume_id_list) self._detach_volume(volume_id_list)
class VolumePolicyTestCase(test.TestCase):
def setUp(self):
super(VolumePolicyTestCase, self).setUp()
nova.policy.reset()
nova.policy.init()
self.context = context.get_admin_context()
self.volume_api = nova.volume.api.API()
def tearDown(self):
super(VolumePolicyTestCase, self).tearDown()
nova.policy.reset()
def _set_rules(self, rules):
nova.common.policy.set_brain(nova.common.policy.HttpBrain(rules))
def test_check_policy(self):
self.mox.StubOutWithMock(nova.policy, 'enforce')
target = {
'project_id': self.context.project_id,
'user_id': self.context.user_id,
}
nova.policy.enforce(self.context, 'volume:attach', target)
self.mox.ReplayAll()
nova.volume.api.check_policy(self.context, 'attach')
self.mox.UnsetStubs()
self.mox.VerifyAll()
def test_check_policy_with_target(self):
self.mox.StubOutWithMock(nova.policy, 'enforce')
target = {
'project_id': self.context.project_id,
'user_id': self.context.user_id,
'id': 2,
}
nova.policy.enforce(self.context, 'volume:attach', target)
self.mox.ReplayAll()
nova.volume.api.check_policy(self.context, 'attach', {'id': 2})
self.mox.UnsetStubs()
self.mox.VerifyAll()

View File

@ -20,12 +20,14 @@
Handles all requests relating to volumes. Handles all requests relating to volumes.
""" """
import functools
from eventlet import greenthread from eventlet import greenthread
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
import nova.policy
from nova import quota from nova import quota
from nova import rpc from nova import rpc
from nova import utils from nova import utils
@ -37,11 +39,36 @@ flags.DECLARE('storage_availability_zone', 'nova.volume.manager')
LOG = logging.getLogger('nova.volume') LOG = logging.getLogger('nova.volume')
def wrap_check_policy(func):
"""Check policy corresponding to the wrapped methods prior to execution
This decorator requires the first 3 args of the wrapped function
to be (self, context, volume)
"""
@functools.wraps(func)
def wrapped(self, context, target_obj, *args, **kwargs):
check_policy(context, func.__name__, target_obj)
return func(self, context, target_obj, *args, **kwargs)
return wrapped
def check_policy(context, action, target_obj=None):
target = {
'project_id': context.project_id,
'user_id': context.user_id,
}
target.update(target_obj or {})
_action = 'volume:%s' % action
nova.policy.enforce(context, _action, target)
class API(base.Base): class API(base.Base):
"""API for interacting with the volume manager.""" """API for interacting with the volume manager."""
def create(self, context, size, name, description, snapshot=None, def create(self, context, size, name, description, snapshot=None,
volume_type=None, metadata=None, availability_zone=None): volume_type=None, metadata=None, availability_zone=None):
check_policy(context, 'create')
if snapshot is not None: if snapshot is not None:
if snapshot['status'] != "available": if snapshot['status'] != "available":
raise exception.ApiError( raise exception.ApiError(
@ -100,6 +127,7 @@ class API(base.Base):
return return
greenthread.sleep(1) greenthread.sleep(1)
@wrap_check_policy
def delete(self, context, volume): def delete(self, context, volume):
volume_id = volume['id'] volume_id = volume['id']
if volume['status'] != "available": if volume['status'] != "available":
@ -113,14 +141,17 @@ class API(base.Base):
{"method": "delete_volume", {"method": "delete_volume",
"args": {"volume_id": volume_id}}) "args": {"volume_id": volume_id}})
@wrap_check_policy
def update(self, context, volume, fields): def update(self, context, volume, fields):
self.db.volume_update(context, volume['id'], fields) self.db.volume_update(context, volume['id'], fields)
def get(self, context, volume_id): def get(self, context, volume_id):
check_policy(context, 'get', {'id': volume_id})
rv = self.db.volume_get(context, volume_id) rv = self.db.volume_get(context, volume_id)
return dict(rv.iteritems()) return dict(rv.iteritems())
def get_all(self, context, search_opts={}): def get_all(self, context, search_opts={}):
check_policy(context, 'get_all')
if context.is_admin: if context.is_admin:
volumes = self.db.volume_get_all(context) volumes = self.db.volume_get_all(context)
else: else:
@ -161,14 +192,17 @@ class API(base.Base):
return volumes return volumes
def get_snapshot(self, context, snapshot_id): def get_snapshot(self, context, snapshot_id):
check_policy(context, 'get_snapshot')
rv = self.db.snapshot_get(context, snapshot_id) rv = self.db.snapshot_get(context, snapshot_id)
return dict(rv.iteritems()) return dict(rv.iteritems())
def get_all_snapshots(self, context): def get_all_snapshots(self, context):
check_policy(context, 'get_all_snapshots')
if context.is_admin: if context.is_admin:
return self.db.snapshot_get_all(context) return self.db.snapshot_get_all(context)
return self.db.snapshot_get_all_by_project(context, context.project_id) return self.db.snapshot_get_all_by_project(context, context.project_id)
@wrap_check_policy
def check_attach(self, context, volume): def check_attach(self, context, volume):
# TODO(vish): abstract status checking? # TODO(vish): abstract status checking?
if volume['status'] != "available": if volume['status'] != "available":
@ -176,6 +210,7 @@ class API(base.Base):
if volume['attach_status'] == "attached": if volume['attach_status'] == "attached":
raise exception.ApiError(_("Volume is already attached")) raise exception.ApiError(_("Volume is already attached"))
@wrap_check_policy
def check_detach(self, context, volume): def check_detach(self, context, volume):
# TODO(vish): abstract status checking? # TODO(vish): abstract status checking?
if volume['status'] == "available": if volume['status'] == "available":
@ -189,6 +224,7 @@ class API(base.Base):
"args": {'instance_id': instance_id, "args": {'instance_id': instance_id,
'volume_id': volume['id']}}) 'volume_id': volume['id']}})
@wrap_check_policy
def attach(self, context, volume, instance_id, mountpoint): def attach(self, context, volume, instance_id, mountpoint):
host = volume['host'] host = volume['host']
queue = self.db.queue_get_for(context, FLAGS.volume_topic, host) queue = self.db.queue_get_for(context, FLAGS.volume_topic, host)
@ -198,6 +234,7 @@ class API(base.Base):
"instance_id": instance_id, "instance_id": instance_id,
"mountpoint": mountpoint}}) "mountpoint": mountpoint}})
@wrap_check_policy
def detach(self, context, volume): def detach(self, context, volume):
host = volume['host'] host = volume['host']
queue = self.db.queue_get_for(context, FLAGS.volume_topic, host) queue = self.db.queue_get_for(context, FLAGS.volume_topic, host)
@ -205,6 +242,7 @@ class API(base.Base):
{"method": "detach_volume", {"method": "detach_volume",
"args": {"volume_id": volume['id']}}) "args": {"volume_id": volume['id']}})
@wrap_check_policy
def initialize_connection(self, context, volume, address): def initialize_connection(self, context, volume, address):
host = volume['host'] host = volume['host']
queue = self.db.queue_get_for(context, FLAGS.volume_topic, host) queue = self.db.queue_get_for(context, FLAGS.volume_topic, host)
@ -213,6 +251,7 @@ class API(base.Base):
"args": {"volume_id": volume['id'], "args": {"volume_id": volume['id'],
"address": address}}) "address": address}})
@wrap_check_policy
def terminate_connection(self, context, volume, address): def terminate_connection(self, context, volume, address):
host = volume['host'] host = volume['host']
queue = self.db.queue_get_for(context, FLAGS.volume_topic, host) queue = self.db.queue_get_for(context, FLAGS.volume_topic, host)
@ -223,6 +262,8 @@ class API(base.Base):
def _create_snapshot(self, context, volume, name, description, def _create_snapshot(self, context, volume, name, description,
force=False): force=False):
check_policy(context, 'create_snapshot')
if ((not force) and (volume['status'] != "available")): if ((not force) and (volume['status'] != "available")):
raise exception.ApiError(_("Volume status must be available")) raise exception.ApiError(_("Volume status must be available"))
@ -253,6 +294,7 @@ class API(base.Base):
return self._create_snapshot(context, volume, name, description, return self._create_snapshot(context, volume, name, description,
True) True)
@wrap_check_policy
def delete_snapshot(self, context, snapshot): def delete_snapshot(self, context, snapshot):
if snapshot['status'] != "available": if snapshot['status'] != "available":
raise exception.ApiError(_("Snapshot status must be available")) raise exception.ApiError(_("Snapshot status must be available"))
@ -264,15 +306,18 @@ class API(base.Base):
"args": {"topic": FLAGS.volume_topic, "args": {"topic": FLAGS.volume_topic,
"snapshot_id": snapshot['id']}}) "snapshot_id": snapshot['id']}})
@wrap_check_policy
def get_volume_metadata(self, context, volume): def get_volume_metadata(self, context, volume):
"""Get all metadata associated with a volume.""" """Get all metadata associated with a volume."""
rv = self.db.volume_metadata_get(context, volume['id']) rv = self.db.volume_metadata_get(context, volume['id'])
return dict(rv.iteritems()) return dict(rv.iteritems())
@wrap_check_policy
def delete_volume_metadata(self, context, volume, key): def delete_volume_metadata(self, context, volume, key):
"""Delete the given metadata item from an volume.""" """Delete the given metadata item from an volume."""
self.db.volume_metadata_delete(context, volume['id'], key) self.db.volume_metadata_delete(context, volume['id'], key)
@wrap_check_policy
def update_volume_metadata(self, context, volume, metadata, delete=False): def update_volume_metadata(self, context, volume, metadata, delete=False):
"""Updates or creates volume metadata. """Updates or creates volume metadata.