Add policy checks to Compute.API

* Second step of blueprint interim-nova-authz-service
 * Adds policy.json to define policy
 * Add nova.policy.wrap_enforce decorator
 * wrap majority of compute api functions with wrap_enforce

Change-Id: If6702873db3249921f931a42e889ee7d0338e4b8
This commit is contained in:
Brian Waldon 2011-12-30 13:11:56 -08:00
parent e0680250c0
commit ace0252d75
8 changed files with 363 additions and 38 deletions

73
etc/nova/policy.json Normal file
View File

@ -0,0 +1,73 @@
{
"admin_or_owner": [["role:admin"], ["project_id:%(project_id)s"]],
"compute:create": [["rule:admin_or_owner"]],
"compute:create:attach_network": [["rule:admin_or_owner"]],
"compute:create:attach_volume": [["rule:admin_or_owner"]],
"compute:get": [["rule:admin_or_owner"]],
"compute:get_all" :[],
"compute:update": [["rule:admin_or_owner"]],
"compute:get_instance_metadata": [["rule:admin_or_owner"]],
"compute:update_instance_metadata": [["rule:admin_or_owner"]],
"compute:delete_instance_metadata": [["rule:admin_or_owner"]],
"compute:get_instance_faults": [["rule:admin_or_owner"]],
"compute:get_actions": [["rule:admin_or_owner"]],
"compute:get_diagnostics": [["rule:admin_or_owner"]],
"compute:get_lock": [["rule:admin_or_owner"]],
"compute:lock": [["rule:admin_or_owner"]],
"compute:unlock": [["rule:admin_or_owner"]],
"compute:get_ajax_console": [["rule:admin_or_owner"]],
"compute:get_vnc_console": [["rule:admin_or_owner"]],
"compute:get_console_output": [["rule:admin_or_owner"]],
"compute:associate_floating_ip": [["rule:admin_or_owner"]],
"compute:reset_network": [["rule:admin_or_owner"]],
"compute:inject_network_info": [["rule:admin_or_owner"]],
"compute:add_fixed_ip": [["rule:admin_or_owner"]],
"compute:remove_fixed_ip": [["rule:admin_or_owner"]],
"compute:attach_volume": [["rule:admin_or_owner"]],
"compute:detach_volume": [["rule:admin_or_owner"]],
"compute:inject_file": [["rule:admin_or_owner"]],
"compute:set_admin_password": [["rule:admin_or_owner"]],
"compute:rescue": [["rule:admin_or_owner"]],
"compute:unrescue": [["rule:admin_or_owner"]],
"compute:suspend": [["rule:admin_or_owner"]],
"compute:resume": [["rule:admin_or_owner"]],
"compute:pause": [["rule:admin_or_owner"]],
"compute:unpause": [["rule:admin_or_owner"]],
"compute:start": [["rule:admin_or_owner"]],
"compute:stop": [["rule:admin_or_owner"]],
"compute:resize": [["rule:admin_or_owner"]],
"compute:confirm_resize": [["rule:admin_or_owner"]],
"compute:revert_resize": [["rule:admin_or_owner"]],
"compute:rebuild": [["rule:admin_or_owner"]],
"compute:reboot": [["rule:admin_or_owner"]],
"compute:snapshot": [["rule:admin_or_owner"]],
"compute:backup": [["rule:admin_or_owner"]],
"compute:add_security_group": [["rule:admin_or_owner"]],
"compute:remove_security_group": [["rule:admin_or_owner"]],
"compute:delete": [["rule:admin_or_owner"]],
"compute:soft_delete": [["rule:admin_or_owner"]],
"compute:force_delete": [["rule:admin_or_owner"]],
"compute:restore": [["rule:admin_or_owner"]]
}

View File

@ -22,7 +22,7 @@ import urllib
import urllib2 import urllib2
class NotAllowed(Exception): class NotAuthorized(Exception):
pass pass
@ -91,14 +91,14 @@ def enforce(match_list, target_dict, credentials_dict):
Credentials dicts contain as much information as we can about the user Credentials dicts contain as much information as we can about the user
performing the action. performing the action.
:raises NotAllowed if the check fails :raises NotAuthorized if the check fails
""" """
global _BRAIN global _BRAIN
if not _BRAIN: if not _BRAIN:
_BRAIN = Brain() _BRAIN = Brain()
if not _BRAIN.check(match_list, target_dict, credentials_dict): if not _BRAIN.check(match_list, target_dict, credentials_dict):
raise NotAllowed() raise NotAuthorized()
class Brain(object): class Brain(object):

View File

@ -37,6 +37,7 @@ from nova import flags
import nova.image import nova.image
from nova import log as logging from nova import log as logging
from nova import network from nova import network
from nova import policy
from nova import quota from nova import quota
from nova import rpc from nova import rpc
from nova.scheduler import api as scheduler_api from nova.scheduler import api as scheduler_api
@ -88,6 +89,20 @@ def check_instance_state(vm_state=None, task_state=None):
return outer return outer
def wrap_check_policy(func):
"""Check corresponding policy prior of wrapped method to execution"""
@functools.wraps(func)
def wrapped(self, context, target, *args, **kwargs):
check_policy(context, func.__name__, target)
return func(self, context, target, *args, **kwargs)
return wrapped
def check_policy(context, action, target):
_action = 'compute:%s' % action
nova.policy.enforce(context, _action, target)
class API(base.Base): class API(base.Base):
"""API for interacting with the compute manager.""" """API for interacting with the compute manager."""
@ -426,6 +441,8 @@ class API(base.Base):
self.db.block_device_mapping_update_or_create(elevated_context, self.db.block_device_mapping_update_or_create(elevated_context,
values) values)
#NOTE(bcwaldon): No policy check since this is only used by scheduler and
# the compute api. That should probably be cleaned up, though.
def create_db_entry_for_new_instance(self, context, instance_type, image, def create_db_entry_for_new_instance(self, context, instance_type, image,
base_options, security_group, block_device_mapping, num=1): base_options, security_group, block_device_mapping, num=1):
"""Create an entry in the DB for this new instance, """Create an entry in the DB for this new instance,
@ -552,6 +569,16 @@ class API(base.Base):
could be 'None' or a list of instance dicts depending on if could be 'None' or a list of instance dicts depending on if
we waited for information from the scheduler or not. we waited for information from the scheduler or not.
""" """
target = {'project_id': context.project_id,
'user_id': context.user_id,
'availability_zone': availability_zone}
check_policy(context, 'create', target)
if requested_networks:
check_policy(context, 'create:attach_network', target)
if block_device_mapping:
check_policy(context, 'create:attach_volume', target)
# We can create the DB entry for the instance here if we're # We can create the DB entry for the instance here if we're
# only going to create 1 instance and we're in a single # only going to create 1 instance and we're in a single
@ -588,16 +615,6 @@ class API(base.Base):
return (inst_ret_list, reservation_id) return (inst_ret_list, reservation_id)
def has_finished_migration(self, context, instance_uuid):
"""Returns true if an instance has a finished migration."""
try:
self.db.migration_get_by_instance_and_status(context,
instance_uuid,
'finished')
return True
except exception.NotFound:
return False
def ensure_default_security_group(self, context): def ensure_default_security_group(self, context):
"""Ensure that a context has a security group. """Ensure that a context has a security group.
@ -705,6 +722,7 @@ class API(base.Base):
return False return False
@wrap_check_policy
def add_security_group(self, context, instance, security_group_name): def add_security_group(self, context, instance, security_group_name):
"""Add security group to the instance""" """Add security group to the instance"""
security_group = self.db.security_group_get_by_name(context, security_group = self.db.security_group_get_by_name(context,
@ -733,6 +751,7 @@ class API(base.Base):
{"method": "refresh_security_group_rules", {"method": "refresh_security_group_rules",
"args": {"security_group_id": security_group['id']}}) "args": {"security_group_id": security_group['id']}})
@wrap_check_policy
def remove_security_group(self, context, instance, security_group_name): def remove_security_group(self, context, instance, security_group_name):
"""Remove the security group associated with the instance""" """Remove the security group associated with the instance"""
security_group = self.db.security_group_get_by_name(context, security_group = self.db.security_group_get_by_name(context,
@ -761,6 +780,7 @@ class API(base.Base):
{"method": "refresh_security_group_rules", {"method": "refresh_security_group_rules",
"args": {"security_group_id": security_group['id']}}) "args": {"security_group_id": security_group['id']}})
@wrap_check_policy
@scheduler_api.reroute_compute("update") @scheduler_api.reroute_compute("update")
def update(self, context, instance, **kwargs): def update(self, context, instance, **kwargs):
"""Updates the instance in the datastore. """Updates the instance in the datastore.
@ -776,6 +796,7 @@ class API(base.Base):
rv = self.db.instance_update(context, instance["id"], kwargs) rv = self.db.instance_update(context, instance["id"], kwargs)
return dict(rv.iteritems()) return dict(rv.iteritems())
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF, @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
vm_states.ERROR]) vm_states.ERROR])
@scheduler_api.reroute_compute("soft_delete") @scheduler_api.reroute_compute("soft_delete")
@ -821,6 +842,7 @@ class API(base.Base):
# NOTE(jerdfelt): The API implies that only ACTIVE and ERROR are # NOTE(jerdfelt): The API implies that only ACTIVE and ERROR are
# allowed but the EC2 API appears to allow from RESCUED and STOPPED # allowed but the EC2 API appears to allow from RESCUED and STOPPED
# too # too
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF, @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
vm_states.ERROR, vm_states.RESCUED, vm_states.ERROR, vm_states.RESCUED,
vm_states.STOPPED]) vm_states.STOPPED])
@ -834,6 +856,7 @@ class API(base.Base):
self._delete(context, instance) self._delete(context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.SOFT_DELETE]) @check_instance_state(vm_state=[vm_states.SOFT_DELETE])
@scheduler_api.reroute_compute("restore") @scheduler_api.reroute_compute("restore")
def restore(self, context, instance): def restore(self, context, instance):
@ -852,12 +875,14 @@ class API(base.Base):
self._cast_compute_message('power_on_instance', context, self._cast_compute_message('power_on_instance', context,
instance['uuid'], host) instance['uuid'], host)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.SOFT_DELETE]) @check_instance_state(vm_state=[vm_states.SOFT_DELETE])
@scheduler_api.reroute_compute("force_delete") @scheduler_api.reroute_compute("force_delete")
def force_delete(self, context, instance): def force_delete(self, context, instance):
"""Force delete a previously deleted (but not reclaimed) instance.""" """Force delete a previously deleted (but not reclaimed) instance."""
self._delete(context, instance) self._delete(context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF, @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
vm_states.RESCUED], vm_states.RESCUED],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@ -884,6 +909,7 @@ class API(base.Base):
else: else:
self._call_compute_message('stop_instance', context, instance) self._call_compute_message('stop_instance', context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.STOPPED, vm_states.SHUTOFF]) @check_instance_state(vm_state=[vm_states.STOPPED, vm_states.SHUTOFF])
def start(self, context, instance): def start(self, context, instance):
"""Start an instance.""" """Start an instance."""
@ -915,11 +941,14 @@ class API(base.Base):
"args": {"topic": FLAGS.compute_topic, "args": {"topic": FLAGS.compute_topic,
"instance_uuid": instance_uuid}}) "instance_uuid": instance_uuid}})
#NOTE(bcwaldon): no policy check here since it should be rolled in to
# search_opts in get_all
def get_active_by_window(self, context, begin, end=None, project_id=None): def get_active_by_window(self, context, begin, end=None, project_id=None):
"""Get instances that were continuously active over a window.""" """Get instances that were continuously active over a window."""
return self.db.instance_get_active_by_window(context, begin, end, return self.db.instance_get_active_by_window(context, begin, end,
project_id) project_id)
#NOTE(bcwaldon): this doesn't really belong in this class
def get_instance_type(self, context, instance_type_id): def get_instance_type(self, context, instance_type_id):
"""Get an instance type by instance type id.""" """Get an instance type by instance type id."""
return instance_types.get_instance_type(instance_type_id) return instance_types.get_instance_type(instance_type_id)
@ -932,6 +961,8 @@ class API(base.Base):
else: else:
instance = self.db.instance_get(context, instance_id) instance = self.db.instance_get(context, instance_id)
check_policy(context, 'get', instance)
inst = dict(instance.iteritems()) inst = dict(instance.iteritems())
# NOTE(comstud): Doesn't get returned with iteritems # NOTE(comstud): Doesn't get returned with iteritems
inst['name'] = instance['name'] inst['name'] = instance['name']
@ -957,6 +988,14 @@ class API(base.Base):
search option that says otherwise. search option that says otherwise.
""" """
#TODO(bcwaldon): determine the best argument for target here
target = {
'project_id': context.project_id,
'user_id': context.user_id,
}
check_policy(context, "get_all", target)
if search_opts is None: if search_opts is None:
search_opts = {} search_opts = {}
@ -1101,6 +1140,7 @@ class API(base.Base):
raise exception.Error(_("Unable to find host for Instance %s") raise exception.Error(_("Unable to find host for Instance %s")
% instance_uuid) % instance_uuid)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF], @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@scheduler_api.reroute_compute("backup") @scheduler_api.reroute_compute("backup")
@ -1120,6 +1160,7 @@ class API(base.Base):
extra_properties=extra_properties) extra_properties=extra_properties)
return recv_meta return recv_meta
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF], @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@scheduler_api.reroute_compute("snapshot") @scheduler_api.reroute_compute("snapshot")
@ -1199,6 +1240,7 @@ class API(base.Base):
return min_ram, min_disk return min_ram, min_disk
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF, @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
vm_states.RESCUED], vm_states.RESCUED],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@ -1216,6 +1258,7 @@ class API(base.Base):
instance['uuid'], instance['uuid'],
params={'reboot_type': reboot_type}) params={'reboot_type': reboot_type})
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF], @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@scheduler_api.reroute_compute("rebuild") @scheduler_api.reroute_compute("rebuild")
@ -1246,6 +1289,7 @@ class API(base.Base):
instance["uuid"], instance["uuid"],
params=rebuild_params) params=rebuild_params)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF], @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
task_state=[task_states.RESIZE_VERIFY]) task_state=[task_states.RESIZE_VERIFY])
@scheduler_api.reroute_compute("revert_resize") @scheduler_api.reroute_compute("revert_resize")
@ -1272,6 +1316,7 @@ class API(base.Base):
self.db.migration_update(context, migration_ref['id'], self.db.migration_update(context, migration_ref['id'],
{'status': 'reverted'}) {'status': 'reverted'})
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF], @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
task_state=[task_states.RESIZE_VERIFY]) task_state=[task_states.RESIZE_VERIFY])
@scheduler_api.reroute_compute("confirm_resize") @scheduler_api.reroute_compute("confirm_resize")
@ -1300,6 +1345,7 @@ class API(base.Base):
self.db.instance_update(context, instance['uuid'], self.db.instance_update(context, instance['uuid'],
{'host': migration_ref['dest_compute'], }) {'host': migration_ref['dest_compute'], })
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF], @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
task_state=[None]) task_state=[None])
@scheduler_api.reroute_compute("resize") @scheduler_api.reroute_compute("resize")
@ -1355,6 +1401,7 @@ class API(base.Base):
"instance_type_id": new_instance_type['id'], "instance_type_id": new_instance_type['id'],
"request_spec": request_spec}}) "request_spec": request_spec}})
@wrap_check_policy
@scheduler_api.reroute_compute("add_fixed_ip") @scheduler_api.reroute_compute("add_fixed_ip")
def add_fixed_ip(self, context, instance, network_id): def add_fixed_ip(self, context, instance, network_id):
"""Add fixed_ip from specified network to given instance.""" """Add fixed_ip from specified network to given instance."""
@ -1364,6 +1411,7 @@ class API(base.Base):
instance_uuid, instance_uuid,
params=dict(network_id=network_id)) params=dict(network_id=network_id))
@wrap_check_policy
@scheduler_api.reroute_compute("remove_fixed_ip") @scheduler_api.reroute_compute("remove_fixed_ip")
def remove_fixed_ip(self, context, instance, address): def remove_fixed_ip(self, context, instance, address):
"""Remove fixed_ip from specified network to given instance.""" """Remove fixed_ip from specified network to given instance."""
@ -1383,6 +1431,7 @@ class API(base.Base):
# didn't raise so this is the correct zone # didn't raise so this is the correct zone
self.network_api.add_network_to_project(context, project_id) self.network_api.add_network_to_project(context, project_id)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF, @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
vm_states.RESCUED], vm_states.RESCUED],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@ -1396,6 +1445,7 @@ class API(base.Base):
task_state=task_states.PAUSING) task_state=task_states.PAUSING)
self._cast_compute_message('pause_instance', context, instance_uuid) self._cast_compute_message('pause_instance', context, instance_uuid)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.PAUSED]) @check_instance_state(vm_state=[vm_states.PAUSED])
@scheduler_api.reroute_compute("unpause") @scheduler_api.reroute_compute("unpause")
def unpause(self, context, instance): def unpause(self, context, instance):
@ -1423,6 +1473,7 @@ class API(base.Base):
return self._call_compute_message_for_host("host_power_action", return self._call_compute_message_for_host("host_power_action",
context, host=host, params={"action": action}) context, host=host, params={"action": action})
@wrap_check_policy
@scheduler_api.reroute_compute("diagnostics") @scheduler_api.reroute_compute("diagnostics")
def get_diagnostics(self, context, instance): def get_diagnostics(self, context, instance):
"""Retrieve diagnostics for the given instance.""" """Retrieve diagnostics for the given instance."""
@ -1430,10 +1481,12 @@ class API(base.Base):
context, context,
instance) instance)
@wrap_check_policy
def get_actions(self, context, instance): def get_actions(self, context, instance):
"""Retrieve actions for the given instance.""" """Retrieve actions for the given instance."""
return self.db.instance_get_actions(context, instance['uuid']) return self.db.instance_get_actions(context, instance['uuid'])
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF, @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
vm_states.RESCUED], vm_states.RESCUED],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@ -1447,6 +1500,7 @@ class API(base.Base):
task_state=task_states.SUSPENDING) task_state=task_states.SUSPENDING)
self._cast_compute_message('suspend_instance', context, instance_uuid) self._cast_compute_message('suspend_instance', context, instance_uuid)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.SUSPENDED]) @check_instance_state(vm_state=[vm_states.SUSPENDED])
@scheduler_api.reroute_compute("resume") @scheduler_api.reroute_compute("resume")
def resume(self, context, instance): def resume(self, context, instance):
@ -1458,6 +1512,7 @@ class API(base.Base):
task_state=task_states.RESUMING) task_state=task_states.RESUMING)
self._cast_compute_message('resume_instance', context, instance_uuid) self._cast_compute_message('resume_instance', context, instance_uuid)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF, @check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
vm_states.STOPPED], vm_states.STOPPED],
task_state=[None, task_states.RESIZE_VERIFY]) task_state=[None, task_states.RESIZE_VERIFY])
@ -1476,6 +1531,7 @@ class API(base.Base):
instance['uuid'], instance['uuid'],
params=rescue_params) params=rescue_params)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.RESCUED]) @check_instance_state(vm_state=[vm_states.RESCUED])
@scheduler_api.reroute_compute("unrescue") @scheduler_api.reroute_compute("unrescue")
def unrescue(self, context, instance): def unrescue(self, context, instance):
@ -1487,6 +1543,7 @@ class API(base.Base):
self._cast_compute_message('unrescue_instance', context, self._cast_compute_message('unrescue_instance', context,
instance['uuid']) instance['uuid'])
@wrap_check_policy
@scheduler_api.reroute_compute("set_admin_password") @scheduler_api.reroute_compute("set_admin_password")
def set_admin_password(self, context, instance, password=None): def set_admin_password(self, context, instance, password=None):
"""Set the root/admin password for the given instance.""" """Set the root/admin password for the given instance."""
@ -1504,6 +1561,7 @@ class API(base.Base):
"args": { "args": {
"instance_uuid": instance_uuid, "new_pass": password}}) "instance_uuid": instance_uuid, "new_pass": password}})
@wrap_check_policy
@scheduler_api.reroute_compute("inject_file") @scheduler_api.reroute_compute("inject_file")
def inject_file(self, context, instance, path, file_contents): def inject_file(self, context, instance, path, file_contents):
"""Write a file to the given instance.""" """Write a file to the given instance."""
@ -1511,6 +1569,7 @@ class API(base.Base):
self._cast_compute_message('inject_file', context, self._cast_compute_message('inject_file', context,
instance['uuid'], params=params) instance['uuid'], params=params)
@wrap_check_policy
def get_ajax_console(self, context, instance): def get_ajax_console(self, context, instance):
"""Get a url to an AJAX Console.""" """Get a url to an AJAX Console."""
output = self._call_compute_message('get_ajax_console', output = self._call_compute_message('get_ajax_console',
@ -1523,6 +1582,7 @@ class API(base.Base):
return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url, return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,
output['token'])} output['token'])}
@wrap_check_policy
def get_vnc_console(self, context, instance): def get_vnc_console(self, context, instance):
"""Get a url to a VNC Console.""" """Get a url to a VNC Console."""
output = self._call_compute_message('get_vnc_console', output = self._call_compute_message('get_vnc_console',
@ -1541,6 +1601,7 @@ class API(base.Base):
'hostignore', 'hostignore',
'portignore')} 'portignore')}
@wrap_check_policy
def get_console_output(self, context, instance, tail_length=None): def get_console_output(self, context, instance, tail_length=None):
"""Get console output for an an instance.""" """Get console output for an an instance."""
return self._call_compute_message('get_console_output', return self._call_compute_message('get_console_output',
@ -1548,29 +1609,35 @@ class API(base.Base):
instance, instance,
{'tail_length': tail_length}) {'tail_length': tail_length})
@wrap_check_policy
def lock(self, context, instance): def lock(self, context, instance):
"""Lock the given instance.""" """Lock the given instance."""
self._cast_compute_message('lock_instance', context, instance['uuid']) self._cast_compute_message('lock_instance', context, instance['uuid'])
@wrap_check_policy
def unlock(self, context, instance): def unlock(self, context, instance):
"""Unlock the given instance.""" """Unlock the given instance."""
self._cast_compute_message('unlock_instance', self._cast_compute_message('unlock_instance',
context, context,
instance['uuid']) instance['uuid'])
@wrap_check_policy
def get_lock(self, context, instance): def get_lock(self, context, instance):
"""Return the boolean state of given instance's lock.""" """Return the boolean state of given instance's lock."""
return self.get(context, instance['uuid'])['locked'] return self.get(context, instance['uuid'])['locked']
@wrap_check_policy
def reset_network(self, context, instance): def reset_network(self, context, instance):
"""Reset networking on the instance.""" """Reset networking on the instance."""
self._cast_compute_message('reset_network', context, instance['uuid']) self._cast_compute_message('reset_network', context, instance['uuid'])
@wrap_check_policy
def inject_network_info(self, context, instance): def inject_network_info(self, context, instance):
"""Inject network info for the instance.""" """Inject network info for the instance."""
self._cast_compute_message('inject_network_info', context, self._cast_compute_message('inject_network_info', context,
instance['uuid']) instance['uuid'])
@wrap_check_policy
def attach_volume(self, context, instance, volume_id, device): def attach_volume(self, context, instance, volume_id, device):
"""Attach an existing volume to an existing instance.""" """Attach an existing volume to an existing instance."""
if not re.match("^/dev/x{0,1}[a-z]d[a-z]+$", device): if not re.match("^/dev/x{0,1}[a-z]d[a-z]+$", device):
@ -1590,6 +1657,9 @@ class API(base.Base):
instance = self.db.volume_get_instance(context.elevated(), volume_id) instance = self.db.volume_get_instance(context.elevated(), volume_id)
if not instance: if not instance:
raise exception.ApiError(_("Volume isn't attached to anything!")) raise exception.ApiError(_("Volume isn't attached to anything!"))
check_policy(context, 'detach_volume', instance)
self.volume_api.check_detach(context, volume_id=volume_id) self.volume_api.check_detach(context, volume_id=volume_id)
host = instance['host'] host = instance['host']
rpc.cast(context, rpc.cast(context,
@ -1599,6 +1669,7 @@ class API(base.Base):
"volume_id": volume_id}}) "volume_id": volume_id}})
return instance return instance
@wrap_check_policy
def associate_floating_ip(self, context, instance, address): def associate_floating_ip(self, context, instance, address):
"""Makes calls to network_api to associate_floating_ip. """Makes calls to network_api to associate_floating_ip.
@ -1630,15 +1701,18 @@ class API(base.Base):
floating_address=address, floating_address=address,
fixed_address=fixed_ip_addrs[0]) fixed_address=fixed_ip_addrs[0])
@wrap_check_policy
def get_instance_metadata(self, context, instance): def get_instance_metadata(self, context, instance):
"""Get all metadata associated with an instance.""" """Get all metadata associated with an instance."""
rv = self.db.instance_metadata_get(context, instance['id']) rv = self.db.instance_metadata_get(context, instance['id'])
return dict(rv.iteritems()) return dict(rv.iteritems())
@wrap_check_policy
def delete_instance_metadata(self, context, instance, key): def delete_instance_metadata(self, context, instance, key):
"""Delete the given metadata item from an instance.""" """Delete the given metadata item from an instance."""
self.db.instance_metadata_delete(context, instance['id'], key) self.db.instance_metadata_delete(context, instance['id'], key)
@wrap_check_policy
def update_instance_metadata(self, context, instance, def update_instance_metadata(self, context, instance,
metadata, delete=False): metadata, delete=False):
"""Updates or creates instance metadata. """Updates or creates instance metadata.
@ -1660,5 +1734,9 @@ class API(base.Base):
def get_instance_faults(self, context, instances): def get_instance_faults(self, context, instances):
"""Get all faults for a list of instance uuids.""" """Get all faults for a list of instance uuids."""
for instance in instances:
check_policy(context, 'get_instance_faults', instance)
uuids = [instance['uuid'] for instance in instances] uuids = [instance['uuid'] for instance in instances]
return self.db.instance_fault_get_by_instance_uuids(context, uuids) return self.db.instance_fault_get_by_instance_uuids(context, uuids)

View File

@ -203,7 +203,7 @@ class AdminRequired(NotAuthorized):
message = _("User does not have admin privileges") message = _("User does not have admin privileges")
class PolicyNotAllowed(NotAuthorized): class PolicyNotAuthorized(NotAuthorized):
message = _("Policy Doesn't allow %(action)s to be performed.") message = _("Policy Doesn't allow %(action)s to be performed.")

View File

@ -17,10 +17,10 @@
"""Policy Engine For Nova""" """Policy Engine For Nova"""
from nova.common import policy
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import utils from nova import utils
from nova.common import policy
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('policy_file', 'policy.json', flags.DEFINE_string('policy_file', 'policy.json',
@ -43,8 +43,8 @@ def init():
global _POLICY_CACHE global _POLICY_CACHE
if not _POLICY_PATH: if not _POLICY_PATH:
_POLICY_PATH = utils.find_config(FLAGS.policy_file) _POLICY_PATH = utils.find_config(FLAGS.policy_file)
data = utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE, utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE,
reload_func=_set_brain) reload_func=_set_brain)
def _set_brain(data): def _set_brain(data):
@ -74,5 +74,5 @@ def enforce(context, action, target):
credentials_dict = context.to_dict() credentials_dict = context.to_dict()
try: try:
policy.enforce(match_list, target_dict, credentials_dict) policy.enforce(match_list, target_dict, credentials_dict)
except policy.NotAllowed: except policy.NotAuthorized:
raise exception.PolicyNotAllowed(action=action) raise exception.PolicyNotAuthorized(action=action)

View File

@ -1,11 +1,70 @@
{ {
"true" : [], "compute:create": [],
"compute:create_instance" : [], "compute:create:attach_network": [],
"compute:attach_network" : [], "compute:create:attach_volume": [],
"compute:attach_volume" : [],
"compute:list_instances": [], "compute:get": [],
"compute:get_instance": [], "compute:get_all" :[],
"network:attach_network" : [],
"volume:create_volume": [], "compute:update": [],
"volume:attach_volume": []
"compute:get_instance_metadata": [],
"compute:update_instance_metadata": [],
"compute:delete_instance_metadata": [],
"compute:get_instance_faults": [],
"compute:get_actions": [],
"compute:get_diagnostics": [],
"compute:get_lock": [],
"compute:lock": [],
"compute:unlock": [],
"compute:get_ajax_console": [],
"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:attach_volume": [],
"compute:detach_volume": [],
"compute:inject_file": [],
"compute:set_admin_password": [],
"compute:rescue": [],
"compute:unrescue": [],
"compute:suspend": [],
"compute:resume": [],
"compute:pause": [],
"compute:unpause": [],
"compute:start": [],
"compute:stop": [],
"compute:resize": [],
"compute:confirm_resize": [],
"compute:revert_resize": [],
"compute:rebuild": [],
"compute:reboot": [],
"compute:snapshot": [],
"compute:backup": [],
"compute:add_security_group": [],
"compute:remove_security_group": [],
"compute:delete": [],
"compute:soft_delete": [],
"compute:force_delete": [],
"compute:restore": []
} }

View File

@ -28,7 +28,9 @@ import mox
import webob.exc import webob.exc
import nova import nova
import nova.common.policy
from nova import compute from nova import compute
import nova.compute.api
from nova.compute import instance_types from nova.compute import instance_types
from nova.compute import manager as compute_manager from nova.compute import manager as compute_manager
from nova.compute import power_state from nova.compute import power_state
@ -42,8 +44,9 @@ from nova.image import fake as fake_image
from nova import log as logging from nova import log as logging
from nova.network.quantum import client as quantum_client from nova.network.quantum import client as quantum_client
from nova.notifier import test_notifier from nova.notifier import test_notifier
from nova.scheduler import driver as scheduler_driver import nova.policy
from nova import rpc from nova import rpc
from nova.scheduler import driver as scheduler_driver
from nova import test from nova import test
from nova.tests import fake_network from nova.tests import fake_network
from nova import utils from nova import utils
@ -111,7 +114,8 @@ class BaseTestCase(test.TestCase):
self.compute = utils.import_object(FLAGS.compute_manager) self.compute = utils.import_object(FLAGS.compute_manager)
self.user_id = 'fake' self.user_id = 'fake'
self.project_id = 'fake' self.project_id = 'fake'
self.context = context.RequestContext(self.user_id, self.project_id) self.context = context.RequestContext(self.user_id,
self.project_id)
test_notifier.NOTIFICATIONS = [] test_notifier.NOTIFICATIONS = []
self.mox = mox.Mox() self.mox = mox.Mox()
self.total_waits = 0 self.total_waits = 0
@ -878,7 +882,9 @@ class ComputeTestCase(BaseTestCase):
instance_uuid = instance['uuid'] instance_uuid = instance['uuid']
self.compute.run_instance(self.context, instance_uuid) self.compute.run_instance(self.context, instance_uuid)
non_admin_context = context.RequestContext(None, None, is_admin=False) non_admin_context = context.RequestContext(None,
None,
is_admin=False)
# decorator should return False (fail) with locked nonadmin context # decorator should return False (fail) with locked nonadmin context
self.compute.lock_instance(self.context, instance_uuid) self.compute.lock_instance(self.context, instance_uuid)
@ -2815,7 +2821,7 @@ class ComputeAPITestCase(BaseTestCase):
def test_attach_volume_invalid(self): def test_attach_volume_invalid(self):
self.assertRaises(exception.ApiError, self.assertRaises(exception.ApiError,
self.compute_api.attach_volume, self.compute_api.attach_volume,
None, self.context,
None, None,
None, None,
'/dev/invalid') '/dev/invalid')
@ -2966,3 +2972,112 @@ class ComputeAPITestCase(BaseTestCase):
self.compute_api.inject_file(self.context, instance, self.compute_api.inject_file(self.context, instance,
"/tmp/test", "File Contents") "/tmp/test", "File Contents")
db.instance_destroy(self.context, instance['id']) db.instance_destroy(self.context, instance['id'])
class ComputePolicyTestCase(BaseTestCase):
def setUp(self):
super(ComputePolicyTestCase, self).setUp()
nova.policy.reset()
nova.policy.init()
self.compute_api = compute.API()
def tearDown(self):
super(ComputePolicyTestCase, self).tearDown()
nova.policy.reset()
def _set_rules(self, rules):
nova.common.policy.set_brain(nova.common.policy.HttpBrain(rules))
def test_actions_are_prefixed(self):
self.mox.StubOutWithMock(nova.policy, 'enforce')
nova.policy.enforce(self.context, 'compute:reboot', {})
self.mox.ReplayAll()
nova.compute.api.check_policy(self.context, 'reboot', {})
self.mox.UnsetStubs()
self.mox.VerifyAll()
def test_wrapped_method(self):
instance = self._create_fake_instance()
self.compute.run_instance(self.context, instance['uuid'])
# force delete to fail
rules = {"compute:delete": [["false:false"]]}
self._set_rules(rules)
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.delete, self.context, instance)
# reset rules to allow deletion
rules = {"compute:delete": []}
self._set_rules(rules)
self.compute_api.delete(self.context, instance)
def test_create_fail(self):
rules = {"compute:create": [["false:false"]]}
self._set_rules(rules)
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.create, self.context, '1', '1')
def test_create_attach_volume_fail(self):
rules = {
"compute:create": [],
"compute:create:attach_network": [["false:false"]],
"compute:create:attach_volume": [],
}
self._set_rules(rules)
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.create, self.context, '1', '1',
requested_networks='blah',
block_device_mapping='blah')
def test_create_attach_network_fail(self):
rules = {
"compute:create": [],
"compute:create:attach_network": [],
"compute:create:attach_volume": [["false:false"]],
}
self._set_rules(rules)
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.create, self.context, '1', '1',
requested_networks='blah',
block_device_mapping='blah')
def test_get_fail(self):
instance = self._create_fake_instance()
rules = {
"compute:get": [["false:false"]],
}
self._set_rules(rules)
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.get, self.context, instance['uuid'])
def test_get_all_fail(self):
rules = {
"compute:get_all": [["false:false"]],
}
self._set_rules(rules)
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.get_all, self.context)
def test_get_instance_faults(self):
instance1 = self._create_fake_instance()
instance2 = self._create_fake_instance()
instances = [instance1, instance2]
rules = {
"compute:get_instance_faults": [["false:false"]],
}
self._set_rules(rules)
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.get_instance_faults,
self.context, instances)

View File

@ -53,7 +53,7 @@ class PolicyFileTestCase(test.TestCase):
policyfile.write("""{"example:test": ["false:false"]}""") policyfile.write("""{"example:test": ["false:false"]}""")
# NOTE(vish): reset stored policy cache so we don't have to sleep(1) # NOTE(vish): reset stored policy cache so we don't have to sleep(1)
policy._POLICY_CACHE = {} policy._POLICY_CACHE = {}
self.assertRaises(exception.PolicyNotAllowed, policy.enforce, self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
self.context, action, self.target) self.context, action, self.target)
@ -89,12 +89,12 @@ class PolicyTestCase(test.TestCase):
def test_enforce_nonexistent_action_throws(self): def test_enforce_nonexistent_action_throws(self):
action = "example:noexist" action = "example:noexist"
self.assertRaises(exception.PolicyNotAllowed, policy.enforce, self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
self.context, action, self.target) self.context, action, self.target)
def test_enforce_bad_action_throws(self): def test_enforce_bad_action_throws(self):
action = "example:denied" action = "example:denied"
self.assertRaises(exception.PolicyNotAllowed, policy.enforce, self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
self.context, action, self.target) self.context, action, self.target)
def test_enforce_good_action(self): def test_enforce_good_action(self):
@ -118,7 +118,7 @@ class PolicyTestCase(test.TestCase):
self.stubs.Set(urllib2, 'urlopen', fakeurlopen) self.stubs.Set(urllib2, 'urlopen', fakeurlopen)
action = "example:get_http" action = "example:get_http"
target = {} target = {}
self.assertRaises(exception.PolicyNotAllowed, policy.enforce, self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
self.context, action, target) self.context, action, target)
def test_templatized_enforcement(self): def test_templatized_enforcement(self):
@ -126,12 +126,12 @@ class PolicyTestCase(test.TestCase):
target_not_mine = {'project_id': 'another'} target_not_mine = {'project_id': 'another'}
action = "example:my_file" action = "example:my_file"
policy.enforce(self.context, action, target_mine) policy.enforce(self.context, action, target_mine)
self.assertRaises(exception.PolicyNotAllowed, policy.enforce, self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
self.context, action, target_not_mine) self.context, action, target_not_mine)
def test_early_AND_enforcement(self): def test_early_AND_enforcement(self):
action = "example:early_and_fail" action = "example:early_and_fail"
self.assertRaises(exception.PolicyNotAllowed, policy.enforce, self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
self.context, action, self.target) self.context, action, self.target)
def test_early_OR_enforcement(self): def test_early_OR_enforcement(self):