From 480875d49c722a2310b52c3bd4b2241598df86c8 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 12 Jan 2012 15:52:11 -0800 Subject: Add policy checking to nova.network.api.API Partially implements bp interim-nova-authz-service Change-Id: Ib93d854ee1a7f22f4e7f313a9d50300df8b8596b --- etc/nova/policy.json | 37 ++++++++++++++++++++++++++++- nova/exception.py | 2 +- nova/network/manager.py | 59 ++++++++++++++++++++++++++++++++++++++++++++-- nova/tests/fake_network.py | 9 ++++--- nova/tests/policy.json | 36 +++++++++++++++++++++++++++- nova/tests/test_network.py | 45 +++++++++++++++++++++++++++++++---- 6 files changed, 173 insertions(+), 15 deletions(-) diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 00140886b..ccc38c9fe 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -91,5 +91,40 @@ "volume:create_snapshot": [], "volume:delete_snapshot": [], "volume:get_snapshot": [], - "volume:get_all_snapshots": [] + "volume:get_all_snapshots": [], + + + "network:get_all_networks": [], + "network:get_network": [], + "network:delete_network": [], + "network:disassociate_network": [], + "network:get_vifs_by_instance": [], + "network:allocate_for_instance": [], + "network:deallocate_for_instance": [], + "network:validate_networks": [], + "network:get_instance_uuids_by_ip_filter": [], + + "network:get_floating_ip": [], + "network:get_floating_ip_pools": [], + "network:get_floating_ip_by_address": [], + "network:get_floating_ips_by_project": [], + "network:get_floating_ips_by_fixed_address": [], + "network:allocate_floating_ip": [], + "network:deallocate_floating_ip": [], + "network:associate_floating_ip": [], + "network:disassociate_floating_ip": [], + + "network:get_fixed_ip": [], + "network:add_fixed_ip_to_instance": [], + "network:remove_fixed_ip_from_instance": [], + "network:add_network_to_project": [], + "network:get_instance_nw_info": [], + + "network:get_dns_zones": [], + "network:add_dns_entry": [], + "network:modify_dns_entry": [], + "network:delete_dns_entry": [], + "network:get_dns_entries_by_address": [], + "network:get_dns_entries_by_name": [] } + diff --git a/nova/exception.py b/nova/exception.py index 716f86dc1..6e60642f6 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -204,7 +204,7 @@ class AdminRequired(NotAuthorized): class PolicyNotAuthorized(NotAuthorized): - message = _("Policy Doesn't allow %(action)s to be performed.") + message = _("Policy doesn't allow %(action)s to be performed.") class Invalid(NovaException): diff --git a/nova/network/manager.py b/nova/network/manager.py index 15e0649f5..962f99ca9 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -46,13 +46,15 @@ topologies. All of the network commands are issued to a subclass of """ import datetime +import functools import itertools import math -import netaddr import random import re import socket + from eventlet import greenpool +import netaddr from nova.compute import api as compute_api from nova.compute import instance_types @@ -65,6 +67,7 @@ from nova import log as logging from nova import manager from nova.network import api as network_api from nova.network import model as network_model +import nova.policy from nova import quota from nova import utils from nova import rpc @@ -187,6 +190,27 @@ class RPCAllocateFixedIP(object): return self.allocate_fixed_ip(context, instance_id, network, **kwargs) +def wrap_check_policy(func): + """Check policy corresponding to the wrapped methods prior to execution""" + + @functools.wraps(func) + def wrapped(self, context, *args, **kwargs): + action = func.__name__ + check_policy(context, action) + return func(self, context, *args, **kwargs) + + return wrapped + + +def check_policy(context, action): + target = { + 'project_id': context.project_id, + 'user_id': context.user_id, + } + _action = 'network:%s' % action + nova.policy.enforce(context, _action, target) + + class FloatingIP(object): """Mixin class for adding floating IP functionality to a manager.""" def init_host_floating_ips(self): @@ -210,6 +234,7 @@ class FloatingIP(object): self.driver.ensure_floating_forward(floating_ip['address'], fixed_address) + @wrap_check_policy def allocate_for_instance(self, context, **kwargs): """Handles allocating the floating IP resources for an instance. @@ -246,6 +271,7 @@ class FloatingIP(object): affect_auto_assigned=True) return nw_info + @wrap_check_policy def deallocate_for_instance(self, context, **kwargs): """Handles deallocating floating IP resources for an instance. @@ -294,6 +320,7 @@ class FloatingIP(object): 'project': context.project_id}) raise exception.NotAuthorized() + @wrap_check_policy def allocate_floating_ip(self, context, project_id, pool=None): """Gets a floating ip from the pool.""" # NOTE(tr3buchet): all network hosts in zone now use the same pool @@ -309,6 +336,7 @@ class FloatingIP(object): project_id, pool) + @wrap_check_policy def deallocate_floating_ip(self, context, address, affect_auto_assigned=False): """Returns an floating ip to the pool.""" @@ -328,8 +356,9 @@ class FloatingIP(object): self.db.floating_ip_deallocate(context, address) + @wrap_check_policy def associate_floating_ip(self, context, floating_address, fixed_address, - affect_auto_assigned=False): + affect_auto_assigned=False): """Associates a floating ip with a fixed ip. Makes sure everything makes sense then calls _associate_floating_ip, @@ -385,6 +414,7 @@ class FloatingIP(object): self.driver.bind_floating_ip(floating_address, interface) self.driver.ensure_floating_forward(floating_address, fixed_address) + @wrap_check_policy def disassociate_floating_ip(self, context, address, affect_auto_assigned=False): """Disassociates a floating ip from its fixed ip. @@ -437,50 +467,61 @@ class FloatingIP(object): self.driver.unbind_floating_ip(address, interface) self.driver.remove_floating_forward(address, fixed_address) + @wrap_check_policy def get_floating_ip(self, context, id): """Returns a floating IP as a dict""" return dict(self.db.floating_ip_get(context, id).iteritems()) + @wrap_check_policy def get_floating_pools(self, context): """Returns list of floating pools""" pools = self.db.floating_ip_get_pools(context) return [dict(pool.iteritems()) for pool in pools] + @wrap_check_policy def get_floating_ip_by_address(self, context, address): """Returns a floating IP as a dict""" return dict(self.db.floating_ip_get_by_address(context, address).iteritems()) + @wrap_check_policy def get_floating_ips_by_project(self, context): """Returns the floating IPs allocated to a project""" ips = self.db.floating_ip_get_all_by_project(context, context.project_id) return [dict(ip.iteritems()) for ip in ips] + @wrap_check_policy def get_floating_ips_by_fixed_address(self, context, fixed_address): """Returns the floating IPs associated with a fixed_address""" floating_ips = self.db.floating_ip_get_by_fixed_address(context, fixed_address) return [floating_ip['address'] for floating_ip in floating_ips] + @wrap_check_policy def get_dns_zones(self, context): return self.floating_dns_manager.get_zones() + @wrap_check_policy def add_dns_entry(self, context, address, dns_name, dns_type, dns_zone): self.floating_dns_manager.create_entry(dns_name, address, dns_type, dns_zone) + @wrap_check_policy def modify_dns_entry(self, context, address, dns_name, dns_zone): self.floating_dns_manager.modify_address(dns_name, address, dns_zone) + @wrap_check_policy def delete_dns_entry(self, context, dns_name, dns_zone): self.floating_dns_manager.delete_entry(dns_name, dns_zone) + @wrap_check_policy def get_dns_entries_by_address(self, context, address, dns_zone): return self.floating_dns_manager.get_entries_by_address(address, dns_zone) + @wrap_check_policy def get_dns_entries_by_name(self, context, name, dns_zone): return self.floating_dns_manager.get_entries_by_name(name, dns_zone) @@ -589,6 +630,7 @@ class NetworkManager(manager.SchedulerDependentManager): # floating ips MUST override this or use the Mixin return [] + @wrap_check_policy def get_instance_uuids_by_ip_filter(self, context, filters): fixed_ip_filter = filters.get('fixed_ip') ip_filter = re.compile(str(filters.get('ip'))) @@ -664,6 +706,7 @@ class NetworkManager(manager.SchedulerDependentManager): return [network for network in networks if not network['vlan']] + @wrap_check_policy def allocate_for_instance(self, context, **kwargs): """Handles allocating the various network resources for an instance. @@ -689,6 +732,7 @@ class NetworkManager(manager.SchedulerDependentManager): return self.get_instance_nw_info(context, instance_id, instance_uuid, type_id, host) + @wrap_check_policy def deallocate_for_instance(self, context, **kwargs): """Handles deallocating various network resources for an instance. @@ -710,6 +754,7 @@ class NetworkManager(manager.SchedulerDependentManager): # deallocate vifs (mac addresses) self.db.virtual_interface_delete_by_instance(context, instance_id) + @wrap_check_policy def get_instance_nw_info(self, context, instance_id, instance_uuid, instance_type_id, host): """Creates network info list for instance. @@ -977,11 +1022,13 @@ class NetworkManager(manager.SchedulerDependentManager): random.randint(0x00, 0xff)] return ':'.join(map(lambda x: "%02x" % x, mac)) + @wrap_check_policy def add_fixed_ip_to_instance(self, context, instance_id, host, network_id): """Adds a fixed ip to an instance from specified network.""" networks = [self._get_network_by_id(context, network_id)] self._allocate_fixed_ips(context, instance_id, host, networks) + @wrap_check_policy def remove_fixed_ip_from_instance(self, context, instance_id, address): """Removes a fixed ip from an instance from specified network.""" fixed_ips = self.db.fixed_ip_get_by_instance(context, instance_id) @@ -1216,6 +1263,7 @@ class NetworkManager(manager.SchedulerDependentManager): self._create_fixed_ips(context, network['id']) return networks + @wrap_check_policy def delete_network(self, context, fixed_range, uuid, require_disassociated=True): @@ -1271,6 +1319,7 @@ class NetworkManager(manager.SchedulerDependentManager): """Sets up network on this host.""" raise NotImplementedError() + @wrap_check_policy def validate_networks(self, context, networks): """check if the networks exists and host is set to each network. @@ -1305,11 +1354,13 @@ class NetworkManager(manager.SchedulerDependentManager): def _get_networks_by_uuids(self, context, network_uuids): return self.db.network_get_all_by_uuids(context, network_uuids) + @wrap_check_policy def get_vifs_by_instance(self, context, instance_id): """Returns the vifs associated with an instance""" vifs = self.db.virtual_interface_get_by_instance(context, instance_id) return [dict(vif.iteritems()) for vif in vifs] + @wrap_check_policy def get_network(self, context, network_uuid): networks = self._get_networks_by_uuids(context, [network_uuid]) try: @@ -1319,14 +1370,17 @@ class NetworkManager(manager.SchedulerDependentManager): return dict(network.iteritems()) + @wrap_check_policy def get_all_networks(self, context): networks = self.db.network_get_all(context) return [dict(network.iteritems()) for network in networks] + @wrap_check_policy def disassociate_network(self, context, network_uuid): network = self.get_network(context, network_uuid) self.db.network_disassociate(context, network['id']) + @wrap_check_policy def get_fixed_ip(self, context, id): """Return a fixed ip""" fixed = self.db.fixed_ip_get(context, id) @@ -1495,6 +1549,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): self._setup_network(context, network) return address + @wrap_check_policy def add_network_to_project(self, context, project_id): """Force adds another network to a project.""" self.db.network_associate(context, project_id, force=True) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 071990bd6..29bb793ce 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import nova.context from nova import db from nova import exception from nova import flags @@ -295,11 +296,9 @@ def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2, stubs.Set(db, 'network_get', network_get_fake) stubs.Set(db, 'instance_info_cache_update', update_cache_fake) - class FakeContext(object): - def __init__(self): - self.project_id = 1 - - return network.get_instance_nw_info(FakeContext(), 0, 0, 0, None) + context = nova.context.RequestContext('testuser', 'testproject', + is_admin=False) + return network.get_instance_nw_info(context, 0, 0, 0, None) def stub_out_nw_api_get_instance_nw_info(stubs, func=None): diff --git a/nova/tests/policy.json b/nova/tests/policy.json index c96908fff..a12a84ab1 100644 --- a/nova/tests/policy.json +++ b/nova/tests/policy.json @@ -88,5 +88,39 @@ "volume:create_snapshot": [], "volume:delete_snapshot": [], "volume:get_snapshot": [], - "volume:get_all_snapshots": [] + "volume:get_all_snapshots": [], + + + "network:get_all_networks": [], + "network:get_network": [], + "network:delete_network": [], + "network:disassociate_network": [], + "network:get_vifs_by_instance": [], + "network:allocate_for_instance": [], + "network:deallocate_for_instance": [], + "network:validate_networks": [], + "network:get_instance_uuids_by_ip_filter": [], + + "network:get_floating_ip": [], + "network:get_floating_ip_pools": [], + "network:get_floating_ip_by_address": [], + "network:get_floating_ips_by_project": [], + "network:get_floating_ips_by_fixed_address": [], + "network:allocate_floating_ip": [], + "network:deallocate_floating_ip": [], + "network:associate_floating_ip": [], + "network:disassociate_floating_ip": [], + + "network:get_fixed_ip": [], + "network:add_fixed_ip_to_instance": [], + "network:remove_fixed_ip_from_instance": [], + "network:add_network_to_project": [], + "network:get_instance_nw_info": [], + + "network:get_dns_zones": [], + "network:add_dns_entry": [], + "network:modify_dns_entry": [], + "network:delete_dns_entry": [], + "network:get_dns_entries_by_address": [], + "network:get_dns_entries_by_name": [] } diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 8f8efc991..8b230376b 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -22,6 +22,7 @@ from nova import db from nova import exception from nova import flags from nova import log as logging +import nova.policy from nova import rpc from nova import test from nova import utils @@ -230,7 +231,7 @@ class FlatNetworkTestCase(test.TestCase): self.mox.ReplayAll() self.assertRaises(exception.FixedIpInvalid, - self.network.validate_networks, None, + self.network.validate_networks, self.context, requested_networks) def test_validate_networks_empty_fixed_ip(self): @@ -243,7 +244,7 @@ class FlatNetworkTestCase(test.TestCase): self.assertRaises(exception.FixedIpInvalid, self.network.validate_networks, - None, requested_networks) + self.context, requested_networks) def test_validate_networks_none_fixed_ip(self): self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') @@ -253,7 +254,7 @@ class FlatNetworkTestCase(test.TestCase): mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() - self.network.validate_networks(None, requested_networks) + self.network.validate_networks(self.context, requested_networks) def test_add_fixed_ip_instance_without_vpn_requested_networks(self): self.mox.StubOutWithMock(db, 'network_get') @@ -813,12 +814,17 @@ class VlanNetworkTestCase(test.TestCase): class CommonNetworkTestCase(test.TestCase): + + def setUp(self): + super(CommonNetworkTestCase, self).setUp() + self.context = context.RequestContext('fake', 'fake') + def fake_create_fixed_ips(self, context, network_id): return None def test_remove_fixed_ip_from_instance(self): manager = fake_network.FakeNetworkManager() - manager.remove_fixed_ip_from_instance(None, 99, '10.0.0.1') + manager.remove_fixed_ip_from_instance(self.context, 99, '10.0.0.1') self.assertEquals(manager.deallocate_called, '10.0.0.1') @@ -826,7 +832,7 @@ class CommonNetworkTestCase(test.TestCase): manager = fake_network.FakeNetworkManager() self.assertRaises(exception.FixedIpNotFoundForSpecificInstance, manager.remove_fixed_ip_from_instance, - None, 99, 'bad input') + self.context, 99, 'bad input') def test_validate_cidrs(self): manager = fake_network.FakeNetworkManager() @@ -1320,3 +1326,32 @@ class FloatingIPTestCase(test.TestCase): self.assertRaises(exception.NotFound, self.network.delete_dns_entry, self.context, name1, zone) + + +class NetworkPolicyTestCase(test.TestCase): + def setUp(self): + super(NetworkPolicyTestCase, self).setUp() + + nova.policy.reset() + nova.policy.init() + + self.context = context.get_admin_context() + + def tearDown(self): + super(NetworkPolicyTestCase, 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, 'network:get_all', target) + self.mox.ReplayAll() + network_manager.check_policy(self.context, 'get_all') + self.mox.UnsetStubs() + self.mox.VerifyAll() -- cgit