From a7afd6e86d833ea44fc17528158e6819618d07f7 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Mon, 29 Oct 2018 17:08:33 +0000 Subject: [PATCH] Secure dnsmasq process against external abuse Currently any dhcp agent instance will work as an open resolver. For deployments using publicly routed addresses for tenant networks, this allows the agent being abused in dDoS attacks, see [1]. By setting the `--local-service` option dnsmasq will filter DNS queries and reply only to queries from directly attached networks. [1] https://bugs.launchpad.net/neutron/+bug/1501206 Conflicts: neutron/cmd/sanity_check.py Closes-Bug: 1501206 Change-Id: I76d810aad2ce0f15a88bd798963012fa0efca74e (cherry picked from commit 0fce3ca2c1641fbcfb8327a86d7225e2c3972263) --- neutron/agent/linux/dhcp.py | 5 +---- neutron/cmd/sanity/checks.py | 15 +++++++++++++++ neutron/cmd/sanity_check.py | 15 +++++++++++++++ neutron/tests/unit/agent/linux/test_dhcp.py | 3 +-- .../dnsmasq-local-service-c8eaa91894a7d6d4.yaml | 8 ++++++++ 5 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/dnsmasq-local-service-c8eaa91894a7d6d4.yaml diff --git a/neutron/agent/linux/dhcp.py b/neutron/agent/linux/dhcp.py index 7bd89bab4ff..ce37f862557 100644 --- a/neutron/agent/linux/dhcp.py +++ b/neutron/agent/linux/dhcp.py @@ -334,24 +334,21 @@ class Dnsmasq(DhcpLocalProcess): 'dnsmasq', '--no-hosts', _no_resolv, - '--except-interface=lo', '--pid-file=%s' % pid_file, '--dhcp-hostsfile=%s' % self.get_conf_file_name('host'), '--addn-hosts=%s' % self.get_conf_file_name('addn_hosts'), '--dhcp-optsfile=%s' % self.get_conf_file_name('opts'), '--dhcp-leasefile=%s' % self.get_conf_file_name('leases'), '--dhcp-match=set:ipxe,175', + '--local-service', ] if self.device_manager.driver.bridged: cmd += [ '--bind-interfaces', - '--interface=%s' % self.interface_name, ] else: cmd += [ '--bind-dynamic', - '--interface=%s' % self.interface_name, - '--interface=tap*', '--bridge-interface=%s,tap*' % self.interface_name, ] diff --git a/neutron/cmd/sanity/checks.py b/neutron/cmd/sanity/checks.py index 560285afd04..1093f9a6e60 100644 --- a/neutron/cmd/sanity/checks.py +++ b/neutron/cmd/sanity/checks.py @@ -198,6 +198,21 @@ def get_dnsmasq_version_with_dhcp_release6(): return DNSMASQ_VERSION_DHCP_RELEASE6 +def dnsmasq_local_service_supported(): + cmd = ['dnsmasq', '--test', '--local-service'] + env = {'LC_ALL': 'C'} + obj, cmd = agent_utils.create_process(cmd, addl_env=env) + _stdout, _stderr = obj.communicate() + returncode = obj.returncode + if returncode == 127: + LOG.debug("Exception while checking dnsmasq version. " + "dnsmasq: No such file or directory") + return False + elif returncode == 1: + return False + return True + + def dnsmasq_version_supported(): try: cmd = ['dnsmasq', '--version'] diff --git a/neutron/cmd/sanity_check.py b/neutron/cmd/sanity_check.py index 82020c549d2..af9ba7e9e88 100644 --- a/neutron/cmd/sanity_check.py +++ b/neutron/cmd/sanity_check.py @@ -118,6 +118,15 @@ def check_dnsmasq_version(): return result +def check_dnsmasq_local_service_supported(): + result = checks.dnsmasq_local_service_supported() + if not result: + LOG.error('The installed version of dnsmasq is too old. ' + 'Please update to a version supporting the ' + '--local-service option.') + return result + + def check_keepalived_ipv6_support(): result = checks.keepalived_ipv6_supported() if not result: @@ -304,6 +313,9 @@ OPTS = [ help=_('Check for VF extended management support')), BoolOptCallback('read_netns', check_read_netns, help=_('Check netns permission settings')), + BoolOptCallback('dnsmasq_local_service_supported', + check_dnsmasq_local_service_supported, + help=_('Check for local-service support in dnsmasq')), BoolOptCallback('dnsmasq_version', check_dnsmasq_version, help=_('Check minimal dnsmasq version'), deprecated_for_removal=True, @@ -367,6 +379,9 @@ def enable_tests_from_config(): cfg.CONF.set_default('read_netns', True) if cfg.CONF.OVS.ovsdb_interface == 'native': cfg.CONF.set_default('ovsdb_native', True) + if cfg.CONF.dhcp_driver == 'neutron.agent.linux.dhcp.Dnsmasq': + cfg.CONF.set_default('dnsmasq_local_service_supported', True) + cfg.CONF.set_default('dnsmasq_version', True) if cfg.CONF.l3_ha: cfg.CONF.set_default('keepalived_ipv6_support', True) cfg.CONF.set_default('ip_nonlocal_bind', True) diff --git a/neutron/tests/unit/agent/linux/test_dhcp.py b/neutron/tests/unit/agent/linux/test_dhcp.py index 34ea174d357..46af2bd44dc 100644 --- a/neutron/tests/unit/agent/linux/test_dhcp.py +++ b/neutron/tests/unit/agent/linux/test_dhcp.py @@ -1233,15 +1233,14 @@ class TestDnsmasq(TestBase): 'dnsmasq', '--no-hosts', no_resolv, - '--except-interface=lo', '--pid-file=%s' % expected_pid_file, '--dhcp-hostsfile=/dhcp/%s/host' % network.id, '--addn-hosts=/dhcp/%s/addn_hosts' % network.id, '--dhcp-optsfile=/dhcp/%s/opts' % network.id, '--dhcp-leasefile=/dhcp/%s/leases' % network.id, '--dhcp-match=set:ipxe,175', + '--local-service', '--bind-interfaces', - '--interface=tap0', ] seconds = '' diff --git a/releasenotes/notes/dnsmasq-local-service-c8eaa91894a7d6d4.yaml b/releasenotes/notes/dnsmasq-local-service-c8eaa91894a7d6d4.yaml new file mode 100644 index 00000000000..50933ae1a5c --- /dev/null +++ b/releasenotes/notes/dnsmasq-local-service-c8eaa91894a7d6d4.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixes bug `1501206 `_. + This ensures that DHCP agent instances running dnsmasq as a DNS server + can no longer be exploited as DNS amplifiers when the tenant network is + using publicly routed IP addresses by adding an option that will allow + them to only serve DNS requests from local networks.