summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSoren Hansen <soren.hansen@rackspace.com>2010-09-10 14:56:36 +0200
committerSoren Hansen <soren.hansen@rackspace.com>2010-09-10 14:56:36 +0200
commitc3dd0aa79d982d8f34172e6023d4b632ea23f2b9 (patch)
tree490a63f31baec93a2a705f017077a1372e4e94aa
parentecbbfa343edf0ca0e82b35dc655fa23701bbdf22 (diff)
downloadnova-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.py2
-rw-r--r--nova/db/sqlalchemy/api.py1
-rw-r--r--nova/endpoint/cloud.py1
-rw-r--r--nova/tests/virt_unittest.py50
-rw-r--r--nova/virt/libvirt_conn.py63
-rw-r--r--run_tests.py1
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