]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
re-adds 0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch
authorThomas Goirand <thomas@goirand.fr>
Wed, 16 Apr 2014 02:02:46 +0000 (10:02 +0800)
committerThomas Goirand <thomas@goirand.fr>
Wed, 16 Apr 2014 02:02:46 +0000 (10:02 +0800)
Change-Id: I76512e455a321952870e3d57fd1e5c74f06d5ad1

Rewritten-From: b4a1fb175ae13541e4a1bdb4946cdc11ef4bf23a

trusty/debian/patches/0001-Add-parameter-and-iptables-rules-to-protect-dnsmasq-.patch [new file with mode: 0644]
trusty/debian/patches/series

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 (file)
index 0000000..6e43e63
--- /dev/null
@@ -0,0 +1,538 @@
+From 4dde0ffdddeaddef219d0ff6d131d474ec190167 Mon Sep 17 00:00:00 2001
+From: Sylvain Afchain <sylvain.afchain@enovance.com>
+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
+
index 40769f9cf18722183e800b4ac15508c70e38b7e3..bc368e184768d72a275aa4a867aeeab028ed0a8d 100644 (file)
@@ -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