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:
parent
e0680250c0
commit
ace0252d75
|
@ -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"]]
|
||||
}
|
|
@ -22,7 +22,7 @@ import urllib
|
|||
import urllib2
|
||||
|
||||
|
||||
class NotAllowed(Exception):
|
||||
class NotAuthorized(Exception):
|
||||
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
|
||||
performing the action.
|
||||
|
||||
:raises NotAllowed if the check fails
|
||||
:raises NotAuthorized if the check fails
|
||||
|
||||
"""
|
||||
global _BRAIN
|
||||
if not _BRAIN:
|
||||
_BRAIN = Brain()
|
||||
if not _BRAIN.check(match_list, target_dict, credentials_dict):
|
||||
raise NotAllowed()
|
||||
raise NotAuthorized()
|
||||
|
||||
|
||||
class Brain(object):
|
||||
|
|
|
@ -37,6 +37,7 @@ from nova import flags
|
|||
import nova.image
|
||||
from nova import log as logging
|
||||
from nova import network
|
||||
from nova import policy
|
||||
from nova import quota
|
||||
from nova import rpc
|
||||
from nova.scheduler import api as scheduler_api
|
||||
|
@ -88,6 +89,20 @@ def check_instance_state(vm_state=None, task_state=None):
|
|||
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):
|
||||
"""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,
|
||||
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,
|
||||
base_options, security_group, block_device_mapping, num=1):
|
||||
"""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
|
||||
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
|
||||
# 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)
|
||||
|
||||
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):
|
||||
"""Ensure that a context has a security group.
|
||||
|
||||
|
@ -705,6 +722,7 @@ class API(base.Base):
|
|||
|
||||
return False
|
||||
|
||||
@wrap_check_policy
|
||||
def add_security_group(self, context, instance, security_group_name):
|
||||
"""Add security group to the instance"""
|
||||
security_group = self.db.security_group_get_by_name(context,
|
||||
|
@ -733,6 +751,7 @@ class API(base.Base):
|
|||
{"method": "refresh_security_group_rules",
|
||||
"args": {"security_group_id": security_group['id']}})
|
||||
|
||||
@wrap_check_policy
|
||||
def remove_security_group(self, context, instance, security_group_name):
|
||||
"""Remove the security group associated with the instance"""
|
||||
security_group = self.db.security_group_get_by_name(context,
|
||||
|
@ -761,6 +780,7 @@ class API(base.Base):
|
|||
{"method": "refresh_security_group_rules",
|
||||
"args": {"security_group_id": security_group['id']}})
|
||||
|
||||
@wrap_check_policy
|
||||
@scheduler_api.reroute_compute("update")
|
||||
def update(self, context, instance, **kwargs):
|
||||
"""Updates the instance in the datastore.
|
||||
|
@ -776,6 +796,7 @@ class API(base.Base):
|
|||
rv = self.db.instance_update(context, instance["id"], kwargs)
|
||||
return dict(rv.iteritems())
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
|
||||
vm_states.ERROR])
|
||||
@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
|
||||
# allowed but the EC2 API appears to allow from RESCUED and STOPPED
|
||||
# too
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
|
||||
vm_states.ERROR, vm_states.RESCUED,
|
||||
vm_states.STOPPED])
|
||||
|
@ -834,6 +856,7 @@ class API(base.Base):
|
|||
|
||||
self._delete(context, instance)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.SOFT_DELETE])
|
||||
@scheduler_api.reroute_compute("restore")
|
||||
def restore(self, context, instance):
|
||||
|
@ -852,12 +875,14 @@ class API(base.Base):
|
|||
self._cast_compute_message('power_on_instance', context,
|
||||
instance['uuid'], host)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.SOFT_DELETE])
|
||||
@scheduler_api.reroute_compute("force_delete")
|
||||
def force_delete(self, context, instance):
|
||||
"""Force delete a previously deleted (but not reclaimed) instance."""
|
||||
self._delete(context, instance)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
|
||||
vm_states.RESCUED],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
|
@ -884,6 +909,7 @@ class API(base.Base):
|
|||
else:
|
||||
self._call_compute_message('stop_instance', context, instance)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.STOPPED, vm_states.SHUTOFF])
|
||||
def start(self, context, instance):
|
||||
"""Start an instance."""
|
||||
|
@ -915,11 +941,14 @@ class API(base.Base):
|
|||
"args": {"topic": FLAGS.compute_topic,
|
||||
"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):
|
||||
"""Get instances that were continuously active over a window."""
|
||||
return self.db.instance_get_active_by_window(context, begin, end,
|
||||
project_id)
|
||||
|
||||
#NOTE(bcwaldon): this doesn't really belong in this class
|
||||
def get_instance_type(self, context, instance_type_id):
|
||||
"""Get an instance type by instance type id."""
|
||||
return instance_types.get_instance_type(instance_type_id)
|
||||
|
@ -932,6 +961,8 @@ class API(base.Base):
|
|||
else:
|
||||
instance = self.db.instance_get(context, instance_id)
|
||||
|
||||
check_policy(context, 'get', instance)
|
||||
|
||||
inst = dict(instance.iteritems())
|
||||
# NOTE(comstud): Doesn't get returned with iteritems
|
||||
inst['name'] = instance['name']
|
||||
|
@ -957,6 +988,14 @@ class API(base.Base):
|
|||
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:
|
||||
search_opts = {}
|
||||
|
||||
|
@ -1101,6 +1140,7 @@ class API(base.Base):
|
|||
raise exception.Error(_("Unable to find host for Instance %s")
|
||||
% instance_uuid)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
@scheduler_api.reroute_compute("backup")
|
||||
|
@ -1120,6 +1160,7 @@ class API(base.Base):
|
|||
extra_properties=extra_properties)
|
||||
return recv_meta
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
@scheduler_api.reroute_compute("snapshot")
|
||||
|
@ -1199,6 +1240,7 @@ class API(base.Base):
|
|||
|
||||
return min_ram, min_disk
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
|
||||
vm_states.RESCUED],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
|
@ -1216,6 +1258,7 @@ class API(base.Base):
|
|||
instance['uuid'],
|
||||
params={'reboot_type': reboot_type})
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
@scheduler_api.reroute_compute("rebuild")
|
||||
|
@ -1246,6 +1289,7 @@ class API(base.Base):
|
|||
instance["uuid"],
|
||||
params=rebuild_params)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
|
||||
task_state=[task_states.RESIZE_VERIFY])
|
||||
@scheduler_api.reroute_compute("revert_resize")
|
||||
|
@ -1272,6 +1316,7 @@ class API(base.Base):
|
|||
self.db.migration_update(context, migration_ref['id'],
|
||||
{'status': 'reverted'})
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
|
||||
task_state=[task_states.RESIZE_VERIFY])
|
||||
@scheduler_api.reroute_compute("confirm_resize")
|
||||
|
@ -1300,6 +1345,7 @@ class API(base.Base):
|
|||
self.db.instance_update(context, instance['uuid'],
|
||||
{'host': migration_ref['dest_compute'], })
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
|
||||
task_state=[None])
|
||||
@scheduler_api.reroute_compute("resize")
|
||||
|
@ -1355,6 +1401,7 @@ class API(base.Base):
|
|||
"instance_type_id": new_instance_type['id'],
|
||||
"request_spec": request_spec}})
|
||||
|
||||
@wrap_check_policy
|
||||
@scheduler_api.reroute_compute("add_fixed_ip")
|
||||
def add_fixed_ip(self, context, instance, network_id):
|
||||
"""Add fixed_ip from specified network to given instance."""
|
||||
|
@ -1364,6 +1411,7 @@ class API(base.Base):
|
|||
instance_uuid,
|
||||
params=dict(network_id=network_id))
|
||||
|
||||
@wrap_check_policy
|
||||
@scheduler_api.reroute_compute("remove_fixed_ip")
|
||||
def remove_fixed_ip(self, context, instance, address):
|
||||
"""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
|
||||
self.network_api.add_network_to_project(context, project_id)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
|
||||
vm_states.RESCUED],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
|
@ -1396,6 +1445,7 @@ class API(base.Base):
|
|||
task_state=task_states.PAUSING)
|
||||
self._cast_compute_message('pause_instance', context, instance_uuid)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.PAUSED])
|
||||
@scheduler_api.reroute_compute("unpause")
|
||||
def unpause(self, context, instance):
|
||||
|
@ -1423,6 +1473,7 @@ class API(base.Base):
|
|||
return self._call_compute_message_for_host("host_power_action",
|
||||
context, host=host, params={"action": action})
|
||||
|
||||
@wrap_check_policy
|
||||
@scheduler_api.reroute_compute("diagnostics")
|
||||
def get_diagnostics(self, context, instance):
|
||||
"""Retrieve diagnostics for the given instance."""
|
||||
|
@ -1430,10 +1481,12 @@ class API(base.Base):
|
|||
context,
|
||||
instance)
|
||||
|
||||
@wrap_check_policy
|
||||
def get_actions(self, context, instance):
|
||||
"""Retrieve actions for the given instance."""
|
||||
return self.db.instance_get_actions(context, instance['uuid'])
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
|
||||
vm_states.RESCUED],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
|
@ -1447,6 +1500,7 @@ class API(base.Base):
|
|||
task_state=task_states.SUSPENDING)
|
||||
self._cast_compute_message('suspend_instance', context, instance_uuid)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.SUSPENDED])
|
||||
@scheduler_api.reroute_compute("resume")
|
||||
def resume(self, context, instance):
|
||||
|
@ -1458,6 +1512,7 @@ class API(base.Base):
|
|||
task_state=task_states.RESUMING)
|
||||
self._cast_compute_message('resume_instance', context, instance_uuid)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
|
||||
vm_states.STOPPED],
|
||||
task_state=[None, task_states.RESIZE_VERIFY])
|
||||
|
@ -1476,6 +1531,7 @@ class API(base.Base):
|
|||
instance['uuid'],
|
||||
params=rescue_params)
|
||||
|
||||
@wrap_check_policy
|
||||
@check_instance_state(vm_state=[vm_states.RESCUED])
|
||||
@scheduler_api.reroute_compute("unrescue")
|
||||
def unrescue(self, context, instance):
|
||||
|
@ -1487,6 +1543,7 @@ class API(base.Base):
|
|||
self._cast_compute_message('unrescue_instance', context,
|
||||
instance['uuid'])
|
||||
|
||||
@wrap_check_policy
|
||||
@scheduler_api.reroute_compute("set_admin_password")
|
||||
def set_admin_password(self, context, instance, password=None):
|
||||
"""Set the root/admin password for the given instance."""
|
||||
|
@ -1504,6 +1561,7 @@ class API(base.Base):
|
|||
"args": {
|
||||
"instance_uuid": instance_uuid, "new_pass": password}})
|
||||
|
||||
@wrap_check_policy
|
||||
@scheduler_api.reroute_compute("inject_file")
|
||||
def inject_file(self, context, instance, path, file_contents):
|
||||
"""Write a file to the given instance."""
|
||||
|
@ -1511,6 +1569,7 @@ class API(base.Base):
|
|||
self._cast_compute_message('inject_file', context,
|
||||
instance['uuid'], params=params)
|
||||
|
||||
@wrap_check_policy
|
||||
def get_ajax_console(self, context, instance):
|
||||
"""Get a url to an 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,
|
||||
output['token'])}
|
||||
|
||||
@wrap_check_policy
|
||||
def get_vnc_console(self, context, instance):
|
||||
"""Get a url to a VNC Console."""
|
||||
output = self._call_compute_message('get_vnc_console',
|
||||
|
@ -1541,6 +1601,7 @@ class API(base.Base):
|
|||
'hostignore',
|
||||
'portignore')}
|
||||
|
||||
@wrap_check_policy
|
||||
def get_console_output(self, context, instance, tail_length=None):
|
||||
"""Get console output for an an instance."""
|
||||
return self._call_compute_message('get_console_output',
|
||||
|
@ -1548,29 +1609,35 @@ class API(base.Base):
|
|||
instance,
|
||||
{'tail_length': tail_length})
|
||||
|
||||
@wrap_check_policy
|
||||
def lock(self, context, instance):
|
||||
"""Lock the given instance."""
|
||||
self._cast_compute_message('lock_instance', context, instance['uuid'])
|
||||
|
||||
@wrap_check_policy
|
||||
def unlock(self, context, instance):
|
||||
"""Unlock the given instance."""
|
||||
self._cast_compute_message('unlock_instance',
|
||||
context,
|
||||
instance['uuid'])
|
||||
|
||||
@wrap_check_policy
|
||||
def get_lock(self, context, instance):
|
||||
"""Return the boolean state of given instance's lock."""
|
||||
return self.get(context, instance['uuid'])['locked']
|
||||
|
||||
@wrap_check_policy
|
||||
def reset_network(self, context, instance):
|
||||
"""Reset networking on the instance."""
|
||||
self._cast_compute_message('reset_network', context, instance['uuid'])
|
||||
|
||||
@wrap_check_policy
|
||||
def inject_network_info(self, context, instance):
|
||||
"""Inject network info for the instance."""
|
||||
self._cast_compute_message('inject_network_info', context,
|
||||
instance['uuid'])
|
||||
|
||||
@wrap_check_policy
|
||||
def attach_volume(self, context, instance, volume_id, device):
|
||||
"""Attach an existing volume to an existing instance."""
|
||||
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)
|
||||
if not instance:
|
||||
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)
|
||||
host = instance['host']
|
||||
rpc.cast(context,
|
||||
|
@ -1599,6 +1669,7 @@ class API(base.Base):
|
|||
"volume_id": volume_id}})
|
||||
return instance
|
||||
|
||||
@wrap_check_policy
|
||||
def associate_floating_ip(self, context, instance, address):
|
||||
"""Makes calls to network_api to associate_floating_ip.
|
||||
|
||||
|
@ -1630,15 +1701,18 @@ class API(base.Base):
|
|||
floating_address=address,
|
||||
fixed_address=fixed_ip_addrs[0])
|
||||
|
||||
@wrap_check_policy
|
||||
def get_instance_metadata(self, context, instance):
|
||||
"""Get all metadata associated with an instance."""
|
||||
rv = self.db.instance_metadata_get(context, instance['id'])
|
||||
return dict(rv.iteritems())
|
||||
|
||||
@wrap_check_policy
|
||||
def delete_instance_metadata(self, context, instance, key):
|
||||
"""Delete the given metadata item from an instance."""
|
||||
self.db.instance_metadata_delete(context, instance['id'], key)
|
||||
|
||||
@wrap_check_policy
|
||||
def update_instance_metadata(self, context, instance,
|
||||
metadata, delete=False):
|
||||
"""Updates or creates instance metadata.
|
||||
|
@ -1660,5 +1734,9 @@ class API(base.Base):
|
|||
|
||||
def get_instance_faults(self, context, instances):
|
||||
"""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]
|
||||
return self.db.instance_fault_get_by_instance_uuids(context, uuids)
|
||||
|
|
|
@ -203,7 +203,7 @@ class AdminRequired(NotAuthorized):
|
|||
message = _("User does not have admin privileges")
|
||||
|
||||
|
||||
class PolicyNotAllowed(NotAuthorized):
|
||||
class PolicyNotAuthorized(NotAuthorized):
|
||||
message = _("Policy Doesn't allow %(action)s to be performed.")
|
||||
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
|
||||
"""Policy Engine For Nova"""
|
||||
|
||||
from nova.common import policy
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import utils
|
||||
from nova.common import policy
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('policy_file', 'policy.json',
|
||||
|
@ -43,8 +43,8 @@ def init():
|
|||
global _POLICY_CACHE
|
||||
if not _POLICY_PATH:
|
||||
_POLICY_PATH = utils.find_config(FLAGS.policy_file)
|
||||
data = utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE,
|
||||
reload_func=_set_brain)
|
||||
utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE,
|
||||
reload_func=_set_brain)
|
||||
|
||||
|
||||
def _set_brain(data):
|
||||
|
@ -74,5 +74,5 @@ def enforce(context, action, target):
|
|||
credentials_dict = context.to_dict()
|
||||
try:
|
||||
policy.enforce(match_list, target_dict, credentials_dict)
|
||||
except policy.NotAllowed:
|
||||
raise exception.PolicyNotAllowed(action=action)
|
||||
except policy.NotAuthorized:
|
||||
raise exception.PolicyNotAuthorized(action=action)
|
||||
|
|
|
@ -1,11 +1,70 @@
|
|||
{
|
||||
"true" : [],
|
||||
"compute:create_instance" : [],
|
||||
"compute:attach_network" : [],
|
||||
"compute:attach_volume" : [],
|
||||
"compute:list_instances": [],
|
||||
"compute:get_instance": [],
|
||||
"network:attach_network" : [],
|
||||
"volume:create_volume": [],
|
||||
"volume:attach_volume": []
|
||||
"compute:create": [],
|
||||
"compute:create:attach_network": [],
|
||||
"compute:create:attach_volume": [],
|
||||
|
||||
"compute:get": [],
|
||||
"compute:get_all" :[],
|
||||
|
||||
"compute:update": [],
|
||||
|
||||
"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": []
|
||||
}
|
||||
|
|
|
@ -28,7 +28,9 @@ import mox
|
|||
import webob.exc
|
||||
|
||||
import nova
|
||||
import nova.common.policy
|
||||
from nova import compute
|
||||
import nova.compute.api
|
||||
from nova.compute import instance_types
|
||||
from nova.compute import manager as compute_manager
|
||||
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.network.quantum import client as quantum_client
|
||||
from nova.notifier import test_notifier
|
||||
from nova.scheduler import driver as scheduler_driver
|
||||
import nova.policy
|
||||
from nova import rpc
|
||||
from nova.scheduler import driver as scheduler_driver
|
||||
from nova import test
|
||||
from nova.tests import fake_network
|
||||
from nova import utils
|
||||
|
@ -111,7 +114,8 @@ class BaseTestCase(test.TestCase):
|
|||
self.compute = utils.import_object(FLAGS.compute_manager)
|
||||
self.user_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 = []
|
||||
self.mox = mox.Mox()
|
||||
self.total_waits = 0
|
||||
|
@ -878,7 +882,9 @@ class ComputeTestCase(BaseTestCase):
|
|||
instance_uuid = 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
|
||||
self.compute.lock_instance(self.context, instance_uuid)
|
||||
|
@ -2815,7 +2821,7 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
def test_attach_volume_invalid(self):
|
||||
self.assertRaises(exception.ApiError,
|
||||
self.compute_api.attach_volume,
|
||||
None,
|
||||
self.context,
|
||||
None,
|
||||
None,
|
||||
'/dev/invalid')
|
||||
|
@ -2966,3 +2972,112 @@ class ComputeAPITestCase(BaseTestCase):
|
|||
self.compute_api.inject_file(self.context, instance,
|
||||
"/tmp/test", "File Contents")
|
||||
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)
|
||||
|
|
|
@ -53,7 +53,7 @@ class PolicyFileTestCase(test.TestCase):
|
|||
policyfile.write("""{"example:test": ["false:false"]}""")
|
||||
# NOTE(vish): reset stored policy cache so we don't have to sleep(1)
|
||||
policy._POLICY_CACHE = {}
|
||||
self.assertRaises(exception.PolicyNotAllowed, policy.enforce,
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
|
||||
self.context, action, self.target)
|
||||
|
||||
|
||||
|
@ -89,12 +89,12 @@ class PolicyTestCase(test.TestCase):
|
|||
|
||||
def test_enforce_nonexistent_action_throws(self):
|
||||
action = "example:noexist"
|
||||
self.assertRaises(exception.PolicyNotAllowed, policy.enforce,
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
|
||||
self.context, action, self.target)
|
||||
|
||||
def test_enforce_bad_action_throws(self):
|
||||
action = "example:denied"
|
||||
self.assertRaises(exception.PolicyNotAllowed, policy.enforce,
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
|
||||
self.context, action, self.target)
|
||||
|
||||
def test_enforce_good_action(self):
|
||||
|
@ -118,7 +118,7 @@ class PolicyTestCase(test.TestCase):
|
|||
self.stubs.Set(urllib2, 'urlopen', fakeurlopen)
|
||||
action = "example:get_http"
|
||||
target = {}
|
||||
self.assertRaises(exception.PolicyNotAllowed, policy.enforce,
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
|
||||
self.context, action, target)
|
||||
|
||||
def test_templatized_enforcement(self):
|
||||
|
@ -126,12 +126,12 @@ class PolicyTestCase(test.TestCase):
|
|||
target_not_mine = {'project_id': 'another'}
|
||||
action = "example:my_file"
|
||||
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)
|
||||
|
||||
def test_early_AND_enforcement(self):
|
||||
action = "example:early_and_fail"
|
||||
self.assertRaises(exception.PolicyNotAllowed, policy.enforce,
|
||||
self.assertRaises(exception.PolicyNotAuthorized, policy.enforce,
|
||||
self.context, action, self.target)
|
||||
|
||||
def test_early_OR_enforcement(self):
|
||||
|
|
Loading…
Reference in New Issue