From 117ead357631d1f1c733e5d1068b2f3af5dec4d4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 30 Dec 2011 13:11:56 -0800 Subject: [PATCH] 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 --- nova/common/policy.py | 6 +- nova/tests/policy.json | 77 ++++++++++++++++++++--- nova/tests/test_compute.py | 123 +++++++++++++++++++++++++++++++++++-- 3 files changed, 190 insertions(+), 16 deletions(-) diff --git a/nova/common/policy.py b/nova/common/policy.py index 9d811c78..b7cd3cf4 100644 --- a/nova/common/policy.py +++ b/nova/common/policy.py @@ -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): diff --git a/nova/tests/policy.json b/nova/tests/policy.json index 47c3d870..7dae81a4 100644 --- a/nova/tests/policy.json +++ b/nova/tests/policy.json @@ -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": [] } diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 16c0db96..ee98aeab 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -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)