diff options
| author | Soren Hansen <soren.hansen@rackspace.com> | 2010-09-10 14:56:36 +0200 |
|---|---|---|
| committer | Soren Hansen <soren.hansen@rackspace.com> | 2010-09-10 14:56:36 +0200 |
| commit | c3dd0aa79d982d8f34172e6023d4b632ea23f2b9 (patch) | |
| tree | 490a63f31baec93a2a705f017077a1372e4e94aa | |
| parent | ecbbfa343edf0ca0e82b35dc655fa23701bbdf22 (diff) | |
| download | nova-c3dd0aa79d982d8f34172e6023d4b632ea23f2b9.tar.gz nova-c3dd0aa79d982d8f34172e6023d4b632ea23f2b9.tar.xz nova-c3dd0aa79d982d8f34172e6023d4b632ea23f2b9.zip | |
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.
| -rw-r--r-- | nova/auth/manager.py | 2 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 1 | ||||
| -rw-r--r-- | nova/endpoint/cloud.py | 1 | ||||
| -rw-r--r-- | nova/tests/virt_unittest.py | 50 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 63 | ||||
| -rw-r--r-- | 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: '<domain type=\'qemu\'>' 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-<id>" where <id> + 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 '''<filter name='nova-base-filter' chain='root'> + <uuid>26717364-50cf-42d1-8185-29bf893ab110</uuid> + <rule action='drop' direction='in' priority='1000'> + <ipv6 /> + </rule> + <rule action='drop' direction='in' priority='1000'> + <ip /> + </rule> +</filter>''' + + 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 += "<rule action='allow' direction='in' priority='900'>" + if rule.cidr: + rule_xml += ("<ip srcipaddr='%s' protocol='%s' " + + "dstportstart='%s' dstportend='%s' />") % \ + (rule.cidr, rule.protocol, + rule.from_port, rule.to_port) + rule_xml += "</rule>" + xml = '''<filter name='nova-secgroup-%d' chain='root'>%s</filter>''' % (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 |
