From 4db00f1cd820f4fc462ce3537d692694224e02a4 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Sep 2020 18:23:08 +0200 Subject: smoketest: dns: forwarding: T2921: add initial testcases --- .../scripts/cli/test_service_dns_forwarding.py | 163 +++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100755 smoketest/scripts/cli/test_service_dns_forwarding.py (limited to 'smoketest/scripts/cli/test_service_dns_forwarding.py') diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py new file mode 100755 index 000000000..0ae27a4d4 --- /dev/null +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-2020 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +import os +import unittest + +from psutil import process_iter + +from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.util import read_file +from vyos.util import process_named_running + +CONFIG_FILE = '/run/powerdns/recursor.conf' +FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf' +PROCESS_NAME= 'pdns-r/worker' + +base_path = ['service', 'dns', 'forwarding'] + +allow_from = ['192.0.2.0/24', '2001:db8::/32'] +listen_adress = ['127.0.0.1', '::1'] + +def get_config_value(key, file=CONFIG_FILE): + tmp = read_file(file) + tmp = re.findall(r'\n{}=+(.*)'.format(key), tmp) + return tmp[0] + +class TestServicePowerDNS(unittest.TestCase): + def setUp(self): + self.session = ConfigSession(os.getpid()) + + def tearDown(self): + # Delete DNS forwarding configuration + self.session.delete(base_path) + self.session.commit() + del self.session + + def test_basic_forwarding(self): + """ Check basic DNS forwarding settings """ + cache_size = '20' + negative_ttl = '120' + + self.session.set(base_path + ['cache-size', cache_size]) + self.session.set(base_path + ['negative-ttl', negative_ttl]) + + # check validate() - allow from must be defined + with self.assertRaises(ConfigSessionError): + self.session.commit() + for network in allow_from: + self.session.set(base_path + ['allow-from', network]) + + # check validate() - listen-address must be defined + with self.assertRaises(ConfigSessionError): + self.session.commit() + for address in listen_adress: + self.session.set(base_path + ['listen-address', address]) + + # configure DNSSEC + self.session.set(base_path + ['dnssec', 'validate']) + + # commit changes + self.session.commit() + + # Check configured cache-size + tmp = get_config_value('max-cache-entries') + self.assertEqual(tmp, cache_size) + + # Networks allowed to query this server + tmp = get_config_value('allow-from') + self.assertEqual(tmp, ','.join(allow_from)) + + # Addresses to listen for DNS queries + tmp = get_config_value('local-address') + self.assertEqual(tmp, ','.join(listen_adress)) + + # Maximum amount of time negative entries are cached + tmp = get_config_value('max-negative-ttl') + self.assertEqual(tmp, negative_ttl) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_dnssec(self): + """ DNSSEC option testing """ + + for network in allow_from: + self.session.set(base_path + ['allow-from', network]) + for address in listen_adress: + self.session.set(base_path + ['listen-address', address]) + + options = ['off', 'process-no-validate', 'process', 'log-fail', 'validate'] + for option in options: + self.session.set(base_path + ['dnssec', option]) + + # commit changes + self.session.commit() + + tmp = get_config_value('dnssec') + self.assertEqual(tmp, option) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_external_nameserver(self): + """ Externe Domain Name Servers (DNS) addresses """ + + for network in allow_from: + self.session.set(base_path + ['allow-from', network]) + for address in listen_adress: + self.session.set(base_path + ['listen-address', address]) + + nameservers = ['192.0.2.1', '192.0.2.2'] + for nameserver in nameservers: + self.session.set(base_path + ['name-server', nameserver]) + + # commit changes + self.session.commit() + + tmp = get_config_value(r'\+.', file=FORWARD_FILE) + self.assertEqual(tmp, ', '.join(nameservers)) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + def test_domain_forwarding(self): + """ Externe Domain Name Servers (DNS) addresses """ + + for network in allow_from: + self.session.set(base_path + ['allow-from', network]) + for address in listen_adress: + self.session.set(base_path + ['listen-address', address]) + + domains = ['vyos.io', 'vyos.net'] + nameservers = ['192.0.2.1', '192.0.2.2'] + for domain in domains: + for nameserver in nameservers: + self.session.set(base_path + ['domain', domain, 'server', nameserver]) + + # commit changes + self.session.commit() + + for domain in domains: + tmp = get_config_value(domain, file=FORWARD_FILE) + self.assertEqual(tmp, ', '.join(nameservers)) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From 58ead7415a3fe8d786bdb6fd2a99d0a57770dbd7 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 24 Sep 2020 19:54:15 +0200 Subject: smoketest: (re-)use process_named_running() from vyos.util --- smoketest/scripts/cli/test_interfaces_macsec.py | 6 +++--- smoketest/scripts/cli/test_interfaces_wireless.py | 10 ++++------ smoketest/scripts/cli/test_service_dns_dynamic.py | 21 +++++---------------- .../scripts/cli/test_service_dns_forwarding.py | 2 -- smoketest/scripts/cli/test_service_mdns-repeater.py | 4 ++-- smoketest/scripts/cli/test_service_pppoe-server.py | 8 ++++---- smoketest/scripts/cli/test_service_router-advert.py | 6 ++---- smoketest/scripts/cli/test_service_snmp.py | 14 ++++++++------ smoketest/scripts/cli/test_service_ssh.py | 15 +++++++-------- smoketest/scripts/cli/test_system_lcd.py | 9 +++++---- smoketest/scripts/cli/test_system_ntp.py | 13 ++++++++----- smoketest/scripts/cli/test_vpn_openconnect.py | 8 +++----- 12 files changed, 51 insertions(+), 65 deletions(-) (limited to 'smoketest/scripts/cli/test_service_dns_forwarding.py') diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index fab5433de..6d1be86ba 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -17,13 +17,13 @@ import re import unittest -from psutil import process_iter from base_interfaces_test import BasicInterfaceTest from netifaces import interfaces -from vyos.ifconfig import Section from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section from vyos.util import read_file +from vyos.util import process_named_running def get_config_value(interface, key): tmp = read_file(f'/run/wpa_supplicant/{interface}.conf') @@ -103,7 +103,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest): self.assertEqual(tmp, '1460') # Check for running process - self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter())) + self.assertTrue(process_named_running('wpa_supplicant')) def test_mandatory_toptions(self): interface = 'macsec1' diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index 691f633b7..0e93b6432 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -19,8 +19,7 @@ import re import unittest from base_interfaces_test import BasicInterfaceTest -from psutil import process_iter - +from vyos.util import process_named_running from vyos.util import check_kmod from vyos.util import read_file @@ -54,10 +53,10 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): for option, option_value in self._options.items(): if 'type access-point' in option_value: # Check for running process - self.assertIn('hostapd', (p.name() for p in process_iter())) + self.assertTrue(process_named_running('hostapd')) elif 'type station' in option_value: # Check for running process - self.assertIn('wpa_supplicant', (p.name() for p in process_iter())) + self.assertTrue(process_named_running('wpa_supplicant')) else: self.assertTrue(False) @@ -137,8 +136,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest): self.assertIn(value, tmp) # Check for running process - self.assertIn('hostapd', (p.name() for p in process_iter())) - + self.assertTrue(process_named_running('hostapd')) if __name__ == '__main__': check_kmod('mac80211_hwsim') diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index be52360ed..51fa38912 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -19,10 +19,11 @@ import os import unittest from getpass import getuser -from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import read_file +PROCESS_NAME = 'ddclient' DDCLIENT_CONF = '/run/ddclient/ddclient.conf' base_path = ['service', 'dns', 'dynamic'] @@ -32,17 +33,6 @@ def get_config_value(key): tmp = tmp[0].rstrip(',') return tmp -def check_process(): - """ - Check for running process, process name changes dynamically e.g. - "ddclient - sleeping for 270 seconds", thus we need a different approach - """ - running = False - for p in process_iter(): - if "ddclient" in p.name(): - running = True - return running - class TestServiceDDNS(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) @@ -104,8 +94,7 @@ class TestServiceDDNS(unittest.TestCase): self.assertTrue(pwd == "'" + password + "'") # Check for running process - self.assertTrue(check_process()) - + self.assertTrue(process_named_running(PROCESS_NAME)) def test_rfc2136(self): """ Check if DDNS service can be configured and runs """ @@ -135,7 +124,7 @@ class TestServiceDDNS(unittest.TestCase): # TODO: inspect generated configuration file # Check for running process - self.assertTrue(check_process()) + self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py index 0ae27a4d4..717b5b56d 100755 --- a/smoketest/scripts/cli/test_service_dns_forwarding.py +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -18,8 +18,6 @@ import re import os import unittest -from psutil import process_iter - from vyos.configsession import ConfigSession, ConfigSessionError from vyos.util import read_file from vyos.util import process_named_running diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py index 18900b6d2..de73b9914 100755 --- a/smoketest/scripts/cli/test_service_mdns-repeater.py +++ b/smoketest/scripts/cli/test_service_mdns-repeater.py @@ -17,8 +17,8 @@ import os import unittest -from psutil import process_iter from vyos.configsession import ConfigSession +from vyos.util import process_named_running base_path = ['service', 'mdns', 'repeater'] intf_base = ['interfaces', 'dummy'] @@ -45,7 +45,7 @@ class TestServiceMDNSrepeater(unittest.TestCase): self.session.commit() # Check for running process - self.assertTrue("mdns-repeater" in (p.name() for p in process_iter())) + self.assertTrue(process_named_running('mdns-repeater')) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index 901ca792d..3a6b12ef4 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -14,15 +14,15 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import os import unittest from configparser import ConfigParser -from psutil import process_iter from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.util import process_named_running +process_name = 'accel-pppd' base_path = ['service', 'pppoe-server'] local_if = ['interfaces', 'dummy', 'dum667'] pppoe_conf = '/run/accel-pppd/pppoe.conf' @@ -116,7 +116,7 @@ class TestServicePPPoEServer(unittest.TestCase): self.assertEqual(conf['connlimit']['limit'], '20/min') # Check for running process - self.assertTrue('accel-pppd' in (p.name() for p in process_iter())) + self.assertTrue(process_named_running(process_name)) def test_radius_auth(self): """ Test configuration of RADIUS authentication for PPPoE server """ @@ -161,7 +161,7 @@ class TestServicePPPoEServer(unittest.TestCase): self.assertFalse(conf['ppp'].getboolean('ccp')) # Check for running process - self.assertTrue('accel-pppd' in (p.name() for p in process_iter())) + self.assertTrue(process_named_running(process_name)) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py index ec2110c8a..238f59e6d 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -18,9 +18,9 @@ import re import os import unittest -from psutil import process_iter from vyos.configsession import ConfigSession from vyos.util import read_file +from vyos.util import process_named_running RADVD_CONF = '/run/radvd/radvd.conf' @@ -90,10 +90,8 @@ class TestServiceRADVD(unittest.TestCase): tmp = get_config_value('AdvOnLink') self.assertEqual(tmp, 'off') - - # Check for running process - self.assertTrue('radvd' in (p.name() for p in process_iter())) + self.assertTrue(process_named_running('radvd')) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py index fb5f5393f..067a3c76b 100755 --- a/smoketest/scripts/cli/test_service_snmp.py +++ b/smoketest/scripts/cli/test_service_snmp.py @@ -19,12 +19,15 @@ import re import unittest from vyos.validate import is_ipv4 -from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import read_file +from vyos.util import process_named_running +PROCESS_NAME = 'snmpd' SNMPD_CONF = '/etc/snmp/snmpd.conf' + base_path = ['service', 'snmp'] def get_config_value(key): @@ -78,7 +81,7 @@ class TestSNMPService(unittest.TestCase): self.assertTrue(expected in config) # Check for running process - self.assertTrue("snmpd" in (p.name() for p in process_iter())) + self.assertTrue(process_named_running(PROCESS_NAME)) def test_snmpv3_sha(self): @@ -113,7 +116,7 @@ class TestSNMPService(unittest.TestCase): # TODO: read in config file and check values # Check for running process - self.assertTrue("snmpd" in (p.name() for p in process_iter())) + self.assertTrue(process_named_running(PROCESS_NAME)) def test_snmpv3_md5(self): """ Check if SNMPv3 can be configured with MD5 authentication and service runs""" @@ -147,8 +150,7 @@ class TestSNMPService(unittest.TestCase): # TODO: read in config file and check values # Check for running process - self.assertTrue("snmpd" in (p.name() for p in process_iter())) - + self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 79850fe44..0cd00ccce 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -18,10 +18,12 @@ import re import os import unittest -from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import read_file +from vyos.util import process_named_running +PROCESS_NAME = 'sshd' SSHD_CONF = '/run/ssh/sshd_config' base_path = ['service', 'ssh'] @@ -30,9 +32,6 @@ def get_config_value(key): tmp = re.findall(f'\n?{key}\s+(.*)', tmp) return tmp -def is_service_running(): - return 'sshd' in (p.name() for p in process_iter()) - class TestServiceSSH(unittest.TestCase): def setUp(self): self.session = ConfigSession(os.getpid()) @@ -62,7 +61,7 @@ class TestServiceSSH(unittest.TestCase): self.assertEqual('22', port) # Check for running process - self.assertTrue(is_service_running()) + self.assertTrue(process_named_running(PROCESS_NAME)) def test_ssh_single(self): """ Check if SSH service can be configured and runs """ @@ -101,7 +100,7 @@ class TestServiceSSH(unittest.TestCase): self.assertTrue("100" in keepalive) # Check for running process - self.assertTrue(is_service_running()) + self.assertTrue(process_named_running(PROCESS_NAME)) def test_ssh_multi(self): """ Check if SSH service can be configured and runs with multiple @@ -128,7 +127,7 @@ class TestServiceSSH(unittest.TestCase): self.assertIn(address, tmp) # Check for running process - self.assertTrue(is_service_running()) + self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_system_lcd.py b/smoketest/scripts/cli/test_system_lcd.py index 931a91c53..9385799b0 100755 --- a/smoketest/scripts/cli/test_system_lcd.py +++ b/smoketest/scripts/cli/test_system_lcd.py @@ -18,9 +18,10 @@ import os import unittest from configparser import ConfigParser -from psutil import process_iter from vyos.configsession import ConfigSession +from vyos.util import process_named_running +config_file = '/run/LCDd/LCDd.conf' base_path = ['system', 'lcd'] class TestSystemLCD(unittest.TestCase): @@ -42,13 +43,13 @@ class TestSystemLCD(unittest.TestCase): # load up ini-styled LCDd.conf conf = ConfigParser() - conf.read('/run/LCDd/LCDd.conf') + conf.read(config_file) self.assertEqual(conf['CFontzPacket']['Model'], '533') self.assertEqual(conf['CFontzPacket']['Device'], '/dev/ttyS1') - # both processes running - self.assertTrue('LCDd' in (p.name() for p in process_iter())) + # Check for running process + self.assertTrue(process_named_running('LCDd')) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py index 856a28916..2a7c64870 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_system_ntp.py @@ -18,11 +18,14 @@ import re import os import unittest -from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError -from vyos.template import vyos_address_from_cidr, vyos_netmask_from_cidr +from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError +from vyos.template import vyos_address_from_cidr +from vyos.template import vyos_netmask_from_cidr from vyos.util import read_file +from vyos.util import process_named_running +PROCESS_NAME = 'ntpd' NTP_CONF = '/etc/ntp.conf' base_path = ['system', 'ntp'] @@ -63,7 +66,7 @@ class TestSystemNTP(unittest.TestCase): self.assertTrue(test in tmp) # Check for running process - self.assertTrue("ntpd" in (p.name() for p in process_iter())) + self.assertTrue(process_named_running(PROCESS_NAME)) def test_ntp_clients(self): """ Test the allowed-networks statement """ @@ -102,7 +105,7 @@ class TestSystemNTP(unittest.TestCase): self.assertEqual(tmp, test) # Check for running process - self.assertTrue("ntpd" in (p.name() for p in process_iter())) + self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': unittest.main() diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index d2b82d686..2ba6aabf9 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -14,13 +14,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import os import unittest -from psutil import process_iter -from vyos.configsession import ConfigSession, ConfigSessionError -from vyos.util import read_file +from vyos.configsession import ConfigSession +from vyos.util import process_named_running OCSERV_CONF = '/run/ocserv/ocserv.conf' base_path = ['vpn', 'openconnect'] @@ -52,7 +50,7 @@ class TestVpnOpenconnect(unittest.TestCase): self.session.commit() # Check for running process - self.assertTrue("ocserv-main" in (p.name() for p in process_iter())) + self.assertTrue(process_named_running('ocserv-main')) if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From f39f5dde342aa5e14d1fb4155920c61ac5fd11b1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 25 Sep 2020 19:21:36 +0200 Subject: dns: forwarding: T2921: migrate to get_config_dict() --- data/configd-include.json | 1 + data/templates/dns-forwarding/recursor.conf.tmpl | 2 +- .../recursor.forward-zones.conf.tmpl | 4 +- .../recursor.vyos-hostsd.conf.lua.tmpl | 4 +- interface-definitions/dns-forwarding.xml.in | 11 +- .../scripts/cli/test_service_dns_forwarding.py | 35 ++++- src/conf_mode/dns_forwarding.py | 172 ++++++++------------- src/services/vyos-hostsd | 21 +-- src/utils/vyos-hostsd-client | 6 +- 9 files changed, 122 insertions(+), 134 deletions(-) (limited to 'smoketest/scripts/cli/test_service_dns_forwarding.py') diff --git a/data/configd-include.json b/data/configd-include.json index 0c75657e0..2711a29b8 100644 --- a/data/configd-include.json +++ b/data/configd-include.json @@ -2,6 +2,7 @@ "bcast_relay.py", "dhcp_relay.py", "dhcpv6_relay.py", +"dns_forwarding.py", "dynamic_dns.py", "firewall_options.py", "host_name.py", diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl index d233b8abc..b0ae3cc61 100644 --- a/data/templates/dns-forwarding/recursor.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.conf.tmpl @@ -21,7 +21,7 @@ max-cache-entries={{ cache_size }} max-negative-ttl={{ negative_ttl }} # ignore-hosts-file -export-etc-hosts={{ export_hosts_file }} +export-etc-hosts={{ 'no' if ignore_hosts_file is defined else 'yes' }} # listen-address local-address={{ listen_address | join(',') }} diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl index e62b9bb81..90f35ae1c 100644 --- a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl +++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl @@ -19,10 +19,10 @@ +.={{ n.dot_zone_ns }} {% endif %} -{% if forward_zones %} +{% if forward_zones is defined %} # zones added via 'service dns forwarding domain' {% for zone, zonedata in forward_zones.items() %} -{% if zonedata['recursion-desired'] %}+{% endif %}{{ zone }}={{ zonedata['nslist']|join(', ') }} +{{ "+" if zonedata['recursion_desired'] is defined }}{{ zone }}={{ zonedata['server']|join(', ') }} {% endfor %} {% endif %} diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl index 8fefae0b2..784d5c360 100644 --- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl +++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl @@ -13,10 +13,10 @@ addNTA("{{ a }}.", "{{ tag }} alias") {% endfor %} {% endif %} -{% if forward_zones %} +{% if forward_zones is defined %} -- from 'service dns forwarding domain' {% for zone, zonedata in forward_zones.items() %} -{% if zonedata['addNTA'] %} +{% if zonedata['addnta'] is defined %} addNTA("{{ zone }}", "static") {% endif %} {% endfor %} diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in index aaf8bb27d..07e63d54a 100644 --- a/interface-definitions/dns-forwarding.xml.in +++ b/interface-definitions/dns-forwarding.xml.in @@ -16,7 +16,7 @@ - DNS forwarding cache size + DNS forwarding cache size (default: 10000) 0-10000 DNS forwarding cache size @@ -25,6 +25,7 @@ + 10000 @@ -37,7 +38,7 @@ - DNSSEC mode + DNSSEC mode (default: process-no-validate) off process-no-validate process log-fail validate @@ -62,9 +63,10 @@ Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses. - (off|process-no-validate|process|log-fail|validate) + ^(off|process-no-validate|process|log-fail|validate)$ + process-no-validate @@ -146,7 +148,7 @@ - Maximum amount of time negative entries are cached + Maximum amount of time negative entries are cached (default: 3600) 0-7200 Seconds to cache NXDOMAIN entries @@ -155,6 +157,7 @@ + 3600 diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py index 717b5b56d..5e2f3dfbd 100755 --- a/smoketest/scripts/cli/test_service_dns_forwarding.py +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -24,6 +24,7 @@ from vyos.util import process_named_running CONFIG_FILE = '/run/powerdns/recursor.conf' FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf' +HOSTSD_FILE = '/run/powerdns/recursor.vyos-hostsd.conf.lua' PROCESS_NAME= 'pdns-r/worker' base_path = ['service', 'dns', 'forwarding'] @@ -69,6 +70,9 @@ class TestServicePowerDNS(unittest.TestCase): # configure DNSSEC self.session.set(base_path + ['dnssec', 'validate']) + # Do not use local /etc/hosts file in name resolution + self.session.set(base_path + ['ignore-hosts-file']) + # commit changes self.session.commit() @@ -88,6 +92,10 @@ class TestServicePowerDNS(unittest.TestCase): tmp = get_config_value('max-negative-ttl') self.assertEqual(tmp, negative_ttl) + # Do not use local /etc/hosts file in name resolution + tmp = get_config_value('export-etc-hosts') + self.assertEqual(tmp, 'no') + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -130,6 +138,11 @@ class TestServicePowerDNS(unittest.TestCase): tmp = get_config_value(r'\+.', file=FORWARD_FILE) self.assertEqual(tmp, ', '.join(nameservers)) + # Do not use local /etc/hosts file in name resolution + # default: yes + tmp = get_config_value('export-etc-hosts') + self.assertEqual(tmp, 'yes') + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) @@ -141,21 +154,39 @@ class TestServicePowerDNS(unittest.TestCase): for address in listen_adress: self.session.set(base_path + ['listen-address', address]) - domains = ['vyos.io', 'vyos.net'] + domains = ['vyos.io', 'vyos.net', 'vyos.com'] nameservers = ['192.0.2.1', '192.0.2.2'] for domain in domains: for nameserver in nameservers: self.session.set(base_path + ['domain', domain, 'server', nameserver]) + # Test 'recursion-desired' flag for only one domain + if domain == domains[0]: + self.session.set(base_path + ['domain', domain, 'recursion-desired']) + + # Test 'negative trust anchor' flag for the second domain only + if domain == domains[1]: + self.session.set(base_path + ['domain', domain, 'addnta']) + # commit changes self.session.commit() + # Test configured name-servers + hosts_conf = read_file(HOSTSD_FILE) for domain in domains: - tmp = get_config_value(domain, file=FORWARD_FILE) + # Test 'recursion-desired' flag for the first domain only + if domain == domains[0]: key =f'\+{domain}' + else: key =f'{domain}' + tmp = get_config_value(key, file=FORWARD_FILE) self.assertEqual(tmp, ', '.join(nameservers)) + # Test 'negative trust anchor' flag for the second domain only + if domain == domains[1]: + self.assertIn(f'addNTA("{domain}", "static")', hosts_conf) + # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': unittest.main() + diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index d6eb76d91..5101c1e79 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -17,14 +17,17 @@ import os from sys import exit -from copy import deepcopy from vyos.config import Config +from vyos.configdict import dict_merge from vyos.hostsd_client import Client as hostsd_client -from vyos import ConfigError -from vyos.util import call, chown +from vyos.util import call +from vyos.util import chown +from vyos.util import vyos_dict_search from vyos.template import render +from vyos.xml import defaults +from vyos import ConfigError from vyos import airbag airbag.enable() @@ -35,116 +38,63 @@ pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.l pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf' pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf' -default_config_data = { - 'allow_from': [], - 'cache_size': 10000, - 'export_hosts_file': 'yes', - 'listen_address': [], - 'name_servers': [], - 'negative_ttl': 3600, - 'system': False, - 'domains': {}, - 'dnssec': 'process-no-validate', - 'dhcp_interfaces': [] -} - hostsd_tag = 'static' -def get_config(conf): - dns = deepcopy(default_config_data) +def get_config(config=None): + if config: + conf = config + else: + conf = Config() base = ['service', 'dns', 'forwarding'] - if not conf.exists(base): return None - conf.set_level(base) - - if conf.exists(['allow-from']): - dns['allow_from'] = conf.return_values(['allow-from']) - - if conf.exists(['cache-size']): - cache_size = conf.return_value(['cache-size']) - dns['cache_size'] = cache_size - - if conf.exists('negative-ttl'): - negative_ttl = conf.return_value(['negative-ttl']) - dns['negative_ttl'] = negative_ttl - - if conf.exists(['domain']): - for domain in conf.list_nodes(['domain']): - conf.set_level(base + ['domain', domain]) - entry = { - 'nslist': bracketize_ipv6_addrs(conf.return_values(['server'])), - 'addNTA': conf.exists(['addnta']), - 'recursion-desired': conf.exists(['recursion-desired']) - } - dns['domains'][domain] = entry - - conf.set_level(base) + dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + # We have gathered the dict representation of the CLI, but there are default + # options which we need to update into the dictionary retrived. + default_values = defaults(base) + dns = dict_merge(default_values, dns) - if conf.exists(['ignore-hosts-file']): - dns['export_hosts_file'] = "no" + # some additions to the default dictionary + if 'system' in dns: + base_nameservers = ['system', 'name-server'] + if conf.exists(base_nameservers): + dns.update({'system_name_server': conf.return_values(base_nameservers)}) - if conf.exists(['name-server']): - dns['name_servers'] = bracketize_ipv6_addrs( - conf.return_values(['name-server'])) - - if conf.exists(['system']): - dns['system'] = True - - if conf.exists(['listen-address']): - dns['listen_address'] = conf.return_values(['listen-address']) - - if conf.exists(['dnssec']): - dns['dnssec'] = conf.return_value(['dnssec']) - - if conf.exists(['dhcp']): - dns['dhcp_interfaces'] = conf.return_values(['dhcp']) + base_nameservers_dhcp = ['system', 'name-servers-dhcp'] + if conf.exists(base_nameservers_dhcp): + dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)}) return dns -def bracketize_ipv6_addrs(addrs): - """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched.""" - return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs] - -def verify(conf, dns): +def verify(dns): # bail out early - looks like removal from running config - if dns is None: + if not dns: return None - if not dns['listen_address']: - raise ConfigError( - "Error: DNS forwarding requires a listen-address") - - if not dns['allow_from']: - raise ConfigError( - "Error: DNS forwarding requires an allow-from network") - - if dns['domains']: - for domain in dns['domains']: - if not dns['domains'][domain]['nslist']: - raise ConfigError(( - f'Error: No server configured for domain {domain}')) - - no_system_nameservers = False - conf.set_level([]) - if dns['system'] and not ( - conf.exists(['system', 'name-server']) or - conf.exists(['system', 'name-servers-dhcp']) ): - no_system_nameservers = True - print(("DNS forwarding warning: No 'system name-server' or " - "'system name-servers-dhcp' set\n")) - - if (no_system_nameservers or not dns['system']) and not ( - dns['name_servers'] or dns['dhcp_interfaces']): - print(("DNS forwarding warning: No 'dhcp', 'name-server' or 'system' " - "nameservers set. Forwarding will operate as a recursor.\n")) + if 'listen_address' not in dns: + raise ConfigError('DNS forwarding requires a listen-address') + + if 'allow_from' not in dns: + raise ConfigError('DNS forwarding requires an allow-from network') + + # we can not use vyos_dict_search() when testing for domain servers + # as a domain will contains dot's which is out dictionary delimiter. + if 'domain' in dns: + for domain in dns['domain']: + if 'server' not in dns['domain'][domain]: + raise ConfigError(f'No server configured for domain {domain}!') + + if 'system' in dns: + if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns): + print("Warning: No 'system name-server' or 'system " \ + "name-servers-dhcp' configured") return None def generate(dns): # bail out early - looks like removal from running config - if dns is None: + if not dns: return None render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl', @@ -154,17 +104,18 @@ def generate(dns): dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group) # if vyos-hostsd didn't create its files yet, create them (empty) - for f in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: - with open(f, 'a'): + for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]: + with open(file, 'a'): pass - chown(f, user=pdns_rec_user, group=pdns_rec_group) + chown(file, user=pdns_rec_user, group=pdns_rec_group) return None def apply(dns): - if dns is None: + if not dns: # DNS forwarding is removed in the commit - call("systemctl stop pdns-recursor.service") + call('systemctl stop pdns-recursor.service') + if os.path.isfile(pdns_rec_config_file): os.unlink(pdns_rec_config_file) else: @@ -174,8 +125,8 @@ def apply(dns): # add static nameservers to hostsd so they can be joined with other # sources hc.delete_name_servers([hostsd_tag]) - if dns['name_servers']: - hc.add_name_servers({hostsd_tag: dns['name_servers']}) + if 'name_server' in dns: + hc.add_name_servers({hostsd_tag: dns['name_server']}) # delete all nameserver tags hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor()) @@ -184,32 +135,33 @@ def apply(dns): # our own tag (static) hc.add_name_server_tags_recursor([hostsd_tag]) - if dns['system']: + if 'system' in dns: hc.add_name_server_tags_recursor(['system']) else: hc.delete_name_server_tags_recursor(['system']) # add dhcp nameserver tags for configured interfaces - for intf in dns['dhcp_interfaces']: - hc.add_name_server_tags_recursor(['dhcp-' + intf, 'dhcpv6-' + intf ]) + if 'system_name_server_dhcp' in dns: + for interface in dns['system_name_server_dhcp']: + hc.add_name_server_tags_recursor(['dhcp-' + interface, + 'dhcpv6-' + interface ]) # hostsd will generate the forward-zones file # the list and keys() are required as get returns a dict, not list hc.delete_forward_zones(list(hc.get_forward_zones().keys())) - if dns['domains']: - hc.add_forward_zones(dns['domains']) + if 'domain' in dns: + hc.add_forward_zones(dns['domain']) # call hostsd to generate forward-zones and its lua-config-file hc.apply() ### finally (re)start pdns-recursor - call("systemctl restart pdns-recursor.service") + call('systemctl restart pdns-recursor.service') if __name__ == '__main__': try: - conf = Config() - c = get_config(conf) - verify(conf, c) + c = get_config() + verify(c) generate(c) apply(c) except ConfigError as e: diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd index 0079f7e5c..59dbeda17 100755 --- a/src/services/vyos-hostsd +++ b/src/services/vyos-hostsd @@ -107,16 +107,17 @@ # ### forward_zones ## Additional zones added to pdns-recursor forward-zones-file. -## If recursion-desired is true, '+' will be prepended to the zone line. -## If addNTA is true, a NTA will be added via lua-config-file. +## If recursion_desired is true, '+' will be prepended to the zone line. +## If addnta is true, a NTA (Negative Trust Anchor) will be added via +## lua-config-file. # # { 'type': 'forward_zones', # 'op': 'add', # 'data': { # '': { -# 'nslist': ['', ...], -# 'addNTA': , -# 'recursion-desired': +# 'server': ['', ...], +# 'addnta': , +# 'recursion_desired': # } # ... # } @@ -305,12 +306,12 @@ tag_regex_schema = op_type_schema.extend({ forward_zone_add_schema = op_type_schema.extend({ 'data': { str: { - 'nslist': [str], - 'addNTA': bool, - 'recursion-desired': bool + 'server': [str], + 'addnta': Any({}, None), + 'recursion_desired': Any({}, None), } } - }, required=True) + }, required=False) hosts_add_schema = op_type_schema.extend({ 'data': { @@ -586,7 +587,7 @@ if __name__ == '__main__': context = zmq.Context() socket = context.socket(zmq.REP) - + # Set the right permissions on the socket, then change it back o_mask = os.umask(0o007) socket.bind(SOCKET_PATH) diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client index 48ebc83f7..d4d38315a 100755 --- a/src/utils/vyos-hostsd-client +++ b/src/utils/vyos-hostsd-client @@ -99,9 +99,9 @@ try: raise ValueError("--nameservers is required for this operation") client.add_forward_zones( { args.add_forward_zone: { - 'nslist': args.nameservers, - 'addNTA': args.addnta, - 'recursion-desired': args.recursion_desired + 'server': args.nameservers, + 'addnta': args.addnta, + 'recursion_desired': args.recursion_desired } }) elif args.delete_forward_zones: -- cgit v1.2.3