Merge "nova-net: Remove firewall support (pt. 3)"
This commit is contained in:
commit
6fd8610da9
|
@ -856,12 +856,6 @@ Related options:
|
|||
|
||||
* iptables_top_regex
|
||||
"""),
|
||||
# NOTE(sfinucan): While this is predominantly used by nova-network, there
|
||||
# appears to be a very limited use case where iptables rules are also used
|
||||
# with neutron. Namely, when neutron's port filtering is disabled, security
|
||||
# groups are disabled, and the 'firewall_driver' has been set to the
|
||||
# libvirt IPTables driver. We may wish to remove this functionality in
|
||||
# favour of neutron in the future.
|
||||
cfg.StrOpt("iptables_drop_action",
|
||||
default="DROP",
|
||||
deprecated_for_removal=True,
|
||||
|
@ -879,12 +873,6 @@ Possible values:
|
|||
|
||||
* A string representing an iptables chain. The default is DROP.
|
||||
"""),
|
||||
# NOTE(sfinucan): While this is predominantly used by nova-network, there
|
||||
# appears to be a very limited use case where iptables rules are also used
|
||||
# with neutron. Namely, when neutron's port filtering is disabled, security
|
||||
# groups are disabled, and the 'firewall_driver' has been set to the
|
||||
# libvirt IPTables driver. We may wish to remove this functionality in
|
||||
# favour of neutron in the future.
|
||||
cfg.BoolOpt('defer_iptables_apply',
|
||||
default=False,
|
||||
deprecated_for_removal=True,
|
||||
|
@ -1135,66 +1123,6 @@ of Neutron in your deployment.
|
|||
Related options:
|
||||
|
||||
* ``use_neutron``
|
||||
"""),
|
||||
cfg.StrOpt('firewall_driver',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='16.0.0',
|
||||
deprecated_reason="""
|
||||
nova-network is deprecated, as are any related configuration options.
|
||||
""",
|
||||
default='nova.virt.firewall.NoopFirewallDriver',
|
||||
help="""
|
||||
Firewall driver to use with ``nova-network`` service.
|
||||
|
||||
This option only applies when using the ``nova-network`` service. When using
|
||||
another networking services, such as Neutron, this should be to set to the
|
||||
``nova.virt.firewall.NoopFirewallDriver``.
|
||||
|
||||
Possible values:
|
||||
|
||||
* ``nova.virt.firewall.IptablesFirewallDriver``
|
||||
* ``nova.virt.firewall.NoopFirewallDriver``
|
||||
* ``nova.virt.libvirt.firewall.IptablesFirewallDriver``
|
||||
* [...]
|
||||
|
||||
Related options:
|
||||
|
||||
* ``use_neutron``: This must be set to ``False`` to enable ``nova-network``
|
||||
networking
|
||||
"""),
|
||||
cfg.BoolOpt('allow_same_net_traffic',
|
||||
default=True,
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='16.0.0',
|
||||
deprecated_reason="""
|
||||
nova-network is deprecated, as are any related configuration options.
|
||||
""",
|
||||
help="""
|
||||
Determine whether to allow network traffic from same network.
|
||||
|
||||
When set to true, hosts on the same subnet are not filtered and are allowed
|
||||
to pass all types of traffic between them. On a flat network, this allows
|
||||
all instances from all projects unfiltered communication. With VLAN
|
||||
networking, this allows access between instances within the same project.
|
||||
|
||||
This option only applies when using the ``nova-network`` service. When using
|
||||
another networking services, such as Neutron, security groups or other
|
||||
approaches should be used.
|
||||
|
||||
Possible values:
|
||||
|
||||
* True: Network traffic should be allowed pass between all instances on the
|
||||
same network, regardless of their tenant and security policies
|
||||
* False: Network traffic should not be allowed pass between instances unless
|
||||
it is unblocked in a security group
|
||||
|
||||
Related options:
|
||||
|
||||
* ``use_neutron``: This must be set to ``False`` to enable ``nova-network``
|
||||
networking
|
||||
* ``firewall_driver``: This must be set to
|
||||
``nova.virt.libvirt.firewall.IptablesFirewallDriver`` to ensure the
|
||||
libvirt firewall driver is enabled.
|
||||
"""),
|
||||
]
|
||||
|
||||
|
|
|
@ -212,9 +212,8 @@ def _get_virt_name(regex, data):
|
|||
return None
|
||||
driver = m.group(1)
|
||||
# Ignore things we mis-detect as virt drivers in the regex
|
||||
if driver in ["test_virt_drivers", "driver", "firewall",
|
||||
"disk", "api", "imagecache", "cpu", "hardware",
|
||||
"image"]:
|
||||
if driver in ["test_virt_drivers", "driver", "disk", "api", "imagecache",
|
||||
"cpu", "hardware", "image"]:
|
||||
return None
|
||||
return driver
|
||||
|
||||
|
|
|
@ -67,10 +67,6 @@ class HackingTestCase(test.NoDBTestCase):
|
|||
"from nova.virt.libvirt import utils as libvirt_utils",
|
||||
"./nova/virt/libvirt/driver.py"))
|
||||
|
||||
self.assertIsNone(checks.import_no_virt_driver_import_deps(
|
||||
"import nova.virt.firewall",
|
||||
"./nova/virt/libvirt/firewall.py"))
|
||||
|
||||
def test_virt_driver_config_vars(self):
|
||||
self.assertIsInstance(checks.import_no_virt_driver_config_deps(
|
||||
"CONF.import_opt('volume_drivers', "
|
||||
|
|
|
@ -1,764 +0,0 @@
|
|||
# Copyright 2010 OpenStack Foundation
|
||||
# Copyright 2012 University Of Minho
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
from xml.dom import minidom
|
||||
|
||||
from eventlet import greenthread
|
||||
from lxml import etree
|
||||
import mock
|
||||
from oslo_concurrency.fixture import lockutils as lock_fixture
|
||||
from oslo_utils.fixture import uuidsentinel as uuids
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from nova import exception
|
||||
from nova.network import linux_net
|
||||
from nova import objects
|
||||
from nova import test
|
||||
from nova.tests.unit import fake_network
|
||||
from nova.tests.unit.virt.libvirt import fakelibvirt
|
||||
from nova.virt.libvirt import firewall
|
||||
from nova.virt.libvirt import host
|
||||
from nova.virt import netutils
|
||||
|
||||
_fake_network_info = fake_network.fake_get_instance_nw_info
|
||||
_fake_stub_out_get_nw_info = fake_network.stub_out_nw_api_get_instance_nw_info
|
||||
_ipv4_like = fake_network.ipv4_like
|
||||
|
||||
|
||||
class NWFilterFakes(object):
|
||||
def __init__(self):
|
||||
self.filters = {}
|
||||
|
||||
def nwfilterLookupByName(self, name):
|
||||
if name in self.filters:
|
||||
return self.filters[name]
|
||||
raise fakelibvirt.libvirtError('Filter Not Found')
|
||||
|
||||
def filterDefineXMLMock(self, xml):
|
||||
class FakeNWFilterInternal(object):
|
||||
def __init__(self, parent, name, u, xml):
|
||||
self.name = name
|
||||
self.uuid = u
|
||||
self.parent = parent
|
||||
self.xml = xml
|
||||
|
||||
def XMLDesc(self, flags):
|
||||
return self.xml
|
||||
|
||||
def undefine(self):
|
||||
del self.parent.filters[self.name]
|
||||
|
||||
tree = etree.fromstring(xml)
|
||||
name = tree.get('name')
|
||||
u = tree.find('uuid')
|
||||
if u is None:
|
||||
u = uuidutils.generate_uuid(dashed=False)
|
||||
else:
|
||||
u = u.text
|
||||
if name not in self.filters:
|
||||
self.filters[name] = FakeNWFilterInternal(self, name, u, xml)
|
||||
else:
|
||||
if self.filters[name].uuid != u:
|
||||
raise fakelibvirt.libvirtError(
|
||||
"Mismatching name '%s' with uuid '%s' vs '%s'"
|
||||
% (name, self.filters[name].uuid, u))
|
||||
self.filters[name].xml = xml
|
||||
return True
|
||||
|
||||
|
||||
class IptablesFirewallTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(IptablesFirewallTestCase, self).setUp()
|
||||
self.useFixture(lock_fixture.ExternalLockFixture())
|
||||
self.useFixture(fakelibvirt.FakeLibvirtFixture())
|
||||
|
||||
self.fw = firewall.IptablesFirewallDriver(
|
||||
host=host.Host("qemu:///system"))
|
||||
|
||||
in_rules = [
|
||||
'# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011',
|
||||
'*nat',
|
||||
':PREROUTING ACCEPT [1170:189210]',
|
||||
':INPUT ACCEPT [844:71028]',
|
||||
':OUTPUT ACCEPT [5149:405186]',
|
||||
':POSTROUTING ACCEPT [5063:386098]',
|
||||
'# Completed on Tue Dec 18 15:50:25 2012',
|
||||
'# Generated by iptables-save v1.4.12 on Tue Dec 18 15:50:25 201;',
|
||||
'*mangle',
|
||||
':PREROUTING ACCEPT [241:39722]',
|
||||
':INPUT ACCEPT [230:39282]',
|
||||
':FORWARD ACCEPT [0:0]',
|
||||
':OUTPUT ACCEPT [266:26558]',
|
||||
':POSTROUTING ACCEPT [267:26590]',
|
||||
'-A POSTROUTING -o virbr0 -p udp -m udp --dport 68 -j CHECKSUM '
|
||||
'--checksum-fill',
|
||||
'COMMIT',
|
||||
'# Completed on Tue Dec 18 15:50:25 2012',
|
||||
'# 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]',
|
||||
'[0:0] -A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ',
|
||||
'[0:0] -A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED'
|
||||
',ESTABLISHED -j ACCEPT ',
|
||||
'[0:0] -A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ',
|
||||
'[0:0] -A FORWARD -i virbr0 -o virbr0 -j ACCEPT ',
|
||||
'[0:0] -A FORWARD -o virbr0 -j REJECT '
|
||||
'--reject-with icmp-port-unreachable ',
|
||||
'[0:0] -A FORWARD -i virbr0 -j REJECT '
|
||||
'--reject-with icmp-port-unreachable ',
|
||||
'COMMIT',
|
||||
'# Completed on Mon Dec 6 11:54:13 2010',
|
||||
]
|
||||
|
||||
in6_filter_rules = [
|
||||
'# Generated by ip6tables-save v1.4.4 on Tue Jan 18 23:47:56 2011',
|
||||
'*filter',
|
||||
':INPUT ACCEPT [349155:75810423]',
|
||||
':FORWARD ACCEPT [0:0]',
|
||||
':OUTPUT ACCEPT [349256:75777230]',
|
||||
'COMMIT',
|
||||
'# Completed on Tue Jan 18 23:47:56 2011',
|
||||
]
|
||||
|
||||
def _create_instance_ref(self,
|
||||
uuid="74526555-9166-4893-a203-126bdcab0d67"):
|
||||
inst = objects.Instance(
|
||||
id=7,
|
||||
uuid=uuid,
|
||||
user_id="fake",
|
||||
project_id="fake",
|
||||
image_ref='155d900f-4e14-4e4c-a73d-069cbf4541e6',
|
||||
instance_type_id=1)
|
||||
inst.info_cache = objects.InstanceInfoCache()
|
||||
inst.info_cache.deleted = False
|
||||
return inst
|
||||
|
||||
@mock.patch.object(objects.InstanceList, "get_by_security_group_id")
|
||||
@mock.patch.object(objects.SecurityGroupRuleList, "get_by_instance")
|
||||
@mock.patch('nova.privsep.linux_net.iptables_get_rules',
|
||||
return_value=('', ''))
|
||||
@mock.patch('nova.privsep.linux_net.iptables_set_rules',
|
||||
return_value=('', ''))
|
||||
def test_static_filters(self, mock_iptables_set_rules,
|
||||
mock_iptables_get_rules, mock_secrule,
|
||||
mock_instlist):
|
||||
UUID = "2674993b-6adb-4733-abd9-a7c10cc1f146"
|
||||
SRC_UUID = "0e0a76b2-7c52-4bc0-9a60-d83017e42c1a"
|
||||
instance_ref = self._create_instance_ref(UUID)
|
||||
src_instance_ref = self._create_instance_ref(SRC_UUID)
|
||||
|
||||
secgroup = objects.SecurityGroup(id=1,
|
||||
user_id='fake',
|
||||
project_id='fake',
|
||||
name='testgroup',
|
||||
description='test group')
|
||||
|
||||
src_secgroup = objects.SecurityGroup(id=2,
|
||||
user_id='fake',
|
||||
project_id='fake',
|
||||
name='testsourcegroup',
|
||||
description='src group')
|
||||
|
||||
r1 = objects.SecurityGroupRule(parent_group_id=secgroup.id,
|
||||
protocol='icmp',
|
||||
from_port=-1,
|
||||
to_port=-1,
|
||||
cidr='192.168.11.0/24',
|
||||
grantee_group=None)
|
||||
|
||||
r2 = objects.SecurityGroupRule(parent_group_id=secgroup.id,
|
||||
protocol='icmp',
|
||||
from_port=8,
|
||||
to_port=-1,
|
||||
cidr='192.168.11.0/24',
|
||||
grantee_group=None)
|
||||
|
||||
r3 = objects.SecurityGroupRule(parent_group_id=secgroup.id,
|
||||
protocol='tcp',
|
||||
from_port=80,
|
||||
to_port=81,
|
||||
cidr='192.168.10.0/24',
|
||||
grantee_group=None)
|
||||
|
||||
r4 = objects.SecurityGroupRule(parent_group_id=secgroup.id,
|
||||
protocol='tcp',
|
||||
from_port=80,
|
||||
to_port=81,
|
||||
cidr=None,
|
||||
grantee_group=src_secgroup,
|
||||
group_id=src_secgroup.id)
|
||||
|
||||
r5 = objects.SecurityGroupRule(parent_group_id=secgroup.id,
|
||||
protocol=None,
|
||||
cidr=None,
|
||||
grantee_group=src_secgroup,
|
||||
group_id=src_secgroup.id)
|
||||
|
||||
secgroup_list = objects.SecurityGroupList()
|
||||
secgroup_list.objects.append(secgroup)
|
||||
src_secgroup_list = objects.SecurityGroupList()
|
||||
src_secgroup_list.objects.append(src_secgroup)
|
||||
instance_ref.security_groups = secgroup_list
|
||||
src_instance_ref.security_groups = src_secgroup_list
|
||||
|
||||
mock_secrule.return_value = objects.SecurityGroupRuleList(
|
||||
objects=[r1, r2, r3, r4, r5])
|
||||
|
||||
def fake_instlist(ctxt, id):
|
||||
if id == src_secgroup.id:
|
||||
insts = objects.InstanceList()
|
||||
insts.objects.append(src_instance_ref)
|
||||
return insts
|
||||
else:
|
||||
insts = objects.InstanceList()
|
||||
insts.objects.append(instance_ref)
|
||||
return insts
|
||||
mock_instlist.side_effect = fake_instlist
|
||||
|
||||
def fake_iptables_get(ipv4=True):
|
||||
if ipv4:
|
||||
return '\n'.join(self.in_rules), None
|
||||
else:
|
||||
return '\n'.join(self.in6_filter_rules), None
|
||||
mock_iptables_get_rules.side_effect = fake_iptables_get
|
||||
|
||||
def fake_iptables_set(rules, ipv4=True):
|
||||
if '*filter' in rules:
|
||||
if ipv4:
|
||||
self.out_rules = rules
|
||||
else:
|
||||
self.out6_rules = rules
|
||||
return '', ''
|
||||
mock_iptables_set_rules.side_effect = fake_iptables_set
|
||||
|
||||
network_model = _fake_network_info(self, 1)
|
||||
|
||||
self.stub_out('nova.objects.Instance.get_network_info',
|
||||
lambda instance: network_model)
|
||||
|
||||
self.fw.prepare_instance_filter(instance_ref, network_model)
|
||||
self.fw.apply_instance_filter(instance_ref, network_model)
|
||||
|
||||
in_rules = [l for l in self.in_rules if not l.startswith('#')]
|
||||
for rule in in_rules:
|
||||
if 'nova' not in rule:
|
||||
self.assertIn(rule, self.out_rules,
|
||||
'Rule went missing: %s' % rule)
|
||||
|
||||
instance_chain = None
|
||||
for rule in self.out_rules:
|
||||
# This is pretty crude, but it'll do for now
|
||||
# last two octets change
|
||||
if re.search('-d 192.168.[0-9]{1,3}.[0-9]{1,3} -j', rule):
|
||||
instance_chain = rule.split(' ')[-1]
|
||||
break
|
||||
self.assertTrue(instance_chain, "The instance chain wasn't added")
|
||||
|
||||
security_group_chain = None
|
||||
for rule in self.out_rules:
|
||||
# This is pretty crude, but it'll do for now
|
||||
if '-A %s -j' % instance_chain in rule:
|
||||
security_group_chain = rule.split(' ')[-1]
|
||||
break
|
||||
self.assertTrue(security_group_chain,
|
||||
"The security group chain wasn't added")
|
||||
|
||||
regex = re.compile(r'\[0\:0\] -A .* -j ACCEPT -p icmp '
|
||||
'-s 192.168.11.0/24')
|
||||
match_rules = [rule for rule in self.out_rules if regex.match(rule)]
|
||||
self.assertGreater(len(match_rules), 0,
|
||||
"ICMP acceptance rule wasn't added")
|
||||
|
||||
regex = re.compile(r'\[0\:0\] -A .* -j ACCEPT -p icmp -m icmp '
|
||||
'--icmp-type 8 -s 192.168.11.0/24')
|
||||
match_rules = [rule for rule in self.out_rules if regex.match(rule)]
|
||||
self.assertGreater(len(match_rules), 0,
|
||||
"ICMP Echo Request acceptance rule wasn't added")
|
||||
|
||||
for ip in network_model.fixed_ips():
|
||||
if ip['version'] != 4:
|
||||
continue
|
||||
regex = re.compile(r'\[0\:0\] -A .* -j ACCEPT -p tcp -m multiport '
|
||||
'--dports 80:81 -s %s' % ip['address'])
|
||||
match_rules = [rule for rule in self.out_rules
|
||||
if regex.match(rule)]
|
||||
self.assertGreater(len(match_rules), 0,
|
||||
"TCP port 80/81 acceptance rule wasn't added")
|
||||
regex = re.compile(r'\[0\:0\] -A .* -j ACCEPT -s '
|
||||
'%s' % ip['address'])
|
||||
match_rules = [rule for rule in self.out_rules
|
||||
if regex.match(rule)]
|
||||
self.assertGreater(len(match_rules), 0,
|
||||
"Protocol/port-less acceptance rule"
|
||||
" wasn't added")
|
||||
|
||||
regex = re.compile(r'\[0\:0\] -A .* -j ACCEPT -p tcp '
|
||||
'-m multiport --dports 80:81 -s 192.168.10.0/24')
|
||||
match_rules = [rule for rule in self.out_rules if regex.match(rule)]
|
||||
self.assertGreater(len(match_rules), 0,
|
||||
"TCP port 80/81 acceptance rule wasn't added")
|
||||
|
||||
def test_filters_for_instance(self):
|
||||
network_info = _fake_network_info(self, 1)
|
||||
rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info)
|
||||
self.assertEqual(len(rulesv4), 2)
|
||||
self.assertEqual(len(rulesv6), 1)
|
||||
|
||||
@mock.patch.object(objects.SecurityGroupRuleList, "get_by_instance")
|
||||
@mock.patch('nova.privsep.linux_net.iptables_get_rules',
|
||||
return_value=('', ''))
|
||||
@mock.patch('nova.privsep.linux_net.iptables_set_rules',
|
||||
return_value=('', ''))
|
||||
def test_multinic_iptables(self, mock_iptables_set_rules,
|
||||
mock_iptables_get_rules, mock_secrule):
|
||||
mock_secrule.return_value = objects.SecurityGroupRuleList()
|
||||
|
||||
ipv4_rules_per_addr = 1
|
||||
ipv4_addr_per_network = 2
|
||||
ipv6_rules_per_addr = 1
|
||||
ipv6_addr_per_network = 1
|
||||
networks_count = 5
|
||||
instance_ref = self._create_instance_ref()
|
||||
instance_ref.security_groups = objects.SecurityGroupList()
|
||||
network_info = _fake_network_info(self, networks_count,
|
||||
ipv4_addr_per_network)
|
||||
network_info[0]['network']['subnets'][0]['meta']['dhcp_server'] = \
|
||||
'1.1.1.1'
|
||||
ipv4_len = len(self.fw.iptables.ipv4['filter'].rules)
|
||||
ipv6_len = len(self.fw.iptables.ipv6['filter'].rules)
|
||||
inst_ipv4, inst_ipv6 = self.fw.instance_rules(instance_ref,
|
||||
network_info)
|
||||
self.fw.prepare_instance_filter(instance_ref, network_info)
|
||||
ipv4 = self.fw.iptables.ipv4['filter'].rules
|
||||
ipv6 = self.fw.iptables.ipv6['filter'].rules
|
||||
ipv4_network_rules = len(ipv4) - len(inst_ipv4) - ipv4_len
|
||||
ipv6_network_rules = len(ipv6) - len(inst_ipv6) - ipv6_len
|
||||
# Extra rules are for the DHCP request
|
||||
rules = (ipv4_rules_per_addr * ipv4_addr_per_network *
|
||||
networks_count) + 2
|
||||
self.assertEqual(ipv4_network_rules, rules)
|
||||
self.assertEqual(ipv6_network_rules,
|
||||
ipv6_rules_per_addr * ipv6_addr_per_network * networks_count)
|
||||
|
||||
@mock.patch.object(firewall.IptablesFirewallDriver, 'instance_rules')
|
||||
@mock.patch.object(firewall.IptablesFirewallDriver,
|
||||
'add_filters_for_instance')
|
||||
@mock.patch.object(linux_net.IptablesTable, 'has_chain')
|
||||
@mock.patch('nova.privsep.linux_net.iptables_get_rules',
|
||||
return_value=('', ''))
|
||||
@mock.patch('nova.privsep.linux_net.iptables_set_rules',
|
||||
return_value=('', ''))
|
||||
def test_do_refresh_security_group_rules(
|
||||
self, mock_iptables_set_rules,
|
||||
mock_iptables_get_rules, mock_has_chain,
|
||||
mock_add_filters, mock_instance_rules):
|
||||
instance_ref = self._create_instance_ref()
|
||||
|
||||
mock_instance_rules.return_value = (None, None)
|
||||
mock_has_chain.return_value = True
|
||||
|
||||
self.fw.prepare_instance_filter(instance_ref, mock.ANY)
|
||||
self.fw.instance_info[instance_ref['id']] = (instance_ref, None)
|
||||
self.fw.do_refresh_security_group_rules("fake")
|
||||
|
||||
expected_rules_calls = [mock.call(instance_ref, None),
|
||||
mock.call(instance_ref, None)]
|
||||
expected_filter_calls = [mock.call(instance_ref, mock.ANY, mock.ANY,
|
||||
mock.ANY),
|
||||
mock.call(instance_ref, mock.ANY, mock.ANY,
|
||||
mock.ANY)]
|
||||
self.assertEqual(mock_instance_rules.mock_calls,
|
||||
expected_rules_calls)
|
||||
self.assertEqual(mock_add_filters.mock_calls, expected_filter_calls)
|
||||
mock_has_chain.assert_called_once_with(mock.ANY)
|
||||
|
||||
def test_do_refresh_security_group_rules_instance_gone(self):
|
||||
instance1 = objects.Instance(None, id=1, uuid=uuids.instance_1)
|
||||
instance2 = objects.Instance(None, id=2, uuid=uuids.instance_2)
|
||||
self.fw.instance_info = {1: (instance1, 'netinfo1'),
|
||||
2: (instance2, 'netinfo2')}
|
||||
mock_filter = mock.MagicMock()
|
||||
with mock.patch.dict(self.fw.iptables.ipv4, {'filter': mock_filter}):
|
||||
mock_filter.has_chain.return_value = False
|
||||
with mock.patch.object(self.fw, 'instance_rules') as mock_ir:
|
||||
mock_ir.return_value = (None, None)
|
||||
self.fw.do_refresh_security_group_rules('secgroup')
|
||||
self.assertEqual(2, mock_ir.call_count)
|
||||
# NOTE(danms): Make sure that it is checking has_chain each time,
|
||||
# continuing to process all the instances, and never adding the
|
||||
# new chains back if has_chain() is False
|
||||
mock_filter.has_chain.assert_has_calls([mock.call('inst-1'),
|
||||
mock.call('inst-2')],
|
||||
any_order=True)
|
||||
self.assertEqual(0, mock_filter.add_chain.call_count)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterLookupByName")
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterDefineXML")
|
||||
@mock.patch.object(objects.InstanceList, "get_by_security_group_id")
|
||||
@mock.patch.object(objects.SecurityGroupRuleList, "get_by_instance")
|
||||
@mock.patch('nova.privsep.linux_net.iptables_get_rules',
|
||||
return_value=('', ''))
|
||||
@mock.patch('nova.privsep.linux_net.iptables_set_rules',
|
||||
return_value=('', ''))
|
||||
def test_unfilter_instance_undefines_nwfilter(
|
||||
self, mock_iptables_set_rules, mock_iptables_get_rules,
|
||||
mock_secrule, mock_instlist, mock_define, mock_lookup):
|
||||
fakefilter = NWFilterFakes()
|
||||
mock_lookup.side_effect = fakefilter.nwfilterLookupByName
|
||||
mock_define.side_effect = fakefilter.filterDefineXMLMock
|
||||
instance_ref = self._create_instance_ref()
|
||||
instance_ref.security_groups = objects.SecurityGroupList()
|
||||
|
||||
mock_secrule.return_value = objects.SecurityGroupRuleList()
|
||||
|
||||
network_info = _fake_network_info(self, 1)
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
self.fw.prepare_instance_filter(instance_ref, network_info)
|
||||
self.fw.apply_instance_filter(instance_ref, network_info)
|
||||
original_filter_count = len(fakefilter.filters)
|
||||
self.fw.unfilter_instance(instance_ref, network_info)
|
||||
|
||||
# should undefine just the instance filter
|
||||
self.assertEqual(original_filter_count - len(fakefilter.filters), 1)
|
||||
|
||||
|
||||
@mock.patch.object(firewall, 'libvirt', fakelibvirt)
|
||||
class NWFilterTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(NWFilterTestCase, self).setUp()
|
||||
|
||||
self.useFixture(fakelibvirt.FakeLibvirtFixture())
|
||||
|
||||
self.fw = firewall.NWFilterFirewall(host=host.Host("qemu:///system"))
|
||||
|
||||
def _create_security_group(self, instance_ref):
|
||||
secgroup = objects.SecurityGroup(id=1,
|
||||
user_id='fake',
|
||||
project_id='fake',
|
||||
name='testgroup',
|
||||
description='test group description')
|
||||
|
||||
secgroup_list = objects.SecurityGroupList()
|
||||
secgroup_list.objects.append(secgroup)
|
||||
instance_ref.security_groups = secgroup_list
|
||||
|
||||
return secgroup
|
||||
|
||||
def _create_instance(self):
|
||||
inst = objects.Instance(
|
||||
id=7,
|
||||
uuid="74526555-9166-4893-a203-126bdcab0d67",
|
||||
user_id="fake",
|
||||
project_id="fake",
|
||||
image_ref='155d900f-4e14-4e4c-a73d-069cbf4541e6',
|
||||
instance_type_id=1)
|
||||
inst.info_cache = objects.InstanceInfoCache()
|
||||
inst.info_cache.deleted = False
|
||||
return inst
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterDefineXML")
|
||||
def test_creates_base_rule_first(self, mock_define):
|
||||
# These come pre-defined by libvirt
|
||||
self.defined_filters = ['no-mac-spoofing',
|
||||
'no-ip-spoofing',
|
||||
'no-arp-spoofing',
|
||||
'allow-dhcp-server']
|
||||
|
||||
self.recursive_depends = {}
|
||||
for f in self.defined_filters:
|
||||
self.recursive_depends[f] = []
|
||||
|
||||
def fake_define(xml):
|
||||
dom = minidom.parseString(xml)
|
||||
name = dom.firstChild.getAttribute('name')
|
||||
self.recursive_depends[name] = []
|
||||
for f in dom.getElementsByTagName('filterref'):
|
||||
ref = f.getAttribute('filter')
|
||||
self.assertIn(ref, self.defined_filters,
|
||||
('%s referenced filter that does ' +
|
||||
'not yet exist: %s') % (name, ref))
|
||||
dependencies = [ref] + self.recursive_depends[ref]
|
||||
self.recursive_depends[name] += dependencies
|
||||
|
||||
self.defined_filters.append(name)
|
||||
return True
|
||||
|
||||
mock_define.side_effect = fake_define
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
self._create_security_group(instance_ref)
|
||||
|
||||
def _ensure_all_called(mac, allow_dhcp):
|
||||
instance_filter = 'nova-instance-%s-%s' % (instance_ref['name'],
|
||||
mac.translate({ord(':'): None}))
|
||||
requiredlist = ['no-arp-spoofing', 'no-ip-spoofing',
|
||||
'no-mac-spoofing']
|
||||
required_not_list = []
|
||||
if allow_dhcp:
|
||||
requiredlist.append('allow-dhcp-server')
|
||||
else:
|
||||
required_not_list.append('allow-dhcp-server')
|
||||
for required in requiredlist:
|
||||
self.assertIn(required,
|
||||
self.recursive_depends[instance_filter],
|
||||
"Instance's filter does not include %s" %
|
||||
required)
|
||||
for required_not in required_not_list:
|
||||
self.assertNotIn(required_not,
|
||||
self.recursive_depends[instance_filter],
|
||||
"Instance filter includes %s" % required_not)
|
||||
|
||||
network_info = _fake_network_info(self, 1)
|
||||
# since there is one (network_info) there is one vif
|
||||
# pass this vif's mac to _ensure_all_called()
|
||||
# to set the instance_filter properly
|
||||
mac = network_info[0]['address']
|
||||
network_info[0]['network']['subnets'][0]['meta']['dhcp_server'] = \
|
||||
'1.1.1.1'
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
allow_dhcp = True
|
||||
_ensure_all_called(mac, allow_dhcp)
|
||||
|
||||
network_info[0]['network']['subnets'][0]['meta']['dhcp_server'] = None
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
allow_dhcp = False
|
||||
_ensure_all_called(mac, allow_dhcp)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterLookupByName")
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterDefineXML")
|
||||
def test_unfilter_instance_undefines_nwfilters(self,
|
||||
mock_define,
|
||||
mock_lookup):
|
||||
fakefilter = NWFilterFakes()
|
||||
mock_lookup.side_effect = fakefilter.nwfilterLookupByName
|
||||
mock_define.side_effect = fakefilter.filterDefineXMLMock
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
self._create_security_group(instance_ref)
|
||||
|
||||
network_info = _fake_network_info(self, 1)
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
original_filter_count = len(fakefilter.filters)
|
||||
self.fw.unfilter_instance(instance_ref, network_info)
|
||||
self.assertEqual(original_filter_count - len(fakefilter.filters), 1)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterLookupByName")
|
||||
@mock.patch.object(greenthread, 'sleep')
|
||||
def test_unfilter_instance_retry_and_error(self, mock_sleep, mock_lookup):
|
||||
# Tests that we try to undefine the network filter when it's in use
|
||||
# until we hit a timeout. We try two times and sleep once in between.
|
||||
self.flags(live_migration_retry_count=2)
|
||||
in_use = fakelibvirt.libvirtError('nwfilter is in use')
|
||||
in_use.err = (fakelibvirt.VIR_ERR_OPERATION_INVALID,)
|
||||
mock_undefine = mock.Mock(side_effect=in_use)
|
||||
fakefilter = mock.MagicMock(undefine=mock_undefine)
|
||||
mock_lookup.return_value = fakefilter
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
network_info = _fake_network_info(self, 1)
|
||||
|
||||
self.assertRaises(fakelibvirt.libvirtError, self.fw.unfilter_instance,
|
||||
instance_ref, network_info)
|
||||
self.assertEqual(2, mock_lookup.call_count)
|
||||
self.assertEqual(2, mock_undefine.call_count)
|
||||
mock_sleep.assert_called_once_with(1)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterLookupByName")
|
||||
@mock.patch.object(greenthread, 'sleep')
|
||||
def test_unfilter_instance_retry_not_found(self, mock_sleep, mock_lookup):
|
||||
# Tests that we exit if the nw filter is not found.
|
||||
in_use = fakelibvirt.libvirtError('nwfilter is in use')
|
||||
in_use.err = (fakelibvirt.VIR_ERR_OPERATION_INVALID,)
|
||||
not_found = fakelibvirt.libvirtError('no nwfilter with matching name')
|
||||
not_found.err = (fakelibvirt.VIR_ERR_NO_NWFILTER,)
|
||||
mock_undefine = mock.Mock(side_effect=(in_use, not_found))
|
||||
fakefilter = mock.MagicMock(undefine=mock_undefine)
|
||||
mock_lookup.return_value = fakefilter
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
network_info = _fake_network_info(self, 1)
|
||||
|
||||
self.fw.unfilter_instance(instance_ref, network_info)
|
||||
self.assertEqual(2, mock_lookup.call_count)
|
||||
self.assertEqual(2, mock_undefine.call_count)
|
||||
mock_sleep.assert_called_once_with(1)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterLookupByName")
|
||||
@mock.patch.object(greenthread, 'sleep')
|
||||
def test_unfilter_instance_retry_and_pass(self, mock_sleep, mock_lookup):
|
||||
# Tests that we retry on in-use error but pass if undefine() works
|
||||
# while looping.
|
||||
in_use = fakelibvirt.libvirtError('nwfilter is in use')
|
||||
in_use.err = (fakelibvirt.VIR_ERR_OPERATION_INVALID,)
|
||||
mock_undefine = mock.Mock(side_effect=(in_use, None))
|
||||
fakefilter = mock.MagicMock(undefine=mock_undefine)
|
||||
mock_lookup.return_value = fakefilter
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
network_info = _fake_network_info(self, 1)
|
||||
|
||||
self.fw.unfilter_instance(instance_ref, network_info)
|
||||
self.assertEqual(2, mock_lookup.call_count)
|
||||
self.assertEqual(2, mock_undefine.call_count)
|
||||
mock_sleep.assert_called_once_with(1)
|
||||
|
||||
def test_redefining_nwfilters(self):
|
||||
fakefilter = NWFilterFakes()
|
||||
self.fw._conn.nwfilterDefineXML = fakefilter.filterDefineXMLMock
|
||||
self.fw._conn.nwfilterLookupByName = fakefilter.nwfilterLookupByName
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
self._create_security_group(instance_ref)
|
||||
|
||||
network_info = _fake_network_info(self, 1)
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterLookupByName")
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterDefineXML")
|
||||
def test_nwfilter_parameters(self,
|
||||
mock_define,
|
||||
mock_lookup):
|
||||
fakefilter = NWFilterFakes()
|
||||
mock_lookup.side_effect = fakefilter.nwfilterLookupByName
|
||||
mock_define.side_effect = fakefilter.filterDefineXMLMock
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
self._create_security_group(instance_ref)
|
||||
|
||||
network_info = _fake_network_info(self, 1)
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
|
||||
vif = network_info[0]
|
||||
nic_id = vif['address'].replace(':', '')
|
||||
instance_filter_name = self.fw._instance_filter_name(instance_ref,
|
||||
nic_id)
|
||||
f = fakefilter.nwfilterLookupByName(instance_filter_name)
|
||||
tree = etree.fromstring(f.xml)
|
||||
|
||||
for fref in tree.findall('filterref'):
|
||||
parameters = fref.findall('./parameter')
|
||||
for parameter in parameters:
|
||||
subnet_v4, subnet_v6 = vif['network']['subnets']
|
||||
if parameter.get('name') == 'IP':
|
||||
self.assertTrue(_ipv4_like(parameter.get('value'),
|
||||
'192.168'))
|
||||
elif parameter.get('name') == 'DHCPSERVER':
|
||||
dhcp_server = subnet_v4.get('dhcp_server')
|
||||
self.assertEqual(parameter.get('value'), dhcp_server)
|
||||
elif parameter.get('name') == 'RASERVER':
|
||||
ra_server = subnet_v6['gateway']['address'] + "/128"
|
||||
self.assertEqual(parameter.get('value'), ra_server)
|
||||
elif parameter.get('name') == 'PROJNET':
|
||||
ipv4_cidr = subnet_v4['cidr']
|
||||
net, mask = netutils.get_net_and_mask(ipv4_cidr)
|
||||
self.assertEqual(parameter.get('value'), net)
|
||||
elif parameter.get('name') == 'PROJMASK':
|
||||
ipv4_cidr = subnet_v4['cidr']
|
||||
net, mask = netutils.get_net_and_mask(ipv4_cidr)
|
||||
self.assertEqual(parameter.get('value'), mask)
|
||||
elif parameter.get('name') == 'PROJNET6':
|
||||
ipv6_cidr = subnet_v6['cidr']
|
||||
net, prefix = netutils.get_net_and_prefixlen(ipv6_cidr)
|
||||
self.assertEqual(parameter.get('value'), net)
|
||||
elif parameter.get('name') == 'PROJMASK6':
|
||||
ipv6_cidr = subnet_v6['cidr']
|
||||
net, prefix = netutils.get_net_and_prefixlen(ipv6_cidr)
|
||||
self.assertEqual(parameter.get('value'), prefix)
|
||||
else:
|
||||
raise exception.InvalidParameterValue('unknown parameter '
|
||||
'in filter')
|
||||
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterLookupByName")
|
||||
@mock.patch.object(fakelibvirt.virConnect, "nwfilterDefineXML")
|
||||
def test_multinic_base_filter_selection(self,
|
||||
mock_define,
|
||||
mock_lookup):
|
||||
fakefilter = NWFilterFakes()
|
||||
mock_lookup.side_effect = fakefilter.nwfilterLookupByName
|
||||
mock_define.side_effect = fakefilter.filterDefineXMLMock
|
||||
|
||||
instance_ref = self._create_instance()
|
||||
self._create_security_group(instance_ref)
|
||||
|
||||
network_info = _fake_network_info(self, 2)
|
||||
network_info[0]['network']['subnets'][0]['meta']['dhcp_server'] = \
|
||||
'1.1.1.1'
|
||||
|
||||
self.fw.setup_basic_filtering(instance_ref, network_info)
|
||||
|
||||
def assert_filterref(instance, vif, expected=None):
|
||||
expected = expected or []
|
||||
nic_id = vif['address'].replace(':', '')
|
||||
filter_name = self.fw._instance_filter_name(instance, nic_id)
|
||||
f = fakefilter.nwfilterLookupByName(filter_name)
|
||||
tree = etree.fromstring(f.xml)
|
||||
frefs = [fr.get('filter') for fr in tree.findall('filterref')]
|
||||
self.assertEqual(set(expected), set(frefs))
|
||||
|
||||
assert_filterref(instance_ref, network_info[0],
|
||||
expected=['nova-base'])
|
||||
assert_filterref(instance_ref, network_info[1],
|
||||
expected=['nova-nodhcp'])
|
||||
|
||||
@mock.patch.object(firewall.LOG, 'debug')
|
||||
def test_get_filter_uuid_unicode_exception_logging(self, debug):
|
||||
with mock.patch.object(self.fw._conn, 'nwfilterLookupByName') as look:
|
||||
look.side_effect = fakelibvirt.libvirtError(u"\U0001F4A9")
|
||||
self.fw._get_filter_uuid('test')
|
||||
self.assertEqual(2, debug.call_count)
|
||||
self.assertEqual(u"Cannot find UUID for filter '%(name)s': '%(e)s'",
|
||||
debug.call_args_list[0][0][0])
|
||||
|
||||
def test_define_filter_already_exists(self):
|
||||
"""Tests that we ignore a libvirt error when the nw filter already
|
||||
exists for a given name.
|
||||
"""
|
||||
error = fakelibvirt.libvirtError('already exists')
|
||||
error.err = (fakelibvirt.VIR_ERR_OPERATION_FAILED, None,
|
||||
"filter 'nova-no-nd-reflection' already exists with uuid "
|
||||
"e740c5ec-c715-4f73-9874-630cc73d4ac2",)
|
||||
with mock.patch.object(self.fw._conn, 'nwfilterDefineXML',
|
||||
side_effect=error) as define:
|
||||
self.fw._define_filter(mock.sentinel.xml)
|
||||
define.assert_called_once_with(mock.sentinel.xml)
|
||||
|
||||
def test_define_filter_fails_wrong_message(self):
|
||||
"""Tests that we reraise the libvirt error for an operational failure
|
||||
if the error message is something unexpected.
|
||||
"""
|
||||
error = fakelibvirt.libvirtError('already exists')
|
||||
error.err = (fakelibvirt.VIR_ERR_OPERATION_FAILED, None, 'oops',)
|
||||
with mock.patch.object(self.fw._conn, 'nwfilterDefineXML',
|
||||
side_effect=error) as define:
|
||||
self.assertRaises(fakelibvirt.libvirtError,
|
||||
self.fw._define_filter, mock.sentinel.xml)
|
||||
define.assert_called_once_with(mock.sentinel.xml)
|
||||
|
||||
def test_define_filter_fails_wrong_code(self):
|
||||
"""Tests that we reraise the libvirt error for an operational failure
|
||||
if the error code is something unexpected.
|
||||
"""
|
||||
error = fakelibvirt.libvirtError('already exists')
|
||||
error.err = (fakelibvirt.VIR_ERR_OPERATION_TIMEOUT, None, 'timeout',)
|
||||
with mock.patch.object(self.fw._conn, 'nwfilterDefineXML',
|
||||
side_effect=error) as define:
|
||||
self.assertRaises(fakelibvirt.libvirtError,
|
||||
self.fw._define_filter, mock.sentinel.xml)
|
||||
define.assert_called_once_with(mock.sentinel.xml)
|
|
@ -1,587 +0,0 @@
|
|||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import mock
|
||||
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
from nova import test
|
||||
from nova.virt import firewall
|
||||
|
||||
_IPT_DRIVER_CLS = firewall.IptablesFirewallDriver
|
||||
_FN_INSTANCE_RULES = 'instance_rules'
|
||||
_FN_ADD_FILTERS = 'add_filters_for_instance'
|
||||
_FN_DO_BASIC_RULES = '_do_basic_rules'
|
||||
_FN_DO_DHCP_RULES = '_do_dhcp_rules'
|
||||
|
||||
|
||||
class TestIptablesFirewallDriver(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestIptablesFirewallDriver, self).setUp()
|
||||
self.driver = _IPT_DRIVER_CLS()
|
||||
|
||||
@mock.patch('nova.network.linux_net.iptables_manager')
|
||||
def test_constructor(self, iptm_mock):
|
||||
self.driver.__init__()
|
||||
|
||||
self.assertEqual({}, self.driver.instance_info)
|
||||
self.assertFalse(self.driver.dhcp_create)
|
||||
self.assertFalse(self.driver.dhcp_created)
|
||||
self.assertEqual(iptm_mock, self.driver.iptables)
|
||||
|
||||
# NOTE(jaypipes): Here we are not testing the IptablesManager
|
||||
# constructor. We are only testing the calls made against the
|
||||
# IptablesManager singleton during initialization of the
|
||||
# IptablesFirewallDriver.
|
||||
expected = [
|
||||
mock.call.add_chain('sg-fallback'),
|
||||
mock.call.add_rule('sg-fallback', '-j DROP'),
|
||||
]
|
||||
iptm_mock.ipv4.__getitem__.return_value \
|
||||
.assert_has_calls(expected)
|
||||
iptm_mock.ipv6.__getitem__.return_value \
|
||||
.assert_has_calls(expected)
|
||||
|
||||
def test_filter_defer_apply_on(self):
|
||||
with mock.patch.object(self.driver.iptables,
|
||||
'defer_apply_on') as dao_mock:
|
||||
self.driver.filter_defer_apply_on()
|
||||
dao_mock.assert_called_once_with()
|
||||
|
||||
def test_filter_defer_apply_off(self):
|
||||
with mock.patch.object(self.driver.iptables,
|
||||
'defer_apply_off') as dao_mock:
|
||||
self.driver.filter_defer_apply_off()
|
||||
dao_mock.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, 'remove_filters_for_instance')
|
||||
def test_unfilter_instance_valid(self, rfii_mock):
|
||||
with mock.patch.object(self.driver, 'instance_info') as ii_mock, \
|
||||
mock.patch.object(self.driver, 'iptables') as ipt_mock:
|
||||
fake_instance = objects.Instance(id=123)
|
||||
ii_mock.pop.return_value = True
|
||||
|
||||
self.driver.unfilter_instance(fake_instance,
|
||||
mock.sentinel.net_info)
|
||||
|
||||
ii_mock.pop.assert_called_once_with(fake_instance.id, None)
|
||||
rfii_mock.assert_called_once_with(fake_instance)
|
||||
ipt_mock.apply.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, 'remove_filters_for_instance')
|
||||
def test_unfilter_instance_invalid(self, rfii_mock):
|
||||
with mock.patch.object(self.driver, 'instance_info') as ii_mock, \
|
||||
mock.patch.object(self.driver, 'iptables') as ipt_mock:
|
||||
fake_instance = objects.Instance(id=123)
|
||||
ii_mock.pop.return_value = False
|
||||
|
||||
self.driver.unfilter_instance(fake_instance,
|
||||
mock.sentinel.net_info)
|
||||
|
||||
ii_mock.pop.assert_called_once_with(fake_instance.id, None)
|
||||
self.assertFalse(rfii_mock.called)
|
||||
self.assertFalse(ipt_mock.apply.called)
|
||||
|
||||
def setup_instance_filter(self, i_rules_mock):
|
||||
# NOTE(chenli) The IptablesFirewallDriver init method calls the
|
||||
# iptables manager, so we must reset here.
|
||||
self.driver.iptables = mock.MagicMock()
|
||||
|
||||
i_mock = mock.MagicMock(spec=dict)
|
||||
i_mock.id = 'fake_id'
|
||||
i_rules_mock.return_value = (mock.sentinel.v4_rules,
|
||||
mock.sentinel.v6_rules)
|
||||
return i_mock
|
||||
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_ADD_FILTERS)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_INSTANCE_RULES)
|
||||
def test_prepare_instance_filter(self, i_rules_mock, add_filters_mock):
|
||||
i_mock = self.setup_instance_filter(i_rules_mock)
|
||||
|
||||
self.driver.prepare_instance_filter(i_mock, mock.sentinel.net_info)
|
||||
|
||||
i_rules_mock.assert_called_once_with(i_mock, mock.sentinel.net_info)
|
||||
add_filters_mock.assert_called_once_with(
|
||||
i_mock, mock.sentinel.net_info,
|
||||
mock.sentinel.v4_rules, mock.sentinel.v6_rules)
|
||||
self.driver.iptables.apply.assert_called_once_with()
|
||||
# When DHCP created flag is False, make sure we don't set any filters
|
||||
gi_mock = self.driver.iptables.ipv4.__getitem__.return_value
|
||||
self.assertFalse(gi_mock.called)
|
||||
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_ADD_FILTERS)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_INSTANCE_RULES)
|
||||
def test_prepare_instance_filter_with_dhcp_create(self, i_rules_mock,
|
||||
add_filters_mock):
|
||||
|
||||
i_mock = self.setup_instance_filter(i_rules_mock)
|
||||
# add rules when DHCP create is set
|
||||
self.driver.dhcp_create = True
|
||||
|
||||
self.driver.prepare_instance_filter(i_mock, mock.sentinel.net_info)
|
||||
|
||||
expected = [
|
||||
mock.call.add_rule(
|
||||
'INPUT',
|
||||
'-s 0.0.0.0/32 -d 255.255.255.255/32 '
|
||||
'-p udp -m udp --sport 68 --dport 67 -j ACCEPT'),
|
||||
mock.call.add_rule(
|
||||
'FORWARD',
|
||||
'-s 0.0.0.0/32 -d 255.255.255.255/32 '
|
||||
'-p udp -m udp --sport 68 --dport 67 -j ACCEPT')
|
||||
]
|
||||
self.driver.iptables.ipv4.__getitem__.return_value.assert_has_calls(
|
||||
expected)
|
||||
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_ADD_FILTERS)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_INSTANCE_RULES)
|
||||
def test_prepare_instance_filter_recreate(self, i_rules_mock,
|
||||
add_filters_mock):
|
||||
|
||||
i_mock = self.setup_instance_filter(i_rules_mock)
|
||||
# add rules when DHCP create is set and create the rule
|
||||
self.driver.dhcp_create = True
|
||||
self.driver.prepare_instance_filter(i_mock, mock.sentinel.net_info)
|
||||
|
||||
# Check we don't recreate the DHCP rules if we've already
|
||||
# done so (there is a dhcp_created flag on the driver that is
|
||||
# set when prepare_instance_filters() first creates them)
|
||||
self.driver.iptables.ipv4.__getitem__.reset_mock()
|
||||
self.driver.prepare_instance_filter(i_mock, mock.sentinel.net_info)
|
||||
gi_mock = self.driver.iptables.ipv4.__getitem__.return_value
|
||||
self.assertFalse(gi_mock.called)
|
||||
|
||||
def test_create_filter(self):
|
||||
filter = self.driver._create_filter(['myip', 'otherip'], 'mychain')
|
||||
self.assertEqual(filter, ['-d myip -j $mychain',
|
||||
'-d otherip -j $mychain'])
|
||||
|
||||
def test_get_subnets(self):
|
||||
subnet1 = {'version': '1', 'foo': 1}
|
||||
subnet2 = {'version': '2', 'foo': 2}
|
||||
subnet3 = {'version': '1', 'foo': 3}
|
||||
network_info = [{'network': {'subnets': [subnet1, subnet2]}},
|
||||
{'network': {'subnets': [subnet3]}}]
|
||||
subnets = self.driver._get_subnets(network_info, '1')
|
||||
self.assertEqual(subnets, [subnet1, subnet3])
|
||||
|
||||
def get_subnets_mock(self, network_info, version):
|
||||
if version == 4:
|
||||
return [{'ips': [{'address': '1.1.1.1'}, {'address': '2.2.2.2'}]}]
|
||||
if version == 6:
|
||||
return [{'ips': [{'address': '3.3.3.3'}]}]
|
||||
|
||||
def create_filter_mock(self, ips, chain_name):
|
||||
if ips == ['1.1.1.1', '2.2.2.2']:
|
||||
return 'rule1'
|
||||
if ips == ['3.3.3.3']:
|
||||
return 'rule2'
|
||||
|
||||
def test_filters_for_instance(self):
|
||||
self.flags(use_ipv6=True)
|
||||
chain_name = 'mychain'
|
||||
network_info = {'foo': 'bar'}
|
||||
self.driver._get_subnets = mock.Mock(side_effect=self.get_subnets_mock)
|
||||
self.driver._create_filter = \
|
||||
mock.Mock(side_effect=self.create_filter_mock)
|
||||
|
||||
ipv4_rules, ipv6_rules = \
|
||||
self.driver._filters_for_instance(chain_name, network_info)
|
||||
|
||||
self.assertEqual(self.driver._get_subnets.mock_calls,
|
||||
[mock.call(network_info, 4), mock.call(network_info, 6)])
|
||||
self.assertEqual(self.driver._create_filter.mock_calls,
|
||||
[mock.call(['1.1.1.1', '2.2.2.2'], chain_name),
|
||||
mock.call(['3.3.3.3'], chain_name)])
|
||||
self.assertEqual(ipv4_rules, 'rule1')
|
||||
self.assertEqual(ipv6_rules, 'rule2')
|
||||
|
||||
def test_add_filters(self):
|
||||
self.flags(use_ipv6=True)
|
||||
self.driver.iptables.ipv4['filter'].add_rule = mock.Mock()
|
||||
self.driver.iptables.ipv6['filter'].add_rule = mock.Mock()
|
||||
chain_name = 'mychain'
|
||||
ipv4_rules = ['rule1', 'rule2']
|
||||
ipv6_rules = ['rule3', 'rule4']
|
||||
|
||||
self.driver._add_filters(chain_name, ipv4_rules, ipv6_rules)
|
||||
|
||||
self.assertEqual(self.driver.iptables.ipv4['filter'].add_rule.
|
||||
mock_calls, [mock.call(chain_name, 'rule1'),
|
||||
mock.call(chain_name, 'rule2')])
|
||||
self.assertEqual(self.driver.iptables.ipv6['filter'].add_rule.
|
||||
mock_calls, [mock.call(chain_name, 'rule3'),
|
||||
mock.call(chain_name, 'rule4')])
|
||||
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, '_instance_chain_name',
|
||||
return_value=mock.sentinel.mychain)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, '_filters_for_instance',
|
||||
return_value=[mock.sentinel.ipv4_rules,
|
||||
mock.sentinel.ipv6_rules])
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, '_add_filters')
|
||||
def test_add_filters_for_instance(self, add_filters_mock,
|
||||
ffi_mock, icn_mock):
|
||||
self.flags(use_ipv6=True)
|
||||
with mock.patch.object(self.driver.iptables.ipv6['filter'],
|
||||
'add_chain') as ipv6_add_chain_mock, \
|
||||
mock.patch.object(self.driver.iptables.ipv4['filter'],
|
||||
'add_chain') as ipv4_add_chain_mock:
|
||||
|
||||
self.driver.add_filters_for_instance(
|
||||
mock.sentinel.instance,
|
||||
mock.sentinel.network_info,
|
||||
mock.sentinel.inst_ipv4_rules,
|
||||
mock.sentinel.inst_ipv6_rules)
|
||||
ipv4_add_chain_mock.assert_called_with(mock.sentinel.mychain)
|
||||
ipv6_add_chain_mock.assert_called_with(mock.sentinel.mychain)
|
||||
icn_mock.assert_called_with(mock.sentinel.instance)
|
||||
ffi_mock.assert_called_with(mock.sentinel.mychain,
|
||||
mock.sentinel.network_info)
|
||||
self.assertEqual([mock.call('local',
|
||||
mock.sentinel.ipv4_rules,
|
||||
mock.sentinel.ipv6_rules),
|
||||
mock.call(mock.sentinel.mychain,
|
||||
mock.sentinel.inst_ipv4_rules,
|
||||
mock.sentinel.inst_ipv6_rules)],
|
||||
add_filters_mock.mock_calls)
|
||||
|
||||
def test_remove_filters_for_instance(self):
|
||||
self.flags(use_ipv6=True)
|
||||
self.driver._instance_chain_name = \
|
||||
mock.Mock(return_value='mychainname')
|
||||
self.driver.iptables.ipv4['filter'].remove_chain = mock.Mock()
|
||||
self.driver.iptables.ipv6['filter'].remove_chain = mock.Mock()
|
||||
|
||||
self.driver.remove_filters_for_instance('myinstance')
|
||||
|
||||
self.driver._instance_chain_name.assert_called_with('myinstance')
|
||||
self.driver.iptables.ipv4['filter'].remove_chain.assert_called_with(
|
||||
'mychainname')
|
||||
self.driver.iptables.ipv6['filter'].remove_chain.assert_called_with(
|
||||
'mychainname')
|
||||
|
||||
def test_instance_chain_name(self):
|
||||
instance = mock.Mock()
|
||||
instance.id = "myinstanceid"
|
||||
instance_chain_name = self.driver._instance_chain_name(instance)
|
||||
self.assertEqual(instance_chain_name, 'inst-myinstanceid')
|
||||
|
||||
def test_do_basic_rules(self):
|
||||
ipv4_rules = ['rule1']
|
||||
ipv6_rules = ['rule2']
|
||||
self.driver._do_basic_rules(ipv4_rules, ipv6_rules,
|
||||
mock.sentinel.net_info)
|
||||
self.assertEqual(ipv4_rules,
|
||||
['rule1', '-m state --state INVALID -j DROP',
|
||||
'-m state --state ESTABLISHED,RELATED -j ACCEPT'])
|
||||
self.assertEqual(ipv6_rules,
|
||||
['rule2', '-m state --state INVALID -j DROP',
|
||||
'-m state --state ESTABLISHED,RELATED -j ACCEPT'])
|
||||
|
||||
def test_do_dhcp_rules(self):
|
||||
subnet1 = mock.Mock()
|
||||
subnet1.get_meta = mock.Mock(return_value='mydhcp')
|
||||
subnet2 = mock.Mock()
|
||||
subnet2.get_meta = mock.Mock(return_value=None)
|
||||
self.driver._get_subnets = mock.Mock(return_value=[subnet1, subnet2])
|
||||
ipv4_rules = ['rule1']
|
||||
self.driver._do_dhcp_rules(ipv4_rules, mock.sentinel.net_info)
|
||||
self.assertEqual(ipv4_rules,
|
||||
['rule1',
|
||||
'-s mydhcp -p udp --sport 67 --dport 68 -j ACCEPT'])
|
||||
|
||||
def test_do_project_network_rules(self):
|
||||
self.flags(use_ipv6=True)
|
||||
subnet1 = {'cidr': 'mycidr1'}
|
||||
subnet2 = {'cidr': 'mycidr2'}
|
||||
ipv4_rules = ['rule1']
|
||||
ipv6_rules = ['rule2']
|
||||
self.driver._get_subnets = mock.Mock(return_value=[subnet1, subnet2])
|
||||
self.driver._do_project_network_rules(ipv4_rules, ipv6_rules,
|
||||
mock.sentinel.net_info)
|
||||
self.assertEqual(ipv4_rules,
|
||||
['rule1',
|
||||
'-s mycidr1 -j ACCEPT', '-s mycidr2 -j ACCEPT'])
|
||||
self.assertEqual(ipv6_rules,
|
||||
['rule2',
|
||||
'-s mycidr1 -j ACCEPT', '-s mycidr2 -j ACCEPT'])
|
||||
|
||||
def test_do_ra_rules(self):
|
||||
subnet1 = {'gateway': {'address': 'myaddress1'}}
|
||||
subnet2 = {'gateway': {'address': 'myaddress2'}}
|
||||
self.driver._get_subnets = \
|
||||
mock.Mock(return_value=[subnet1, subnet2])
|
||||
ipv6_rules = ['rule1']
|
||||
self.driver._do_ra_rules(ipv6_rules, mock.sentinel.net_info)
|
||||
self.assertEqual(ipv6_rules, ['rule1',
|
||||
'-s myaddress1/128 -p icmpv6 -j ACCEPT',
|
||||
'-s myaddress2/128 -p icmpv6 -j ACCEPT'])
|
||||
|
||||
def test_build_icmp_rule(self):
|
||||
rule = mock.Mock()
|
||||
# invalid icmp type
|
||||
rule.from_port = -1
|
||||
icmp_rule = self.driver._build_icmp_rule(rule, 4)
|
||||
self.assertEqual(icmp_rule, [])
|
||||
# version 4 invalid icmp code
|
||||
rule.from_port = 123
|
||||
rule.to_port = -1
|
||||
icmp_rule = self.driver._build_icmp_rule(rule, 4)
|
||||
self.assertEqual(icmp_rule,
|
||||
['-m', 'icmp', '--icmp-type', '123'])
|
||||
# version 6 valid icmp code
|
||||
rule.from_port = 123
|
||||
rule.to_port = 456
|
||||
icmp_rule = self.driver._build_icmp_rule(rule, 6)
|
||||
self.assertEqual(icmp_rule,
|
||||
['-m', 'icmp6', '--icmpv6-type', '123/456'])
|
||||
|
||||
def test_build_tcp_udp_rule(self):
|
||||
rule = mock.Mock()
|
||||
# equal from and to port
|
||||
rule.from_port = 123
|
||||
rule.to_port = 123
|
||||
tu_rule = self.driver._build_tcp_udp_rule(rule, 42)
|
||||
self.assertEqual(tu_rule, ['--dport', '123'])
|
||||
# different from and to port
|
||||
rule.to_port = 456
|
||||
tu_rule = self.driver._build_tcp_udp_rule(rule, 42)
|
||||
self.assertEqual(tu_rule, ['-m', 'multiport', '--dports', '123:456'])
|
||||
|
||||
def setup_instance_rules(self, ins_obj_cls_mock):
|
||||
"""Create necessary mock varibles for instance_rules.
|
||||
|
||||
The i_mock and ni_mock represent instance_rules parameters
|
||||
instance and network_info.
|
||||
|
||||
The i_obj_mock represent the return vaue for nova.objects.Instance.
|
||||
"""
|
||||
i_mock = mock.MagicMock(spec=dict)
|
||||
ni_mock = mock.MagicMock(spec=dict)
|
||||
i_obj_mock = mock.MagicMock()
|
||||
ins_obj_cls_mock._from_db_object.return_value = i_obj_mock
|
||||
driver = firewall.IptablesFirewallDriver()
|
||||
return i_mock, ni_mock, i_obj_mock, driver
|
||||
|
||||
@mock.patch('nova.objects.SecurityGroupRuleList')
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_DHCP_RULES)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_BASIC_RULES)
|
||||
@mock.patch('nova.objects.Instance')
|
||||
@mock.patch('nova.context.get_admin_context',
|
||||
return_value=mock.sentinel.ctx)
|
||||
@mock.patch('nova.network.linux_net.iptables_manager')
|
||||
def test_instance_rules_no_secgroups(self, _iptm_mock, ctx_mock,
|
||||
ins_obj_cls_mock, _do_basic_mock, _do_dhcp_mock,
|
||||
sec_grp_list_mock):
|
||||
|
||||
i_mock, ni_mock, i_obj_mock, driver = self.setup_instance_rules(
|
||||
ins_obj_cls_mock)
|
||||
|
||||
# Simple unit test that verifies that the fallback jump
|
||||
# is the only rule added to the returned list of rules if
|
||||
# no secgroups are found (we ignore the basic and DHCP
|
||||
# rule additions here)
|
||||
sec_grp_list_mock.get_by_instance.return_value = []
|
||||
|
||||
v4_rules, v6_rules = driver.instance_rules(i_mock, ni_mock)
|
||||
|
||||
ins_obj_cls_mock._from_db_object.assert_called_once_with(
|
||||
mock.sentinel.ctx, mock.ANY, i_mock, mock.ANY)
|
||||
sec_grp_list_mock.get_by_instance.assert_called_once_with(
|
||||
mock.sentinel.ctx, i_obj_mock)
|
||||
|
||||
expected = ['-j $sg-fallback']
|
||||
self.assertEqual(expected, v4_rules)
|
||||
self.assertEqual(expected, v6_rules)
|
||||
|
||||
@mock.patch('nova.objects.SecurityGroupRuleList')
|
||||
@mock.patch('nova.objects.SecurityGroupList')
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_DHCP_RULES)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_BASIC_RULES)
|
||||
@mock.patch('nova.objects.Instance')
|
||||
@mock.patch('nova.context.get_admin_context',
|
||||
return_value=mock.sentinel.ctx)
|
||||
@mock.patch('nova.network.linux_net.iptables_manager')
|
||||
def test_instance_rules_cidr(self, _iptm_mock, ctx_mock,
|
||||
ins_obj_cls_mock, _do_basic_mock, _do_dhcp_mock,
|
||||
sec_grp_list_mock, sec_grp_rule_list_mock):
|
||||
|
||||
i_mock, ni_mock, i_obj_mock, driver = self.setup_instance_rules(
|
||||
ins_obj_cls_mock)
|
||||
|
||||
# Tests that sec group rules that contain a CIDR (i.e. the
|
||||
# rule does not contain a grantee group of instances) populates
|
||||
# the returned iptables rules with appropriate ingress and
|
||||
# egress filters.
|
||||
sec_grp_list_mock.get_by_instance.return_value = [
|
||||
mock.sentinel.sec_grp
|
||||
]
|
||||
sec_grp_rule_list_mock.get_by_security_group.return_value = [
|
||||
{
|
||||
"cidr": "192.168.1.0/24",
|
||||
"protocol": "tcp",
|
||||
"to_port": "22",
|
||||
"from_port": "22"
|
||||
}
|
||||
]
|
||||
|
||||
v4_rules, v6_rules = driver.instance_rules(i_mock, ni_mock)
|
||||
|
||||
expected = [
|
||||
# '-j ACCEPT -p tcp --dport 22 -s 192.168.1.0/24',
|
||||
'-j $sg-fallback'
|
||||
]
|
||||
self.assertEqual(expected, v4_rules)
|
||||
expected = ['-j $sg-fallback']
|
||||
self.assertEqual(expected, v6_rules)
|
||||
|
||||
def setup_grantee_group(
|
||||
self, ins_obj_cls_mock, sec_grp_list_mock, sec_grp_rule_list_mock,
|
||||
ins_list_mock):
|
||||
|
||||
i_mock, ni_mock, i_obj_mock, driver = self.setup_instance_rules(
|
||||
ins_obj_cls_mock)
|
||||
|
||||
# Tests that sec group rules that DO NOT contain a CIDR (i.e. the
|
||||
# rule contains a grantee group of instances) populates
|
||||
# the returned iptables rules with appropriate ingress and
|
||||
# egress filters after calling out to the network API for information
|
||||
# about the instances in the grantee group.
|
||||
sec_grp_list_mock.get_by_instance.return_value = [
|
||||
mock.sentinel.sec_grp
|
||||
]
|
||||
sec_grp_rule_list_mock.get_by_security_group.return_value = [
|
||||
{
|
||||
"cidr": None,
|
||||
"grantee_group": mock.sentinel.gg,
|
||||
"protocol": "tcp",
|
||||
"to_port": "22",
|
||||
"from_port": "22"
|
||||
}
|
||||
]
|
||||
i_obj_list_mock = mock.MagicMock()
|
||||
i_obj_list_mock.info_cache.return_value = {
|
||||
"deleted": False
|
||||
}
|
||||
ins_list_mock.get_by_security_group.return_value = [i_obj_list_mock]
|
||||
return i_mock, i_obj_mock, ni_mock, driver
|
||||
|
||||
@mock.patch('nova.objects.Instance.get_network_info')
|
||||
@mock.patch('nova.objects.InstanceList')
|
||||
@mock.patch('nova.objects.SecurityGroupRuleList')
|
||||
@mock.patch('nova.objects.SecurityGroupList')
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_DHCP_RULES)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_BASIC_RULES)
|
||||
@mock.patch('nova.objects.Instance')
|
||||
@mock.patch('nova.context.get_admin_context',
|
||||
return_value=mock.sentinel.ctx)
|
||||
@mock.patch('nova.network.linux_net.iptables_manager')
|
||||
def test_instance_rules_grantee_group(self, _iptm_mock, ctx_mock,
|
||||
ins_obj_cls_mock, _do_basic_mock, _do_dhcp_mock,
|
||||
sec_grp_list_mock, sec_grp_rule_list_mock, ins_list_mock,
|
||||
get_nw_info_mock):
|
||||
|
||||
i_mock, i_obj_mock, ni_mock, driver = self.setup_grantee_group(
|
||||
ins_obj_cls_mock, sec_grp_list_mock, sec_grp_rule_list_mock,
|
||||
ins_list_mock)
|
||||
|
||||
nw_info_mock = mock.MagicMock()
|
||||
nw_info_mock.fixed_ips.return_value = [
|
||||
{
|
||||
"address": "10.0.1.4",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
get_nw_info_mock.return_value = nw_info_mock
|
||||
|
||||
v4_rules, v6_rules = driver.instance_rules(i_mock, ni_mock)
|
||||
|
||||
expected = ['-j $sg-fallback']
|
||||
self.assertEqual(expected, v4_rules)
|
||||
self.assertEqual(expected, v6_rules)
|
||||
|
||||
@mock.patch('nova.objects.Instance.get_network_info')
|
||||
@mock.patch('nova.objects.InstanceList')
|
||||
@mock.patch('nova.objects.SecurityGroupRuleList')
|
||||
@mock.patch('nova.objects.SecurityGroupList')
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_DHCP_RULES)
|
||||
@mock.patch.object(_IPT_DRIVER_CLS, _FN_DO_BASIC_RULES)
|
||||
@mock.patch('nova.objects.Instance')
|
||||
@mock.patch('nova.context.get_admin_context',
|
||||
return_value=mock.sentinel.ctx)
|
||||
@mock.patch('nova.network.linux_net.iptables_manager')
|
||||
def test_instance_rules_grantee_group_instance_deleted(
|
||||
self, _iptm_mock, ctx_mock, ins_obj_cls_mock, _do_basic_mock,
|
||||
_do_dhcp_mock, sec_grp_list_mock, sec_grp_rule_list_mock,
|
||||
ins_list_mock, get_nw_info_mock):
|
||||
|
||||
i_mock, i_obj_mock, ni_mock, driver = self.setup_grantee_group(
|
||||
ins_obj_cls_mock, sec_grp_list_mock, sec_grp_rule_list_mock,
|
||||
ins_list_mock)
|
||||
|
||||
# Emulate one of the instances in the grantee group being deleted
|
||||
# in between when the spawn of this instance and when we set up
|
||||
# network for that instance, and ensure that we do not crash and
|
||||
# burn but just skip the deleted instance from the iptables filters
|
||||
get_nw_info_mock.side_effect = exception.InstanceNotFound(
|
||||
instance_id="_ignored")
|
||||
|
||||
v4_rules, v6_rules = driver.instance_rules(i_mock, ni_mock)
|
||||
|
||||
expected = ['-j $sg-fallback']
|
||||
self.assertEqual(expected, v4_rules)
|
||||
self.assertEqual(expected, v6_rules)
|
||||
|
||||
def test_refresh_security_group_rules(self):
|
||||
self.driver.do_refresh_security_group_rules = mock.Mock()
|
||||
self.driver.iptables.apply = mock.Mock()
|
||||
self.driver.refresh_security_group_rules('mysecgroup')
|
||||
self.driver.do_refresh_security_group_rules \
|
||||
.assert_called_with('mysecgroup')
|
||||
self.driver.iptables.apply.assert_called()
|
||||
|
||||
def test_refresh_instance_security_rules(self):
|
||||
self.driver.do_refresh_instance_rules = mock.Mock()
|
||||
self.driver.iptables.apply = mock.Mock()
|
||||
self.driver.refresh_instance_security_rules('myinstance')
|
||||
self.driver.do_refresh_instance_rules.assert_called_with('myinstance')
|
||||
self.driver.iptables.apply.assert_called()
|
||||
|
||||
def test_do_refresh_security_group_rules(self):
|
||||
self.driver.instance_info = \
|
||||
{'1': ['myinstance1', 'netinfo1'],
|
||||
'2': ['myinstance2', 'netinfo2']}
|
||||
self.driver.instance_rules = \
|
||||
mock.Mock(return_value=['myipv4rules', 'myipv6rules'])
|
||||
self.driver._inner_do_refresh_rules = mock.Mock()
|
||||
self.driver.do_refresh_security_group_rules('mysecgroup')
|
||||
self.driver.instance_rules.assert_any_call('myinstance1', 'netinfo1')
|
||||
self.driver.instance_rules.assert_any_call('myinstance2', 'netinfo2')
|
||||
self.driver._inner_do_refresh_rules.assert_any_call(
|
||||
'myinstance1', 'netinfo1',
|
||||
'myipv4rules', 'myipv6rules')
|
||||
self.driver._inner_do_refresh_rules.assert_any_call(
|
||||
'myinstance2', 'netinfo2',
|
||||
'myipv4rules', 'myipv6rules')
|
||||
|
||||
def test_do_refresh_instance_rules(self):
|
||||
instance = mock.Mock()
|
||||
instance.id = 'myid'
|
||||
self.driver.instance_info = {instance.id: ['myinstance', 'mynetinfo']}
|
||||
self.driver.instance_rules = \
|
||||
mock.Mock(return_value=['myipv4rules', 'myipv6rules'])
|
||||
self.driver._inner_do_refresh_rules = mock.Mock()
|
||||
self.driver.do_refresh_instance_rules(instance)
|
||||
self.driver.instance_rules.assert_called_with(instance, 'mynetinfo')
|
||||
self.driver._inner_do_refresh_rules.assert_called_with(
|
||||
instance, 'mynetinfo', 'myipv4rules', 'myipv6rules')
|
|
@ -1,429 +0,0 @@
|
|||
# Copyright 2011 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2011 Citrix Systems, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
|
||||
import nova.conf
|
||||
from nova import context
|
||||
from nova.network import linux_net
|
||||
from nova import objects
|
||||
from nova import utils
|
||||
from nova.virt import netutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
|
||||
def load_driver(default, *args, **kwargs):
|
||||
fw_class = importutils.import_class(CONF.firewall_driver or default)
|
||||
return fw_class(*args, **kwargs)
|
||||
|
||||
|
||||
class FirewallDriver(object):
|
||||
"""Firewall Driver base class.
|
||||
|
||||
Defines methods that any driver providing security groups should implement.
|
||||
|
||||
"""
|
||||
def prepare_instance_filter(self, instance, network_info):
|
||||
"""Prepare filters for the instance.
|
||||
|
||||
At this point, the instance isn't running yet.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def filter_defer_apply_on(self):
|
||||
"""Defer application of IPTables rules."""
|
||||
pass
|
||||
|
||||
def filter_defer_apply_off(self):
|
||||
"""Turn off deferral of IPTables rules and apply the rules now."""
|
||||
pass
|
||||
|
||||
def unfilter_instance(self, instance, network_info):
|
||||
"""Stop filtering instance."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def apply_instance_filter(self, instance, network_info):
|
||||
"""Apply instance filter.
|
||||
|
||||
Once this method returns, the instance should be firewalled
|
||||
appropriately. This method should as far as possible be a
|
||||
no-op. It's vastly preferred to get everything set up in
|
||||
prepare_instance_filter.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def refresh_security_group_rules(self, security_group_id):
|
||||
"""Refresh security group rules from data store
|
||||
|
||||
Gets called when a rule has been added to or removed from
|
||||
the security group.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def refresh_instance_security_rules(self, instance):
|
||||
"""Refresh security group rules from data store
|
||||
|
||||
Gets called when an instance gets added to or removed from
|
||||
the security group the instance is a member of or if the
|
||||
group gains or loses a rule.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def setup_basic_filtering(self, instance, network_info):
|
||||
"""Create rules to block spoofing and allow dhcp.
|
||||
|
||||
This gets called when spawning an instance, before
|
||||
:py:meth:`prepare_instance_filter`.
|
||||
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def instance_filter_exists(self, instance, network_info):
|
||||
"""Check nova-instance-instance-xxx exists."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class IptablesFirewallDriver(FirewallDriver):
|
||||
"""Driver which enforces security groups through iptables rules."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.iptables = linux_net.iptables_manager
|
||||
self.instance_info = {}
|
||||
|
||||
# Flags for DHCP request rule
|
||||
self.dhcp_create = False
|
||||
self.dhcp_created = False
|
||||
|
||||
self.iptables.ipv4['filter'].add_chain('sg-fallback')
|
||||
self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
|
||||
self.iptables.ipv6['filter'].add_chain('sg-fallback')
|
||||
self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')
|
||||
|
||||
def setup_basic_filtering(self, instance, network_info):
|
||||
pass
|
||||
|
||||
def apply_instance_filter(self, instance, network_info):
|
||||
"""No-op. Everything is done in prepare_instance_filter."""
|
||||
pass
|
||||
|
||||
def filter_defer_apply_on(self):
|
||||
self.iptables.defer_apply_on()
|
||||
|
||||
def filter_defer_apply_off(self):
|
||||
self.iptables.defer_apply_off()
|
||||
|
||||
def unfilter_instance(self, instance, network_info):
|
||||
if self.instance_info.pop(instance.id, None):
|
||||
self.remove_filters_for_instance(instance)
|
||||
self.iptables.apply()
|
||||
else:
|
||||
LOG.info('Attempted to unfilter instance which is not filtered',
|
||||
instance=instance)
|
||||
|
||||
def prepare_instance_filter(self, instance, network_info):
|
||||
self.instance_info[instance.id] = (instance, network_info)
|
||||
ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
|
||||
self.add_filters_for_instance(instance, network_info, ipv4_rules,
|
||||
ipv6_rules)
|
||||
LOG.debug('Filters added to instance: %s', instance.id,
|
||||
instance=instance)
|
||||
# Ensure that DHCP request rule is updated if necessary
|
||||
if (self.dhcp_create and not self.dhcp_created):
|
||||
self.iptables.ipv4['filter'].add_rule(
|
||||
'INPUT',
|
||||
'-s 0.0.0.0/32 -d 255.255.255.255/32 '
|
||||
'-p udp -m udp --sport 68 --dport 67 -j ACCEPT')
|
||||
self.iptables.ipv4['filter'].add_rule(
|
||||
'FORWARD',
|
||||
'-s 0.0.0.0/32 -d 255.255.255.255/32 '
|
||||
'-p udp -m udp --sport 68 --dport 67 -j ACCEPT')
|
||||
self.dhcp_created = True
|
||||
self.iptables.apply()
|
||||
|
||||
def _create_filter(self, ips, chain_name):
|
||||
return ['-d %s -j $%s' % (ip, chain_name) for ip in ips]
|
||||
|
||||
def _get_subnets(self, network_info, version):
|
||||
subnets = []
|
||||
for vif in network_info:
|
||||
if 'network' in vif and 'subnets' in vif['network']:
|
||||
for subnet in vif['network']['subnets']:
|
||||
if subnet['version'] == version:
|
||||
subnets.append(subnet)
|
||||
return subnets
|
||||
|
||||
def _filters_for_instance(self, chain_name, network_info):
|
||||
"""Creates a rule corresponding to each ip that defines a
|
||||
jump to the corresponding instance - chain for all the traffic
|
||||
destined to that ip.
|
||||
"""
|
||||
v4_subnets = self._get_subnets(network_info, 4)
|
||||
v6_subnets = self._get_subnets(network_info, 6)
|
||||
ips_v4 = [ip['address'] for subnet in v4_subnets
|
||||
for ip in subnet['ips']]
|
||||
ipv4_rules = self._create_filter(ips_v4, chain_name)
|
||||
|
||||
ipv6_rules = ips_v6 = []
|
||||
if CONF.use_ipv6:
|
||||
if v6_subnets:
|
||||
ips_v6 = [ip['address'] for subnet in v6_subnets
|
||||
for ip in subnet['ips']]
|
||||
ipv6_rules = self._create_filter(ips_v6, chain_name)
|
||||
|
||||
return ipv4_rules, ipv6_rules
|
||||
|
||||
def _add_filters(self, chain_name, ipv4_rules, ipv6_rules):
|
||||
for rule in ipv4_rules:
|
||||
self.iptables.ipv4['filter'].add_rule(chain_name, rule)
|
||||
|
||||
if CONF.use_ipv6:
|
||||
for rule in ipv6_rules:
|
||||
self.iptables.ipv6['filter'].add_rule(chain_name, rule)
|
||||
|
||||
def add_filters_for_instance(self, instance, network_info, inst_ipv4_rules,
|
||||
inst_ipv6_rules):
|
||||
chain_name = self._instance_chain_name(instance)
|
||||
if CONF.use_ipv6:
|
||||
self.iptables.ipv6['filter'].add_chain(chain_name)
|
||||
self.iptables.ipv4['filter'].add_chain(chain_name)
|
||||
ipv4_rules, ipv6_rules = self._filters_for_instance(chain_name,
|
||||
network_info)
|
||||
self._add_filters('local', ipv4_rules, ipv6_rules)
|
||||
self._add_filters(chain_name, inst_ipv4_rules, inst_ipv6_rules)
|
||||
|
||||
def remove_filters_for_instance(self, instance):
|
||||
chain_name = self._instance_chain_name(instance)
|
||||
|
||||
self.iptables.ipv4['filter'].remove_chain(chain_name)
|
||||
if CONF.use_ipv6:
|
||||
self.iptables.ipv6['filter'].remove_chain(chain_name)
|
||||
|
||||
def _instance_chain_name(self, instance):
|
||||
return 'inst-%s' % (instance.id,)
|
||||
|
||||
def _do_basic_rules(self, ipv4_rules, ipv6_rules, network_info):
|
||||
# Always drop invalid packets
|
||||
ipv4_rules += ['-m state --state ' 'INVALID -j DROP']
|
||||
ipv6_rules += ['-m state --state ' 'INVALID -j DROP']
|
||||
|
||||
# Allow established connections
|
||||
ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
|
||||
ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
|
||||
|
||||
def _do_dhcp_rules(self, ipv4_rules, network_info):
|
||||
v4_subnets = self._get_subnets(network_info, 4)
|
||||
dhcp_servers = [subnet.get_meta('dhcp_server')
|
||||
for subnet in v4_subnets if subnet.get_meta('dhcp_server')]
|
||||
|
||||
for dhcp_server in dhcp_servers:
|
||||
if dhcp_server:
|
||||
ipv4_rules.append('-s %s -p udp --sport 67 --dport 68 '
|
||||
'-j ACCEPT' % (dhcp_server,))
|
||||
self.dhcp_create = True
|
||||
|
||||
def _do_project_network_rules(self, ipv4_rules, ipv6_rules, network_info):
|
||||
v4_subnets = self._get_subnets(network_info, 4)
|
||||
v6_subnets = self._get_subnets(network_info, 6)
|
||||
cidrs = [subnet['cidr'] for subnet in v4_subnets]
|
||||
for cidr in cidrs:
|
||||
ipv4_rules.append('-s %s -j ACCEPT' % (cidr,))
|
||||
if CONF.use_ipv6:
|
||||
cidrv6s = [subnet['cidr'] for subnet in v6_subnets]
|
||||
for cidrv6 in cidrv6s:
|
||||
ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,))
|
||||
|
||||
def _do_ra_rules(self, ipv6_rules, network_info):
|
||||
v6_subnets = self._get_subnets(network_info, 6)
|
||||
gateways_v6 = [subnet['gateway']['address'] for subnet in v6_subnets]
|
||||
|
||||
for gateway_v6 in gateways_v6:
|
||||
ipv6_rules.append(
|
||||
'-s %s/128 -p icmpv6 -j ACCEPT' % (gateway_v6,))
|
||||
|
||||
def _build_icmp_rule(self, rule, version):
|
||||
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:
|
||||
if version == 4:
|
||||
return ['-m', 'icmp', '--icmp-type', icmp_type_arg]
|
||||
elif version == 6:
|
||||
return ['-m', 'icmp6', '--icmpv6-type', icmp_type_arg]
|
||||
# return empty list if icmp_type == -1
|
||||
return []
|
||||
|
||||
def _build_tcp_udp_rule(self, rule, version):
|
||||
if rule.from_port == rule.to_port:
|
||||
return ['--dport', '%s' % (rule.from_port,)]
|
||||
else:
|
||||
return ['-m', 'multiport',
|
||||
'--dports', '%s:%s' % (rule.from_port,
|
||||
rule.to_port)]
|
||||
|
||||
def instance_rules(self, instance, network_info):
|
||||
ctxt = context.get_admin_context()
|
||||
if isinstance(instance, dict):
|
||||
# NOTE(danms): allow old-world instance objects from
|
||||
# unconverted callers; all we need is instance.uuid below
|
||||
instance = objects.Instance._from_db_object(
|
||||
ctxt, objects.Instance(), instance, [])
|
||||
|
||||
ipv4_rules = []
|
||||
ipv6_rules = []
|
||||
|
||||
# Initialize with basic rules
|
||||
self._do_basic_rules(ipv4_rules, ipv6_rules, network_info)
|
||||
# Set up rules to allow traffic to/from DHCP server
|
||||
self._do_dhcp_rules(ipv4_rules, network_info)
|
||||
|
||||
# Allow project network traffic
|
||||
if CONF.allow_same_net_traffic:
|
||||
self._do_project_network_rules(ipv4_rules, ipv6_rules,
|
||||
network_info)
|
||||
# We wrap these in CONF.use_ipv6 because they might cause
|
||||
# a DB lookup. The other ones are just list operations, so
|
||||
# they're not worth the clutter.
|
||||
if CONF.use_ipv6:
|
||||
# Allow RA responses
|
||||
self._do_ra_rules(ipv6_rules, network_info)
|
||||
|
||||
# then, security group chains and rules
|
||||
rules = objects.SecurityGroupRuleList.get_by_instance(ctxt, instance)
|
||||
|
||||
for rule in rules:
|
||||
if not rule.cidr:
|
||||
version = 4
|
||||
else:
|
||||
version = netutils.get_ip_version(rule.cidr)
|
||||
|
||||
if version == 4:
|
||||
fw_rules = ipv4_rules
|
||||
else:
|
||||
fw_rules = ipv6_rules
|
||||
|
||||
protocol = rule.protocol
|
||||
|
||||
if protocol:
|
||||
protocol = rule.protocol.lower()
|
||||
|
||||
if version == 6 and protocol == 'icmp':
|
||||
protocol = 'icmpv6'
|
||||
|
||||
args = ['-j ACCEPT']
|
||||
if protocol:
|
||||
args += ['-p', protocol]
|
||||
|
||||
if protocol in ['udp', 'tcp']:
|
||||
args += self._build_tcp_udp_rule(rule, version)
|
||||
elif protocol == 'icmp':
|
||||
args += self._build_icmp_rule(rule, version)
|
||||
if rule.cidr:
|
||||
args += ['-s', str(rule.cidr)]
|
||||
fw_rules += [' '.join(args)]
|
||||
else:
|
||||
if rule.grantee_group:
|
||||
insts = objects.InstanceList.get_by_security_group(
|
||||
ctxt, rule.grantee_group)
|
||||
for inst in insts:
|
||||
if inst.info_cache.deleted:
|
||||
LOG.debug('ignoring deleted cache')
|
||||
continue
|
||||
nw_info = inst.get_network_info()
|
||||
|
||||
ips = [ip['address'] for ip in nw_info.fixed_ips()
|
||||
if ip['version'] == version]
|
||||
|
||||
LOG.debug('ips: %r', ips, instance=inst)
|
||||
for ip in ips:
|
||||
subrule = args + ['-s %s' % ip]
|
||||
fw_rules += [' '.join(subrule)]
|
||||
|
||||
ipv4_rules += ['-j $sg-fallback']
|
||||
ipv6_rules += ['-j $sg-fallback']
|
||||
LOG.debug('Security Group Rules %s translated to ipv4: %r, ipv6: %r',
|
||||
list(rules), ipv4_rules, ipv6_rules,
|
||||
instance=instance)
|
||||
return ipv4_rules, ipv6_rules
|
||||
|
||||
def instance_filter_exists(self, instance, network_info):
|
||||
pass
|
||||
|
||||
def refresh_security_group_rules(self, security_group):
|
||||
self.do_refresh_security_group_rules(security_group)
|
||||
self.iptables.apply()
|
||||
|
||||
def refresh_instance_security_rules(self, instance):
|
||||
self.do_refresh_instance_rules(instance)
|
||||
self.iptables.apply()
|
||||
|
||||
@utils.synchronized('iptables', external=True)
|
||||
def _inner_do_refresh_rules(self, instance, network_info, ipv4_rules,
|
||||
ipv6_rules):
|
||||
chain_name = self._instance_chain_name(instance)
|
||||
if not self.iptables.ipv4['filter'].has_chain(chain_name):
|
||||
LOG.info('instance chain %s disappeared during refresh, skipping',
|
||||
chain_name, instance=instance)
|
||||
return
|
||||
self.remove_filters_for_instance(instance)
|
||||
self.add_filters_for_instance(instance, network_info, ipv4_rules,
|
||||
ipv6_rules)
|
||||
|
||||
def do_refresh_security_group_rules(self, security_group):
|
||||
id_list = self.instance_info.keys()
|
||||
for instance_id in id_list:
|
||||
try:
|
||||
instance, network_info = self.instance_info[instance_id]
|
||||
except KeyError:
|
||||
# NOTE(danms): instance cache must have been modified,
|
||||
# ignore this deleted instance and move on
|
||||
continue
|
||||
ipv4_rules, ipv6_rules = self.instance_rules(instance,
|
||||
network_info)
|
||||
self._inner_do_refresh_rules(instance, network_info, ipv4_rules,
|
||||
ipv6_rules)
|
||||
|
||||
def do_refresh_instance_rules(self, instance):
|
||||
_instance, network_info = self.instance_info[instance.id]
|
||||
ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
|
||||
self._inner_do_refresh_rules(instance, network_info, ipv4_rules,
|
||||
ipv6_rules)
|
||||
|
||||
|
||||
class NoopFirewallDriver(object):
|
||||
"""Firewall driver which just provides No-op methods."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def _noop(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __getattr__(self, key):
|
||||
return self._noop
|
||||
|
||||
def instance_filter_exists(self, instance, network_info):
|
||||
return True
|
|
@ -1,353 +0,0 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from eventlet import greenthread
|
||||
from lxml import etree
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
import nova.conf
|
||||
import nova.virt.firewall as base_firewall
|
||||
from nova.virt import netutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
libvirt = None
|
||||
|
||||
|
||||
class NWFilterFirewall(base_firewall.FirewallDriver):
|
||||
"""This class implements a network filtering mechanism by using
|
||||
libvirt's nwfilter.
|
||||
all instances get a filter ("nova-base") applied. This filter
|
||||
provides some basic security such as protection against MAC
|
||||
spoofing, IP spoofing, and ARP spoofing.
|
||||
"""
|
||||
|
||||
def __init__(self, host, **kwargs):
|
||||
"""Create an NWFilter firewall driver
|
||||
|
||||
:param host: nova.virt.libvirt.host.Host instance
|
||||
:param kwargs: currently unused
|
||||
"""
|
||||
|
||||
global libvirt
|
||||
if libvirt is None:
|
||||
try:
|
||||
libvirt = importutils.import_module('libvirt')
|
||||
except ImportError:
|
||||
LOG.warning("Libvirt module could not be loaded. "
|
||||
"NWFilterFirewall will not work correctly.")
|
||||
self._host = host
|
||||
self.static_filters_configured = False
|
||||
|
||||
def apply_instance_filter(self, instance, network_info):
|
||||
"""No-op. Everything is done in prepare_instance_filter."""
|
||||
pass
|
||||
|
||||
def _get_connection(self):
|
||||
return self._host.get_connection()
|
||||
_conn = property(_get_connection)
|
||||
|
||||
def nova_no_nd_reflection_filter(self):
|
||||
"""This filter protects false positives on IPv6 Duplicate Address
|
||||
Detection(DAD).
|
||||
"""
|
||||
uuid = self._get_filter_uuid('nova-no-nd-reflection')
|
||||
return '''<filter name='nova-no-nd-reflection' chain='ipv6'>
|
||||
<!-- no nd reflection -->
|
||||
<!-- drop if destination mac is v6 mcast mac addr and
|
||||
we sent it. -->
|
||||
<uuid>%s</uuid>
|
||||
<rule action='drop' direction='in'>
|
||||
<mac dstmacaddr='33:33:00:00:00:00'
|
||||
dstmacmask='ff:ff:00:00:00:00' srcmacaddr='$MAC'/>
|
||||
</rule>
|
||||
</filter>''' % uuid
|
||||
|
||||
def nova_dhcp_filter(self):
|
||||
"""The standard allow-dhcp-server filter is an <ip> one, so it uses
|
||||
ebtables to allow traffic through. Without a corresponding rule in
|
||||
iptables, it'll get blocked anyway.
|
||||
"""
|
||||
uuid = self._get_filter_uuid('nova-allow-dhcp-server')
|
||||
return '''<filter name='nova-allow-dhcp-server' chain='ipv4'>
|
||||
<uuid>%s</uuid>
|
||||
<rule action='accept' direction='out'
|
||||
priority='100'>
|
||||
<udp srcipaddr='0.0.0.0'
|
||||
dstipaddr='255.255.255.255'
|
||||
srcportstart='68'
|
||||
dstportstart='67'/>
|
||||
</rule>
|
||||
<rule action='accept' direction='in'
|
||||
priority='100'>
|
||||
<udp srcipaddr='$DHCPSERVER'
|
||||
srcportstart='67'
|
||||
dstportstart='68'/>
|
||||
</rule>
|
||||
</filter>''' % uuid
|
||||
|
||||
def setup_basic_filtering(self, instance, network_info):
|
||||
"""Set up basic filtering (MAC, IP, and ARP spoofing protection)."""
|
||||
LOG.info('Called setup_basic_filtering in nwfilter',
|
||||
instance=instance)
|
||||
|
||||
LOG.info('Ensuring static filters', instance=instance)
|
||||
self._ensure_static_filters()
|
||||
|
||||
nodhcp_base_filter = self.get_base_filter_list(instance, False)
|
||||
dhcp_base_filter = self.get_base_filter_list(instance, True)
|
||||
|
||||
for vif in network_info:
|
||||
_base_filter = nodhcp_base_filter
|
||||
for subnet in vif['network']['subnets']:
|
||||
if subnet.get_meta('dhcp_server'):
|
||||
_base_filter = dhcp_base_filter
|
||||
break
|
||||
self._define_filter(self._get_instance_filter_xml(instance,
|
||||
_base_filter,
|
||||
vif))
|
||||
|
||||
def _get_instance_filter_parameters(self, vif):
|
||||
parameters = []
|
||||
|
||||
def format_parameter(parameter, value):
|
||||
return ("<parameter name='%s' value='%s'/>" % (parameter, value))
|
||||
|
||||
network = vif['network']
|
||||
if not vif['network'] or not vif['network']['subnets']:
|
||||
return parameters
|
||||
|
||||
v4_subnets = [s for s in network['subnets'] if s['version'] == 4]
|
||||
v6_subnets = [s for s in network['subnets'] if s['version'] == 6]
|
||||
|
||||
for subnet in v4_subnets:
|
||||
for ip in subnet['ips']:
|
||||
parameters.append(format_parameter('IP', ip['address']))
|
||||
|
||||
dhcp_server = subnet.get_meta('dhcp_server')
|
||||
if dhcp_server:
|
||||
parameters.append(format_parameter('DHCPSERVER', dhcp_server))
|
||||
|
||||
ipv4_cidr = subnet['cidr']
|
||||
net, mask = netutils.get_net_and_mask(ipv4_cidr)
|
||||
parameters.append(format_parameter('PROJNET', net))
|
||||
parameters.append(format_parameter('PROJMASK', mask))
|
||||
|
||||
for subnet in v6_subnets:
|
||||
gateway = subnet.get('gateway')
|
||||
if gateway:
|
||||
ra_server = gateway['address'] + "/128"
|
||||
parameters.append(format_parameter('RASERVER', ra_server))
|
||||
|
||||
ipv6_cidr = subnet['cidr']
|
||||
net, prefix = netutils.get_net_and_prefixlen(ipv6_cidr)
|
||||
parameters.append(format_parameter('PROJNET6', net))
|
||||
parameters.append(format_parameter('PROJMASK6', prefix))
|
||||
|
||||
return parameters
|
||||
|
||||
def _get_instance_filter_xml(self, instance, filters, vif):
|
||||
nic_id = vif['address'].replace(':', '')
|
||||
instance_filter_name = self._instance_filter_name(instance, nic_id)
|
||||
parameters = self._get_instance_filter_parameters(vif)
|
||||
uuid = self._get_filter_uuid(instance_filter_name)
|
||||
xml = '''<filter name='%s' chain='root'>''' % instance_filter_name
|
||||
xml += '<uuid>%s</uuid>' % uuid
|
||||
for f in filters:
|
||||
xml += '''<filterref filter='%s'>''' % f
|
||||
xml += ''.join(parameters)
|
||||
xml += '</filterref>'
|
||||
xml += '</filter>'
|
||||
return xml
|
||||
|
||||
def get_base_filter_list(self, instance, allow_dhcp):
|
||||
"""Obtain a list of base filters to apply to an instance.
|
||||
The return value should be a list of strings, each
|
||||
specifying a filter name. Subclasses can override this
|
||||
function to add additional filters as needed. Additional
|
||||
filters added to the list must also be correctly defined
|
||||
within the subclass.
|
||||
"""
|
||||
if allow_dhcp:
|
||||
base_filter = 'nova-base'
|
||||
else:
|
||||
base_filter = 'nova-nodhcp'
|
||||
return [base_filter]
|
||||
|
||||
def _ensure_static_filters(self):
|
||||
"""Static filters are filters that have no need to be IP aware.
|
||||
|
||||
There is no configuration or tuneability of these filters, so they
|
||||
can be set up once and forgotten about.
|
||||
|
||||
"""
|
||||
|
||||
if self.static_filters_configured:
|
||||
return
|
||||
|
||||
filter_set = ['no-mac-spoofing',
|
||||
'no-ip-spoofing',
|
||||
'no-arp-spoofing']
|
||||
|
||||
self._define_filter(self.nova_no_nd_reflection_filter())
|
||||
filter_set.append('nova-no-nd-reflection')
|
||||
self._define_filter(self._filter_container('nova-nodhcp', filter_set))
|
||||
filter_set.append('allow-dhcp-server')
|
||||
self._define_filter(self._filter_container('nova-base', filter_set))
|
||||
self._define_filter(self.nova_dhcp_filter())
|
||||
|
||||
self.static_filters_configured = True
|
||||
|
||||
def _filter_container(self, name, filters):
|
||||
uuid = self._get_filter_uuid(name)
|
||||
xml = '''<filter name='%s' chain='root'>
|
||||
<uuid>%s</uuid>
|
||||
%s
|
||||
</filter>''' % (name, uuid,
|
||||
''.join(["<filterref filter='%s'/>" % (f,) for f in filters]))
|
||||
return xml
|
||||
|
||||
def _get_filter_uuid(self, name):
|
||||
try:
|
||||
flt = self._conn.nwfilterLookupByName(name)
|
||||
xml = flt.XMLDesc(0)
|
||||
doc = etree.fromstring(xml)
|
||||
u = doc.find("./uuid").text
|
||||
except Exception as e:
|
||||
LOG.debug(u"Cannot find UUID for filter '%(name)s': '%(e)s'",
|
||||
{'name': name, 'e': e})
|
||||
u = uuidutils.generate_uuid(dashed=False)
|
||||
|
||||
LOG.debug("UUID for filter '%s' is '%s'", name, u)
|
||||
return u
|
||||
|
||||
def _define_filter(self, xml):
|
||||
if callable(xml):
|
||||
xml = xml()
|
||||
try:
|
||||
self._conn.nwfilterDefineXML(xml)
|
||||
except libvirt.libvirtError as ex:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
errcode = ex.get_error_code()
|
||||
if errcode == libvirt.VIR_ERR_OPERATION_FAILED:
|
||||
# Since libvirt 1.2.7 this operation can fail if the filter
|
||||
# with the same name already exists for the given uuid.
|
||||
# Unfortunately there is not a specific error code for this
|
||||
# so we have to parse the error message to see if that was
|
||||
# the failure.
|
||||
errmsg = ex.get_error_message()
|
||||
if 'already exists with uuid' in errmsg:
|
||||
ctxt.reraise = False
|
||||
|
||||
def unfilter_instance(self, instance, network_info):
|
||||
"""Clear out the nwfilter rules."""
|
||||
for vif in network_info:
|
||||
nic_id = vif['address'].replace(':', '')
|
||||
instance_filter_name = self._instance_filter_name(instance, nic_id)
|
||||
|
||||
# nwfilters may be defined in a separate thread in the case
|
||||
# of libvirt non-blocking mode, so we wait for completion
|
||||
max_retry = CONF.live_migration_retry_count
|
||||
for cnt in range(max_retry):
|
||||
try:
|
||||
_nw = self._conn.nwfilterLookupByName(instance_filter_name)
|
||||
_nw.undefine()
|
||||
break
|
||||
except libvirt.libvirtError as e:
|
||||
if cnt == max_retry - 1:
|
||||
raise
|
||||
errcode = e.get_error_code()
|
||||
if errcode == libvirt.VIR_ERR_OPERATION_INVALID:
|
||||
# This happens when the instance filter is still in use
|
||||
# (ie. when the instance has not terminated properly)
|
||||
LOG.info('Failed to undefine network filter '
|
||||
'%(name)s. Try %(cnt)d of %(max_retry)d.',
|
||||
{'name': instance_filter_name,
|
||||
'cnt': cnt + 1,
|
||||
'max_retry': max_retry},
|
||||
instance=instance)
|
||||
greenthread.sleep(1)
|
||||
else:
|
||||
LOG.debug('The nwfilter(%s) is not found.',
|
||||
instance_filter_name, instance=instance)
|
||||
break
|
||||
|
||||
@staticmethod
|
||||
def _instance_filter_name(instance, nic_id=None):
|
||||
if not nic_id:
|
||||
return 'nova-instance-%s' % (instance.name)
|
||||
return 'nova-instance-%s-%s' % (instance.name, nic_id)
|
||||
|
||||
def instance_filter_exists(self, instance, network_info):
|
||||
"""Check nova-instance-instance-xxx exists."""
|
||||
for vif in network_info:
|
||||
nic_id = vif['address'].replace(':', '')
|
||||
instance_filter_name = self._instance_filter_name(instance, nic_id)
|
||||
try:
|
||||
self._conn.nwfilterLookupByName(instance_filter_name)
|
||||
except libvirt.libvirtError:
|
||||
name = instance.name
|
||||
LOG.debug('The nwfilter(%(instance_filter_name)s) for '
|
||||
'%(name)s is not found.',
|
||||
{'instance_filter_name': instance_filter_name,
|
||||
'name': name},
|
||||
instance=instance)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class IptablesFirewallDriver(base_firewall.IptablesFirewallDriver):
|
||||
def __init__(self, execute=None, **kwargs):
|
||||
"""Create an IP tables firewall driver instance
|
||||
|
||||
:param execute: unused, pass None
|
||||
:param kwargs: extra arguments
|
||||
|
||||
The @kwargs parameter must contain a key 'host' that
|
||||
maps to an instance of the nova.virt.libvirt.host.Host
|
||||
class.
|
||||
"""
|
||||
|
||||
super(IptablesFirewallDriver, self).__init__(**kwargs)
|
||||
self.nwfilter = NWFilterFirewall(kwargs['host'])
|
||||
|
||||
def setup_basic_filtering(self, instance, network_info):
|
||||
"""Set up basic NWFilter."""
|
||||
self.nwfilter.setup_basic_filtering(instance, network_info)
|
||||
|
||||
def apply_instance_filter(self, instance, network_info):
|
||||
"""No-op. Everything is done in prepare_instance_filter."""
|
||||
pass
|
||||
|
||||
def unfilter_instance(self, instance, network_info):
|
||||
# NOTE(salvatore-orlando):
|
||||
# Overriding base class method for applying nwfilter operation
|
||||
if self.instance_info.pop(instance.id, None):
|
||||
self.remove_filters_for_instance(instance)
|
||||
self.iptables.apply()
|
||||
self.nwfilter.unfilter_instance(instance, network_info)
|
||||
else:
|
||||
LOG.info('Attempted to unfilter instance which is not filtered',
|
||||
instance=instance)
|
||||
|
||||
def instance_filter_exists(self, instance, network_info):
|
||||
"""Check nova-instance-instance-xxx exists."""
|
||||
return self.nwfilter.instance_filter_exists(instance, network_info)
|
|
@ -1,60 +0,0 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from nova.virt import firewall
|
||||
|
||||
|
||||
class Dom0IptablesFirewallDriver(firewall.IptablesFirewallDriver):
|
||||
"""Dom0IptablesFirewallDriver class
|
||||
|
||||
This class provides an implementation for nova.virt.Firewall
|
||||
using iptables. This class is meant to be used with the xenapi
|
||||
backend and uses xenapi plugin to enforce iptables rules in dom0.
|
||||
"""
|
||||
def _plugin_execute(self, *cmd, **kwargs):
|
||||
# Prepare arguments for plugin call
|
||||
args = {}
|
||||
process_input = kwargs.get('process_input', None)
|
||||
if process_input is not None and isinstance(process_input, bytes):
|
||||
kwargs['process_input'] = process_input.decode('utf-8')
|
||||
args.update(map(lambda x: (x, str(kwargs[x])), kwargs))
|
||||
args['cmd_args'] = jsonutils.dumps(cmd)
|
||||
ret = self._session.call_plugin('xenhost.py', 'iptables_config', args)
|
||||
json_ret = jsonutils.loads(ret)
|
||||
return (json_ret['out'], json_ret['err'])
|
||||
|
||||
def __init__(self, xenapi_session=None, **kwargs):
|
||||
from nova.network import linux_net
|
||||
super(Dom0IptablesFirewallDriver, self).__init__(**kwargs)
|
||||
self._session = xenapi_session
|
||||
# Create IpTablesManager with executor through plugin
|
||||
self.iptables = linux_net.IptablesManager(
|
||||
redirect_privsep_calls_to=self._plugin_execute)
|
||||
self.iptables.ipv4['filter'].add_chain('sg-fallback')
|
||||
self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
|
||||
self.iptables.ipv6['filter'].add_chain('sg-fallback')
|
||||
self.iptables.ipv6['filter'].add_rule('sg-fallback', '-j DROP')
|
||||
|
||||
def _build_tcp_udp_rule(self, rule, version):
|
||||
if rule.from_port == rule.to_port:
|
||||
return ['--dport', '%s' % (rule.from_port,)]
|
||||
else:
|
||||
# No multiport needed for XS!
|
||||
return ['--dport', '%s:%s' % (rule.from_port,
|
||||
rule.to_port)]
|
|
@ -59,4 +59,6 @@ upgrade:
|
|||
network driver which has now been removed. The config options have
|
||||
therefore been removed also.
|
||||
|
||||
* ``vmware.vlan_interface``
|
||||
* ``[DEFAULT] firewall_driver``
|
||||
* ``[DEFAULT] allow_same_net_traffic``
|
||||
* ``[vmware] vlan_interface``
|
||||
|
|
Loading…
Reference in New Issue