From 6d1598b5b8135daa104a2dd6ca83886405c1c32a Mon Sep 17 00:00:00 2001 From: Thomas Goirand Date: Wed, 16 Apr 2014 10:02:46 +0800 Subject: [PATCH] re-adds 0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch Change-Id: I76512e455a321952870e3d57fd1e5c74f06d5ad1 Rewritten-From: b4a1fb175ae13541e4a1bdb4946cdc11ef4bf23a --- ...d-iptables-rules-to-protect-dnsmasq-.patch | 538 ++++++++++++++++++ trusty/debian/patches/series | 1 + 2 files changed, 539 insertions(+) create mode 100644 trusty/debian/patches/0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch diff --git a/trusty/debian/patches/0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch b/trusty/debian/patches/0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch new file mode 100644 index 000000000..6e43e634d --- /dev/null +++ b/trusty/debian/patches/0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch @@ -0,0 +1,538 @@ +From 4dde0ffdddeaddef219d0ff6d131d474ec190167 Mon Sep 17 00:00:00 2001 +From: Sylvain Afchain +Date: Thu, 12 Dec 2013 23:20:17 +0100 +Subject: [PATCH] Add parameter and iptables rules to protect dnsmasq ports + +Add a paramater to the configuration file of the dhcp +agent. With this new param set as true, iptables rules +are inserted to limit dns requests to dnsmasq only to +the clients which are on the same subnet. + +Change-Id: Iac3635326cb81d5de51b903510ff31cb6164aa86 +Closes-bug: #1260731 +--- + etc/dhcp_agent.ini | 5 + + neutron/agent/linux/dhcp.py | 159 ++++++++++++++++++++ + neutron/tests/unit/test_linux_dhcp.py | 259 ++++++++++++++++++++++++++++++++- + 3 files changed, 421 insertions(+), 2 deletions(-) + +diff --git a/etc/dhcp_agent.ini b/etc/dhcp_agent.ini +index 9836d35..df11b35 100644 +--- a/etc/dhcp_agent.ini ++++ b/etc/dhcp_agent.ini +@@ -86,3 +86,8 @@ + # Timeout for ovs-vsctl commands. + # If the timeout expires, ovs commands will fail with ALARMCLOCK error. + # ovs_vsctl_timeout = 10 ++ ++# Limits the dns requests to dnsmasq only to clients which are on its subnet. ++# Useful when a subnet is routed to another one or in the case of an ++# external network. ++# isolate_dns_requests = False +diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py +index e650c00..267e020 100644 +--- a/neutron/agent/linux/dhcp.py ++++ b/neutron/agent/linux/dhcp.py +@@ -29,6 +29,7 @@ from oslo.config import cfg + import six + + from neutron.agent.linux import ip_lib ++from neutron.agent.linux import iptables_manager + from neutron.agent.linux import utils + from neutron.common import constants + from neutron.common import exceptions +@@ -59,6 +60,9 @@ OPTS = [ + 'dnsmasq_lease_max', + default=(2 ** 24), + help=_('Limit number of leases to prevent a denial-of-service.')), ++ cfg.BoolOpt('isolate_dns_requests', default=False, ++ help=_("Allow dns requests to dnsmasq only from clients " ++ "on its subnet.")), + ] + + IPV4 = 4 +@@ -75,6 +79,7 @@ METADATA_DEFAULT_CIDR = '%s/%d' % (METADATA_DEFAULT_IP, + METADATA_PORT = 80 + WIN2k3_STATIC_DNS = 249 + NS_PREFIX = 'qdhcp-' ++DNS_CHAIN_PREFIX = 'dns-' + + + class DictModel(object): +@@ -670,7 +675,67 @@ class Dnsmasq(DhcpLocalProcess): + sock.close() + + ++class NamespaceIptablesManagerCache(object): ++ def __init__(self): ++ self.iptables_managers = {} ++ self.subnets = {} ++ ++ def get_or_create(self, network, root_helper): ++ ns = network.namespace ++ ++ im = self.iptables_managers.get(ns) ++ if not im: ++ im = iptables_manager.IptablesManager( ++ root_helper=root_helper, ++ use_ipv6=True, ++ namespace=network.namespace) ++ self.iptables_managers[ns] = im ++ ++ return im ++ ++ def is_up_to_date(self, network): ++ subnets = self.subnets.get(network.id) ++ current_subnets = set([subnet.id for subnet in network.subnets]) ++ return subnets == current_subnets ++ ++ def get(self, network): ++ return self.iptables_managers.get(network.namespace) ++ ++ def remove(self, network): ++ self.subnets.pop(network.id, None) ++ self.iptables_managers.pop(network.namespace, None) ++ ++ def apply(self, network): ++ im = self.iptables_managers.get(network.namespace) ++ if not im: ++ LOG.warn(_("apply called on a non existant iptables_manager " ++ "for network %s, get_or_create has to be called " ++ "before"), network.id) ++ return ++ ++ self.subnets[network.id] = set([subnet.id ++ for subnet in network.subnets]) ++ ++ im.apply() ++ ++ ++class InterfaceNameCache(object): ++ def __init__(self): ++ self.interface_names = {} ++ ++ def set(self, network, interface_name): ++ self.interface_names[network.id] = interface_name ++ ++ def get(self, network): ++ return self.interface_names.get(network.id) ++ ++ def remove(self, network): ++ self.interface_names.pop(network.id, None) ++ ++ + class DeviceManager(object): ++ iptables_manager_cache = NamespaceIptablesManagerCache() ++ interface_name_cache = InterfaceNameCache() + + def __init__(self, conf, root_helper, plugin): + self.conf = conf +@@ -811,10 +876,93 @@ class DeviceManager(object): + + return dhcp_port + ++ @staticmethod ++ def _get_isolation_rule(proto, chain, interface): ++ return ('-p %(proto)s -m %(proto)s ' ++ '--dport %(port)s -i %(interface)s ' ++ '-j $%(chain)s') % {'port': DNS_PORT, ++ 'proto': proto, ++ 'chain': chain, ++ 'interface': interface} ++ ++ def _remove_dns_isolation(self, network): ++ interface_name = DeviceManager.interface_name_cache.get(network) ++ if not interface_name: ++ LOG.warn(_("Error unable to get interface name for network: %s"), ++ network.id) ++ return ++ ++ im = DeviceManager.iptables_manager_cache.get(network) ++ if not im: ++ LOG.error(_("Error unable get the iptables manager created " ++ "for network %s"), network.id) ++ return ++ ++ rules_chain = iptables_manager.get_chain_name(DNS_CHAIN_PREFIX + ++ network.id) ++ ++ for tables in [im.ipv4, im.ipv6]: ++ tables['filter'].remove_chain(rules_chain) ++ ++ im.apply() ++ ++ DeviceManager.iptables_manager_cache.remove(network) ++ ++ def _apply_dns_isolation(self, network, interface_name=None): ++ if DeviceManager.iptables_manager_cache.is_up_to_date( ++ network): ++ return ++ ++ im = DeviceManager.iptables_manager_cache.get_or_create( ++ network, self.root_helper) ++ if not im: ++ LOG.error(_("Error unable to create or get an iptables manager " ++ "for network %s"), network.id) ++ return ++ ++ if not interface_name: ++ interface_name = DeviceManager.interface_name_cache.get(network) ++ if not interface_name: ++ LOG.error(_("Error unable to get interface name for network: %s"), ++ network.id) ++ return ++ ++ rules_chain = iptables_manager.get_chain_name(DNS_CHAIN_PREFIX + ++ network.id) ++ ++ for tables in [im.ipv4, im.ipv6]: ++ tables['filter'].add_chain(rules_chain) ++ ++ # empty_chain has to be called since a previous subnet ++ # could be now removed, with an empty chain only current subnet ++ # rules will be present. ++ tables['filter'].empty_chain(rules_chain) ++ tables['filter'].add_rule(rules_chain, '-j DROP') ++ ++ for proto in [UDP, TCP]: ++ rule = self._get_isolation_rule(proto, rules_chain, ++ interface_name) ++ tables['filter'].add_rule('INPUT', rule) ++ ++ # allow traffic from subnets everything else will be denied ++ for subnet in network.subnets: ++ if not subnet.enable_dhcp: ++ continue ++ ++ tables = im.ipv4 if subnet.ip_version == IPV4 else im.ipv6 ++ ++ rule = '-s ' + subnet.cidr + ' -j RETURN' ++ tables['filter'].add_rule(rules_chain, ++ rule, top=True) ++ ++ DeviceManager.iptables_manager_cache.apply(network) ++ + def setup(self, network, reuse_existing=False): + """Create and initialize a device for network's DHCP on this host.""" + port = self.setup_dhcp_port(network) ++ + interface_name = self.get_interface_name(network, port) ++ DeviceManager.interface_name_cache.set(network, interface_name) + + if ip_lib.device_exists(interface_name, + self.root_helper, +@@ -853,6 +1001,9 @@ class DeviceManager(object): + if self.conf.use_namespaces: + self._set_default_route(network, port) + ++ if self.conf.isolate_dns_requests: ++ self._apply_dns_isolation(network, interface_name) ++ + return interface_name + + def update(self, network): +@@ -864,9 +1015,17 @@ class DeviceManager(object): + raise exceptions.NetworkNotFound(net_id=network.id) + self._set_default_route(network, port) + ++ if self.conf.isolate_dns_requests: ++ self._apply_dns_isolation(network) ++ + def destroy(self, network, device_name): + """Destroy the device used for the network's DHCP on this host.""" ++ if self.conf.isolate_dns_requests: ++ self._remove_dns_isolation(network) ++ + self.driver.unplug(device_name, namespace=network.namespace) + + self.plugin.release_dhcp_port(network.id, + self.get_device_id(network)) ++ ++ DeviceManager.interface_name_cache.remove(network) +diff --git a/neutron/tests/unit/test_linux_dhcp.py b/neutron/tests/unit/test_linux_dhcp.py +index 7764bce..f18677f 100644 +--- a/neutron/tests/unit/test_linux_dhcp.py ++++ b/neutron/tests/unit/test_linux_dhcp.py +@@ -15,6 +15,7 @@ + # License for the specific language governing permissions and limitations + # under the License. + ++import contextlib + import os + + import mock +@@ -396,8 +397,8 @@ class TestBase(base.BaseTestCase): + self.conf.register_opts(base_config.core_opts) + self.conf.register_opts(dhcp.OPTS) + config.register_interface_driver_opts_helper(self.conf) +- instance = mock.patch("neutron.agent.linux.dhcp.DeviceManager") +- self.mock_mgr = instance.start() ++ self.device_mock = mock.patch("neutron.agent.linux.dhcp.DeviceManager") ++ self.mock_mgr = self.device_mock.start() + self.conf.register_opt(cfg.BoolOpt('enable_isolated_metadata', + default=True)) + self.conf(args=args) +@@ -531,6 +532,260 @@ class TestDhcpLocalProcess(TestBase): + self.assertEqual(lp.called, ['spawn']) + self.assertTrue(mocks['interface_name'].__set__.called) + ++ def _test_namespace_iptables_manager_cache(self): ++ network1 = FakeV4Network() ++ network2 = FakeV4Network() ++ network2.id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' ++ network2.namespace = 'abc' ++ network3 = FakeV4Network() ++ network3.id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' ++ network3.namespace = 'ghi' ++ ++ im_cache = dhcp.NamespaceIptablesManagerCache() ++ im1 = im_cache.get_or_create(network1, 'sudo') ++ self.assertIsNotNone(im1) ++ ++ im = im_cache.get_or_create(network1, 'sudo') ++ self.assertEqual(im, im1) ++ ++ im2 = im_cache.get_or_create(network2, 'sudo') ++ self.assertIsNotNone(im2) ++ self.assertNotEqual(im1, im2) ++ ++ im = im_cache.get(network1) ++ self.assertEqual(im, im1) ++ ++ im = im_cache.get(network2) ++ self.assertEqual(im, im2) ++ ++ im = im_cache.get(network3) ++ self.assertIsNone(im) ++ ++ im_cache.remove(network1) ++ im = im_cache.get(network1) ++ self.assertIsNone(im) ++ ++ iptables_inst = mock.Mock() ++ with contextlib.nested(mock.patch('neutron.agent.linux.' ++ 'iptables_manager.IptablesManager', ++ return_value=iptables_inst), ++ mock.patch.object(dhcp.LOG, 'error') ++ ) as (ipm, log): ++ im_cache = dhcp.NamespaceIptablesManagerCache() ++ up_to_date = im_cache.is_up_to_date(network1) ++ self.assertFalse(up_to_date) ++ ++ im = im_cache.get_or_create(network1, 'sudo') ++ im_cache.apply(network1) ++ ++ up_to_date = im_cache.is_up_to_date(network1) ++ self.assertTrue(up_to_date) ++ ++ network1.subnets.append(FakeV4SubnetNoRouter()) ++ up_to_date = im_cache.is_up_to_date(network1) ++ self.assertFalse(up_to_date) ++ ++ im_cache.apply(network3) ++ log.assert_called_only_once() ++ ++ def test_interface_name_cache(self): ++ int_cache = dhcp.InterfaceNameCache() ++ ++ network = FakeV4Network() ++ int_cache.set(network, 'tap123') ++ name = int_cache.get(network) ++ self.assertEqual('tap123', name) ++ ++ name = int_cache.get(FakeV4SubnetNoRouter()) ++ self.assertEqual(name, None) ++ ++ int_cache.remove(network) ++ name = int_cache.get(network) ++ self.assertEqual(name, None) ++ ++ def test_apply_dns_isolation_non_existing_port(self): ++ self.device_mock.stop() ++ self.conf.set_override('interface_driver', ++ 'neutron.agent.linux.interface.' ++ 'OVSInterfaceDriver') ++ ++ iptables_inst = mock.Mock() ++ v4filter_inst = mock.Mock() ++ v6filter_inst = mock.Mock() ++ v4filter_inst.chains = [] ++ v6filter_inst.chains = [] ++ iptables_inst.ipv4 = {'filter': v4filter_inst} ++ iptables_inst.ipv6 = {'filter': v6filter_inst} ++ ++ plugin_inst = mock.Mock() ++ ++ dhcp.DeviceManager.interface_name_cache = dhcp.InterfaceNameCache() ++ dhcp.DeviceManager.iptables_manager_cache = ( ++ dhcp.NamespaceIptablesManagerCache()) ++ ++ with contextlib.nested(mock.patch('neutron.agent.linux.interface.' ++ 'OVSInterfaceDriver'), ++ mock.patch('neutron.agent.linux.' ++ 'iptables_manager.IptablesManager', ++ return_value=iptables_inst), ++ mock.patch.object(dhcp.LOG, 'error') ++ ) as (ovs_driver, ipm, log): ++ network = FakeV4Network() ++ ++ plugin_inst.get_dhcp_port.return_value = None ++ ++ device_mng = dhcp.DeviceManager(self.conf, ++ 'sudo', plugin_inst) ++ device_mng.driver.get_device_name.return_value = 'tap123' ++ device_mng.get_interface_name(network, network.ports[0]) ++ ++ log.assert_called_once() ++ ++ self.assertFalse(v4filter_inst.called) ++ self.assertFalse(v6filter_inst.called) ++ self.assertFalse(iptables_inst.apply.called) ++ ++ def test_apply_dns_isolation(self): ++ self.device_mock.stop() ++ self.conf.set_override('interface_driver', ++ 'neutron.agent.linux.interface.' ++ 'OVSInterfaceDriver') ++ ++ iptables_inst = mock.Mock() ++ v4filter_inst = mock.Mock() ++ v6filter_inst = mock.Mock() ++ v4filter_inst.chains = [] ++ v6filter_inst.chains = [] ++ iptables_inst.ipv4 = {'filter': v4filter_inst} ++ iptables_inst.ipv6 = {'filter': v6filter_inst} ++ ++ plugin_inst = mock.Mock() ++ ++ dhcp.DeviceManager.interface_name_cache = dhcp.InterfaceNameCache() ++ dhcp.DeviceManager.iptables_manager_cache = ( ++ dhcp.NamespaceIptablesManagerCache()) ++ ++ with contextlib.nested(mock.patch('neutron.agent.linux.interface.' ++ 'OVSInterfaceDriver'), ++ mock.patch('neutron.agent.linux.' ++ 'iptables_manager.IptablesManager', ++ return_value=iptables_inst) ++ ) as (ovs_driver, ipm): ++ network = FakeV4Network() ++ ++ device_mng = dhcp.DeviceManager(self.conf, ++ 'sudo', plugin_inst) ++ device_mng.interface_name_cache.set(network, 'tap123') ++ ++ device_mng._apply_dns_isolation(network) ++ ++ callsv4 = [mock.call.add_chain('dns-aaaaaaa'), ++ mock.call.empty_chain('dns-aaaaaaa'), ++ mock.call.add_rule('dns-aaaaaaa', '-j DROP'), ++ mock.call.add_rule('INPUT', '-p udp -m udp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-aaaaaaa'), ++ mock.call.add_rule('INPUT', '-p tcp -m tcp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-aaaaaaa'), ++ mock.call.add_rule('dns-aaaaaaa', ++ '-s 192.168.0.0/24 ' ++ '-j RETURN', top=True)] ++ v4filter_inst.assert_has_calls(callsv4) ++ ++ callsv6 = [mock.call.add_chain('dns-aaaaaaa'), ++ mock.call.empty_chain('dns-aaaaaaa'), ++ mock.call.add_rule('dns-aaaaaaa', '-j DROP'), ++ mock.call.add_rule('INPUT', '-p udp -m udp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-aaaaaaa'), ++ mock.call.add_rule('INPUT', '-p tcp -m tcp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-aaaaaaa')] ++ v6filter_inst.assert_has_calls(callsv6) ++ ++ iptables_inst.apply.assert_called_once() ++ ++ v4filter_inst.reset_mock() ++ v6filter_inst.reset_mock() ++ ++ network = FakeV6Network() ++ ++ device_mng.interface_name_cache.set(network, 'tap123') ++ device_mng._apply_dns_isolation(network) ++ ++ callsv4 = [mock.call.add_chain('dns-bbbbbbb'), ++ mock.call.empty_chain('dns-bbbbbbb'), ++ mock.call.add_rule('dns-bbbbbbb', '-j DROP'), ++ mock.call.add_rule('INPUT', '-p udp -m udp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-bbbbbbb'), ++ mock.call.add_rule('INPUT', '-p tcp -m tcp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-bbbbbbb')] ++ v4filter_inst.assert_has_calls(callsv4) ++ ++ callsv6 = [mock.call.add_chain('dns-bbbbbbb'), ++ mock.call.empty_chain('dns-bbbbbbb'), ++ mock.call.add_rule('dns-bbbbbbb', '-j DROP'), ++ mock.call.add_rule('INPUT', '-p udp -m udp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-bbbbbbb'), ++ mock.call.add_rule('INPUT', '-p tcp -m tcp ' ++ '--dport 53 -i tap123 ' ++ '-j $dns-bbbbbbb'), ++ mock.call.add_rule('dns-bbbbbbb', ++ '-s fdca:3ba5:a17a:4ba3::/64' ++ ' -j RETURN', top=True)] ++ v6filter_inst.assert_has_calls(callsv6) ++ ++ iptables_inst.apply.assert_called_once() ++ ++ def test_remove_dns_isolation(self): ++ self.device_mock.stop() ++ self.conf.set_override('interface_driver', ++ 'neutron.agent.linux.interface.' ++ 'OVSInterfaceDriver') ++ ++ iptables_inst = mock.Mock() ++ v4filter_inst = mock.Mock() ++ v6filter_inst = mock.Mock() ++ v4filter_inst.chains = [] ++ v6filter_inst.chains = [] ++ iptables_inst.ipv4 = {'filter': v4filter_inst} ++ iptables_inst.ipv6 = {'filter': v6filter_inst} ++ ++ plugin_inst = mock.Mock() ++ ++ dhcp.DeviceManager.interface_name_cache = dhcp.InterfaceNameCache() ++ dhcp.DeviceManager.iptables_manager_cache = ( ++ dhcp.NamespaceIptablesManagerCache()) ++ ++ with contextlib.nested(mock.patch('neutron.agent.linux.interface.' ++ 'OVSInterfaceDriver'), ++ mock.patch('neutron.agent.linux.' ++ 'iptables_manager.IptablesManager', ++ return_value=iptables_inst) ++ ) as (ovs_driver, ipm): ++ network = FakeV4Network() ++ ++ device_mng = dhcp.DeviceManager(self.conf, ++ 'sudo', plugin_inst) ++ device_mng.interface_name_cache.set(network, 'tap123') ++ ++ # First, apply the dns isolation ++ device_mng._apply_dns_isolation(network) ++ ++ # Get a new instance of the DeviceManager, in order to check ++ # that the iptables manager and the interface name caches work. ++ device_mng = dhcp.DeviceManager(self.conf, ++ 'sudo', plugin_inst) ++ device_mng._remove_dns_isolation(network) ++ ++ v4filter_inst.remove_chain.assert_called_once_with('dns-aaaaaaa') ++ ++ iptables_inst.apply.assert_called_once() ++ + def test_disable_not_active(self): + attrs_to_mock = dict([(a, mock.DEFAULT) for a in + ['active', 'interface_name', 'pid']]) +-- +1.7.9.5 + diff --git a/trusty/debian/patches/series b/trusty/debian/patches/series index 40769f9cf..bc368e184 100644 --- a/trusty/debian/patches/series +++ b/trusty/debian/patches/series @@ -1,4 +1,5 @@ fix-alembic-migration-with-sqlite3.patch better-config-default.patch OVS_lib_defer_apply_doesn_t_handle_concurrency.patch +0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch 0004-Fix-Metering-doesn-t-respect-the-l3-agent-binding.patch -- 2.45.2