From c3dd0aa79d982d8f34172e6023d4b632ea23f2b9 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 10 Sep 2010 14:56:36 +0200 Subject: First pass of nwfilter based security group implementation. It is not where it is supposed to be and it does not actually do anything yet. --- nova/auth/manager.py | 2 +- nova/db/sqlalchemy/api.py | 1 + nova/endpoint/cloud.py | 1 - nova/tests/virt_unittest.py | 50 ++++++++++++++++++++++++++++++++--- nova/virt/libvirt_conn.py | 63 +++++++++++++++++++++++++++++++++++++++++++++ run_tests.py | 1 + 6 files changed, 113 insertions(+), 5 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 6aa5721c8..281e2d8f0 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -649,7 +649,7 @@ class AuthManager(object): def delete_user(self, user): """Deletes a user""" with self.driver() as drv: - for security_group in db.security_group_get_by_user(context = {}, user_id=user.id): + for security_group in db.security_group_get_by_user(context = {}, user_id=User.safe_id(user)): db.security_group_destroy({}, security_group.id) drv.delete_user(User.safe_id(user)) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 4027e901c..622e76cd7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -598,6 +598,7 @@ def security_group_create(_context, values): def security_group_get_by_id(_context, security_group_id): with managed_session() as session: return session.query(models.SecurityGroup) \ + .options(eagerload('rules')) \ .get(security_group_id) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index e6eca9850..5e5ed6c5e 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -299,7 +299,6 @@ class CloudController(object): def authorize_security_group_ingress(self, context, group_name, to_port=None, from_port=None, ip_protocol=None, cidr_ip=None, - user_id=None, source_security_group_name=None, source_security_group_owner_id=None): security_group = db.security_group_get_by_user_and_name(context, diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 2aab16809..b8dcec12b 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -14,23 +14,30 @@ # License for the specific language governing permissions and limitations # under the License. +from xml.dom.minidom import parseString + from nova import flags from nova import test +from nova.endpoint import cloud from nova.virt import libvirt_conn FLAGS = flags.FLAGS class LibvirtConnTestCase(test.TrialTestCase): - def test_get_uri_and_template(self): + def bitrot_test_get_uri_and_template(self): class MockDataModel(object): + def __getitem__(self, name): + return self.datamodel[name] + def __init__(self): self.datamodel = { 'name' : 'i-cafebabe', 'memory_kb' : '1024000', 'basepath' : '/some/path', 'bridge_name' : 'br100', 'mac_address' : '02:12:34:46:56:67', - 'vcpus' : 2 } + 'vcpus' : 2, + 'project_id' : None } type_uri_map = { 'qemu' : ('qemu:///system', [lambda s: '' in s, @@ -53,7 +60,7 @@ class LibvirtConnTestCase(test.TrialTestCase): self.assertEquals(uri, expected_uri) for i, check in enumerate(checks): - xml = conn.toXml(MockDataModel()) + xml = conn.to_xml(MockDataModel()) self.assertTrue(check(xml), '%s failed check %d' % (xml, i)) # Deliberately not just assigning this string to FLAGS.libvirt_uri and @@ -67,3 +74,40 @@ class LibvirtConnTestCase(test.TrialTestCase): uri, template = conn.get_uri_and_template() self.assertEquals(uri, testuri) + +class NWFilterTestCase(test.TrialTestCase): + def test_stuff(self): + cloud_controller = cloud.CloudController() + class FakeContext(object): + pass + + context = FakeContext() + context.user = FakeContext() + context.user.id = 'fake' + context.user.is_superuser = lambda:True + cloud_controller.create_security_group(context, 'testgroup', 'test group description') + cloud_controller.authorize_security_group_ingress(context, 'testgroup', from_port='80', + to_port='81', ip_protocol='tcp', + cidr_ip='0.0.0.0/0') + + fw = libvirt_conn.NWFilterFirewall() + xml = fw.security_group_to_nwfilter_xml(1) + + dom = parseString(xml) + self.assertEqual(dom.firstChild.tagName, 'filter') + + rules = dom.getElementsByTagName('rule') + self.assertEqual(len(rules), 1) + + # It's supposed to allow inbound traffic. + self.assertEqual(rules[0].getAttribute('action'), 'allow') + self.assertEqual(rules[0].getAttribute('direction'), 'in') + + # Must be lower priority than the base filter (which blocks everything) + self.assertTrue(int(rules[0].getAttribute('priority')) < 1000) + + ip_conditions = rules[0].getElementsByTagName('ip') + self.assertEqual(len(ip_conditions), 1) + self.assertEqual(ip_conditions[0].getAttribute('protocol'), 'tcp') + self.assertEqual(ip_conditions[0].getAttribute('dstportstart'), '80') + self.assertEqual(ip_conditions[0].getAttribute('dstportend'), '81') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index e26030158..7bf2a68b1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -426,3 +426,66 @@ class LibvirtConnection(object): """ domain = self._conn.lookupByName(instance_name) return domain.interfaceStats(interface) + + +class NWFilterFirewall(object): + """ + This class implements a network filtering mechanism versatile + enough for EC2 style Security Group filtering by leveraging + libvirt's nwfilter. + + First, all instances get a filter ("nova-base-filter") applied. + This filter drops all incoming ipv4 and ipv6 connections. + Outgoing connections are never blocked. + + Second, every security group maps to a nwfilter filter(*). + NWFilters can be updated at runtime and changes are applied + immediately, so changes to security groups can be applied at + runtime (as mandated by the spec). + + Security group rules are named "nova-secgroup-" where + is the internal id of the security group. They're applied only on + hosts that have instances in the security group in question. + + Updates to security groups are done by updating the data model + (in response to API calls) followed by a request sent to all + the nodes with instances in the security group to refresh the + security group. + + Outstanding questions: + + The name is unique, so would there be any good reason to sync + the uuid across the nodes (by assigning it from the datamodel)? + + + (*) This sentence brought to you by the redundancy department of + redundancy. + """ + + def __init__(self): + pass + + def nova_base_filter(self): + return ''' + 26717364-50cf-42d1-8185-29bf893ab110 + + + + + + +''' + + def security_group_to_nwfilter_xml(self, security_group_id): + security_group = db.security_group_get_by_id({}, security_group_id) + rule_xml = "" + for rule in security_group.rules: + rule_xml += "" + if rule.cidr: + rule_xml += ("") % \ + (rule.cidr, rule.protocol, + rule.from_port, rule.to_port) + rule_xml += "" + xml = '''%s''' % (security_group_id, rule_xml,) + return xml diff --git a/run_tests.py b/run_tests.py index d5dc5f934..75ab561a1 100644 --- a/run_tests.py +++ b/run_tests.py @@ -62,6 +62,7 @@ from nova.tests.rpc_unittest import * from nova.tests.service_unittest import * from nova.tests.validator_unittest import * from nova.tests.volume_unittest import * +from nova.tests.virt_unittest import * FLAGS = flags.FLAGS -- cgit