summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
Diffstat (limited to 'nova')
-rw-r--r--nova/db/sqlalchemy/api.py20
-rw-r--r--nova/network/linux_net.py2
-rw-r--r--nova/tests/virt_unittest.py121
-rw-r--r--nova/virt/libvirt_conn.py165
4 files changed, 290 insertions, 18 deletions
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index afa55fc03..21b991548 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -574,12 +574,14 @@ def instance_get(context, instance_id, session=None):
if is_admin_context(context):
result = session.query(models.Instance).\
options(joinedload('security_groups')).\
+ options(joinedload_all('security_groups.rules')).\
filter_by(id=instance_id).\
filter_by(deleted=can_read_deleted(context)).\
first()
elif is_user_context(context):
result = session.query(models.Instance).\
options(joinedload('security_groups')).\
+ options(joinedload_all('security_groups.rules')).\
filter_by(project_id=context.project_id).\
filter_by(id=instance_id).\
filter_by(deleted=False).\
@@ -1506,6 +1508,24 @@ def security_group_rule_get(context, security_group_rule_id, session=None):
@require_context
+def security_group_rule_get_by_security_group(context, security_group_id, session=None):
+ if not session:
+ session = get_session()
+ if is_admin_context(context):
+ result = session.query(models.SecurityGroupIngressRule).\
+ filter_by(deleted=can_read_deleted(context)).\
+ filter_by(parent_group_id=security_group_id).\
+ all()
+ else:
+ # TODO(vish): Join to group and check for project_id
+ result = session.query(models.SecurityGroupIngressRule).\
+ filter_by(deleted=False).\
+ filter_by(parent_group_id=security_group_id).\
+ all()
+ return result
+
+
+@require_context
def security_group_rule_create(context, values):
security_group_rule_ref = models.SecurityGroupIngressRule()
security_group_rule_ref.update(values)
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 7b00e65d4..3803f886e 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -160,6 +160,8 @@ def ensure_bridge(bridge, interface, net_attrs=None):
_execute("sudo ifconfig %s up" % bridge)
_confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge)
_confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge)
+ _execute("sudo iptables -N nova-local", check_exit_code=False)
+ _confirm_rule("FORWARD", "-j nova-local")
def get_dhcp_hosts(context, network_id):
diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py
index 4bbf2b50b..6c0f379da 100644
--- a/nova/tests/virt_unittest.py
+++ b/nova/tests/virt_unittest.py
@@ -43,15 +43,14 @@ class LibvirtConnTestCase(test.TrialTestCase):
def test_get_uri_and_template(self):
ip = '10.11.12.13'
- instance = {'internal_id': 1,
- 'memory_kb': '1024000',
- 'basepath': '/some/path',
- 'bridge_name': 'br100',
- 'mac_address': '02:12:34:46:56:67',
- 'vcpus': 2,
- 'project_id': 'fake',
- 'bridge': 'br101',
- 'instance_type': 'm1.small'}
+ instance = { 'memory_kb': '1024000',
+ 'basepath': '/some/path',
+ 'bridge_name': 'br100',
+ 'mac_address': '02:12:34:46:56:67',
+ 'vcpus': 2,
+ 'project_id': 'fake',
+ 'bridge': 'br101',
+ 'instance_type': 'm1.small'}
user_context = context.RequestContext(project=self.project,
user=self.user)
@@ -123,6 +122,108 @@ class LibvirtConnTestCase(test.TrialTestCase):
self.manager.delete_user(self.user)
+class IptablesFirewallTestCase(test.TrialTestCase):
+ def setUp(self):
+ super(IptablesFirewallTestCase, self).setUp()
+
+ self.manager = manager.AuthManager()
+ self.user = self.manager.create_user('fake', 'fake', 'fake',
+ admin=True)
+ self.project = self.manager.create_project('fake', 'fake', 'fake')
+ self.context = context.RequestContext('fake', 'fake')
+ self.network = utils.import_object(FLAGS.network_manager)
+ self.fw = libvirt_conn.IptablesFirewallDriver()
+
+ def tearDown(self):
+ self.manager.delete_project(self.project)
+ self.manager.delete_user(self.user)
+ super(IptablesFirewallTestCase, self).tearDown()
+
+ def _p(self, *args, **kwargs):
+ if 'iptables-restore' in args:
+ print ' '.join(args), kwargs['stdin']
+ if 'iptables-save' in args:
+ return
+ in_rules = ['# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010',
+ '*filter',
+ ':INPUT ACCEPT [969615:281627771]',
+ ':FORWARD ACCEPT [0:0]',
+ ':OUTPUT ACCEPT [915599:63811649]',
+ ':nova-block-ipv4 - [0:0]',
+ '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ',
+ '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ',
+ '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ',
+ '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ',
+ '-A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT ',
+ '-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ',
+ '-A FORWARD -i virbr0 -o virbr0 -j ACCEPT ',
+ '-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable ',
+ '-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable ',
+ 'COMMIT',
+ '# Completed on Mon Dec 6 11:54:13 2010']
+
+ def test_static_filters(self):
+ self.fw.execute = self._p
+ instance_ref = db.instance_create(self.context,
+ {'user_id': 'fake',
+ 'project_id': 'fake'})
+ ip = '10.11.12.13'
+
+ network_ref = self.network.get_network(self.context)
+
+ fixed_ip = {'address': ip,
+ 'network_id': network_ref['id']}
+
+ admin_ctxt = context.get_admin_context()
+ db.fixed_ip_create(admin_ctxt, fixed_ip)
+ db.fixed_ip_update(admin_ctxt, ip, {'allocated': True,
+ 'instance_id': instance_ref['id']})
+
+
+ secgroup = db.security_group_create(admin_ctxt,
+ {'user_id': 'fake',
+ 'project_id': 'fake',
+ 'name': 'testgroup',
+ 'description': 'test group'})
+
+ db.security_group_rule_create(admin_ctxt,
+ {'parent_group_id': secgroup['id'],
+ 'protocol': 'tcp',
+ 'from_port': 80,
+ 'to_port': 81,
+ 'cidr': '192.168.10.0/24'})
+
+ db.instance_add_security_group(admin_ctxt, instance_ref['id'],
+ secgroup['id'])
+ instance_ref = db.instance_get(admin_ctxt, instance_ref['id'])
+
+ self.fw.add_instance(instance_ref)
+
+ out_rules = self.fw.modify_rules(self.in_rules)
+
+ in_rules = filter(lambda l: not l.startswith('#'), self.in_rules)
+ for rule in in_rules:
+ if not 'nova' in rule:
+ self.assertTrue(rule in out_rules, 'Rule went missing: %s' % rule)
+
+ print '\n'.join(out_rules)
+
+ def est_stuff(self):
+ self.fw.execute = self._p
+ cloud_controller = cloud.CloudController()
+ cloud_controller.create_security_group(self.context,
+ 'testgroup',
+ 'test group description')
+ cloud_controller.authorize_security_group_ingress(self.context,
+ 'testgroup',
+ from_port='80',
+ to_port='81',
+ ip_protocol='tcp',
+ cidr_ip='0.0.0.0/0')
+
+ self.fw._apply_ruleset()
+
+
class NWFilterTestCase(test.TrialTestCase):
def setUp(self):
@@ -130,8 +231,6 @@ class NWFilterTestCase(test.TrialTestCase):
class Mock(object):
pass
- #def __call__(self, *args, **kwargs):
- # return
self.manager = manager.AuthManager()
self.user = self.manager.create_user('fake', 'fake', 'fake',
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index a0149c5ca..495ee020d 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -104,7 +104,7 @@ flags.DEFINE_string('libvirt_uri',
flags.DEFINE_bool('allow_project_net_traffic',
True,
'Whether to allow in project network traffic')
-flags.DEFINE_bool('firewall_driver', None,
+flags.DEFINE_string('firewall_driver', 'nova.virt.libvirt_conn.IptablesFirewallDriver',
'Firewall driver (defaults to nwfilter)')
@@ -677,7 +677,7 @@ class FirewallDriver(object):
"""
raise NotImplementedError()
- def refresh_security_group(security_group_id):
+ def refresh_security_group(self, security_group_id):
"""Refresh security group from data store
Gets called when changes have been made to the security
@@ -734,6 +734,7 @@ class NWFilterFirewall(FirewallDriver):
def __init__(self, get_connection):
self._libvirt_get_connection = get_connection
self.static_filters_configured = False
+ self.handle_security_groups = False
def _get_connection(self):
return self._libvirt_get_connection()
@@ -763,12 +764,14 @@ class NWFilterFirewall(FirewallDriver):
def setup_basic_filtering(self, instance):
"""Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
+ logging.info('called setup_basic_filtering in nwfilter')
if self.handle_security_groups:
# No point in setting up a filter set that we'll be overriding
# anyway.
return
+ logging.info('ensuring static filters')
self._ensure_static_filters()
instance_filter_name = self._instance_filter_name(instance)
@@ -841,10 +844,6 @@ class NWFilterFirewall(FirewallDriver):
net = IPy.IP(cidr)
return str(net.net()), str(net.netmask())
- def setup_security_groups_filtering(self, instance):
- """Set up basic filtering (MAC, IP, and ARP spoofing protection)
- as well as security groups filtering."""
-
@defer.inlineCallbacks
def prepare_instance_filter(self, instance):
@@ -874,7 +873,7 @@ class NWFilterFirewall(FirewallDriver):
instance_secgroup_filter_children += [('nova-project-%s' %
instance['project_id'])]
- for security_group in instance.security_groups:
+ for security_group in db.security_group_get_by_instance(instance['id']):
yield self.refresh_security_group(security_group['id'])
instance_secgroup_filter_children += [('nova-secgroup-%s' %
@@ -925,3 +924,155 @@ class NWFilterFirewall(FirewallDriver):
def _instance_filter_name(self, instance):
return 'nova-instance-%s' % instance['name']
+
+class IptablesFirewallDriver(FirewallDriver):
+ def __init__(self, execute=None):
+ self.execute = execute or utils.execute
+ self.instances = set()
+
+ def apply_instance_filter(self, instance):
+ """No-op. Everything is done in prepare_instance_filter"""
+ pass
+
+ def remove_instance(self, instance):
+ self.instances.remove(instance)
+
+ def add_instance(self, instance):
+ self.instances.add(instance)
+
+ def prepare_instance_filter(self, instance):
+ self.add_instance(instance)
+ self.apply_ruleset()
+
+ def apply_ruleset(self):
+ current_filter, _ = self.execute('sudo iptables-save -t filter')
+ current_lines = current_filter.split('\n')
+ new_filter = self.modify_rules(current_lines)
+ self.execute('sudo iptables-restore',
+ process_input='\n'.join(new_filter))
+
+ def modify_rules(self, current_lines):
+ ctxt = context.get_admin_context()
+ # Remove any trace of nova rules.
+ new_filter = filter(lambda l: 'nova-' not in l, current_lines)
+
+ seen_chains = False
+ for rules_index in range(len(new_filter)):
+ if not seen_chains:
+ if new_filter[rules_index].startswith(':'):
+ seen_chains = True
+ elif seen_chains == 1:
+ if not new_filter[rules_index].startswith(':'):
+ break
+
+
+ our_chains = [':nova-ipv4-fallback - [0:0]']
+ our_rules = ['-A nova-ipv4-fallback -j DROP']
+
+ our_chains += [':nova-local - [0:0]']
+ our_rules += ['-A FORWARD -j nova-local']
+
+ security_groups = set()
+ # Add our chains
+ # First, we add instance chains and rules
+ for instance in self.instances:
+ chain_name = self._instance_chain_name(instance)
+ ip_address = self._ip_for_instance(instance)
+
+ our_chains += [':%s - [0:0]' % chain_name]
+
+ # Jump to the per-instance chain
+ our_rules += ['-A nova-local -d %s -j %s' % (ip_address,
+ chain_name)]
+
+ # Always drop invalid packets
+ our_rules += ['-A %s -m state --state '
+ 'INVALID -j DROP' % (chain_name,)]
+
+ # Allow established connections
+ our_rules += ['-A %s -m state --state '
+ 'ESTABLISHED,RELATED -j ACCEPT' % (chain_name,)]
+
+ # Jump to each security group chain in turn
+ for security_group in \
+ db.security_group_get_by_instance(ctxt,
+ instance['id']):
+ security_groups.add(security_group)
+
+ sg_chain_name = self._security_group_chain_name(security_group)
+
+ our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
+
+ # Allow DHCP responses
+ dhcp_server = self._dhcp_server_for_instance(instance)
+ our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' % (chain_name, dhcp_server)]
+
+ # If nothing matches, jump to the fallback chain
+ our_rules += ['-A %s -j nova-ipv4-fallback' % (chain_name,)]
+
+
+ # then, security group chains and rules
+ for security_group in security_groups:
+ chain_name = self._security_group_chain_name(security_group)
+ our_chains += [':%s - [0:0]' % chain_name]
+
+ rules = \
+ db.security_group_rule_get_by_security_group(ctxt,
+ security_group['id'])
+
+ for rule in rules:
+ logging.info('%r', rule)
+ args = ['-A', chain_name, '-p', rule.protocol]
+
+ if rule.cidr:
+ args += ['-s', rule.cidr]
+ else:
+ # Something about ipsets
+ pass
+
+ if rule.protocol in ['udp', 'tcp']:
+ if rule.from_port == rule.to_port:
+ args += ['--dport', '%s' % (rule.from_port,)]
+ else:
+ args += ['-m', 'multiport',
+ '--dports', '%s:%s' % (rule.from_port,
+ rule.to_port)]
+ elif rule.protocol == 'icmp':
+ icmp_type = rule.from_port
+ icmp_code = rule.to_port
+
+ if icmp_type == '-1':
+ icmp_type_arg = None
+ else:
+ icmp_type_arg = '%s' % icmp_type
+ if not icmp_code == '-1':
+ icmp_type_arg += '/%s' % icmp_code
+
+ if icmp_type_arg:
+ args += ['-m', 'icmp', '--icmp_type', icmp_type_arg]
+
+ args += ['-j ACCEPT']
+ our_rules += [' '.join(args)]
+
+ new_filter[rules_index:rules_index] = our_rules
+ new_filter[rules_index:rules_index] = our_chains
+ logging.info('new_filter: %s', '\n'.join(new_filter))
+ return new_filter
+
+ def refresh_security_group(self, security_group):
+ self.apply_ruleset()
+
+ def _security_group_chain_name(self, security_group):
+ return 'nova-sg-%s' % (security_group['id'],)
+
+ def _instance_chain_name(self, instance):
+ return 'nova-inst-%s' % (instance['id'],)
+
+ def _ip_for_instance(self, instance):
+ return db.instance_get_fixed_address(context.get_admin_context(),
+ instance['id'])
+
+ def _dhcp_server_for_instance(self, instance):
+ network = db.project_get_network(context.get_admin_context(),
+ instance['project_id'])
+ return network['gateway']