From 4074315da3b651d1c629430cd3bd4e209a80c6bc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 20 Aug 2019 10:29:33 +0200 Subject: vyos.configtree: add help for set method --- python/vyos/configtree.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index a812b62ec..8832a5a63 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -185,6 +185,14 @@ class ConfigTree(object): return self.__to_commands(self.__config).decode() def set(self, path, value=None, replace=True): + """Set new entry in VyOS configuration. + path: configuration path e.g. 'system dns forwarding listen-address' + value: value to be added to node, e.g. '172.18.254.201' + replace: True: current occurance will be replaced + False: new value will be appended to current occurances - use + this for adding values to a multi node + """ + check_path(path) path_str = " ".join(map(str, path)).encode() -- cgit v1.2.3 From dc0f641956d002fa8588ef8d1213791cf36e92f2 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 20 Aug 2019 11:50:55 +0200 Subject: powerdns: T1524: support setting allow-from network Netmasks (both IPv4 and IPv6) that are allowed to use the server. The default allows access only from RFC 1918 private IP addresses. Due to the aggressive nature of the internet these days, it is highly recommended to not open up the recursor for the entire internet. Questions from IP addresses not listed here are ignored and do not get an answer. https://docs.powerdns.com/recursor/settings.html#allow-from Imagine an ISP network with non RFC1918 IP adresses - they can't make use of PowerDNS recursor. As of now VyOS hat allow-from set to 0.0.0.0/0 and ::/0 which created an open resolver. If there is no allow-from statement a config-migrator will add the appropriate nodes to the configuration, resulting in: service { dns { forwarding { allow-from 0.0.0.0/0 allow-from ::/0 cache-size 0 ignore-hosts-file listen-address 192.0.2.1 } } } --- interface-definitions/dns-forwarding.xml | 17 ++++++++++ src/conf_mode/dns_forwarding.py | 21 ++++++------ src/migration-scripts/dns-forwarding/0-to-1 | 50 +++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) create mode 100755 src/migration-scripts/dns-forwarding/0-to-1 diff --git a/interface-definitions/dns-forwarding.xml b/interface-definitions/dns-forwarding.xml index 56820608c..08a221ebe 100644 --- a/interface-definitions/dns-forwarding.xml +++ b/interface-definitions/dns-forwarding.xml @@ -97,6 +97,23 @@ + + + Networks allowed to query this server + + ipv4net + IP address and prefix length + + + ipv6net + IPv6 address and prefix length + + + + + + + Addresses to listen for DNS queries [REQUIRED] diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 3ca77adee..86683e72c 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -44,7 +44,7 @@ config_tmpl = """ # Non-configurable defaults daemon=yes threads=1 -allow-from=0.0.0.0/0, ::/0 +allow-from={{ allow_from | join(',') }} log-common-errors=yes non-local-bind=yes query-local-address=0.0.0.0 @@ -83,6 +83,7 @@ dnssec={{ dnssec }} """ default_config_data = { + 'allow_from': [], 'cache_size': 10000, 'export_hosts_file': 'yes', 'listen_on': [], @@ -121,6 +122,9 @@ def get_config(arguments): conf.set_level('service dns forwarding') + 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 @@ -216,12 +220,10 @@ def get_config(arguments): 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(dns): # bail out early - looks like removal from running config if dns is None: @@ -231,6 +233,10 @@ def verify(dns): raise ConfigError( "Error: DNS forwarding requires either a listen-address (preferred) or a listen-on option") + 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 domain['servers']: @@ -239,7 +245,6 @@ def verify(dns): return None - def generate(dns): # bail out early - looks like removal from running config if dns is None: @@ -251,16 +256,14 @@ def generate(dns): f.write(config_text) return None - def apply(dns): - if dns is not None: - os.system("systemctl restart pdns-recursor") - else: + if dns is None: # DNS forwarding is removed in the commit os.system("systemctl stop pdns-recursor") if os.path.isfile(config_file): os.unlink(config_file) - + else: + os.system("systemctl restart pdns-recursor") if __name__ == '__main__': args = parser.parse_args() diff --git a/src/migration-scripts/dns-forwarding/0-to-1 b/src/migration-scripts/dns-forwarding/0-to-1 new file mode 100755 index 000000000..6e8720eef --- /dev/null +++ b/src/migration-scripts/dns-forwarding/0-to-1 @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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 . +# + +# This migration script will check if there is a allow-from directive configured +# for the dns forwarding service - if not, the node will be created with the old +# default values of 0.0.0.0/0 and ::/0 + +import sys +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +base = ['service', 'dns', 'forwarding'] +if not config.exists(base): + # Nothing to do + sys.exit(0) +else: + if not config.exists(base + ['allow-from']): + config.set(base + ['allow-from'], value='0.0.0.0/0', replace=False) + config.set(base + ['allow-from'], value='::/0', replace=False) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) -- cgit v1.2.3 From 34e69abedb61b293dee097e67ecda45457da1b75 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 20 Aug 2019 11:54:12 +0200 Subject: vyos.interfaces: T1595: add method to query for interface type As of now we only could list the available interfaces for a given interface type. There was no reverse mapping available which told us that interface eth0.201 is an ethernet interface or vtun0 is openvpn. --- python/vyos/interfaces.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/python/vyos/interfaces.py b/python/vyos/interfaces.py index 2e8ee4feb..d69ce9d04 100644 --- a/python/vyos/interfaces.py +++ b/python/vyos/interfaces.py @@ -43,3 +43,14 @@ def list_interfaces_of_type(typ): else: r = re.compile('^{0}\d+'.format(types_data[typ])) return list(filter(lambda i: re.match(r, i), all_intfs)) + +def get_type_of_interface(intf): + with open(intf_type_data_file, 'r') as f: + types_data = json.load(f) + + for key,val in types_data.items(): + r = re.compile('^{0}\d+'.format(val)) + if re.match(r, intf): + return key + + raise ValueError("No type found for interface name: {0}".format(intf)) -- cgit v1.2.3 From 3f225b56f576d30bd163f975c821e8baf2be6d28 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 20 Aug 2019 11:56:08 +0200 Subject: powerdns: T1595: add config migrator to remove 'listen-on' --- src/migration-scripts/dns-forwarding/1-to-2 | 78 +++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100755 src/migration-scripts/dns-forwarding/1-to-2 diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2 new file mode 100755 index 000000000..31ba5573f --- /dev/null +++ b/src/migration-scripts/dns-forwarding/1-to-2 @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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 . +# + +# This migration script will remove the deprecated 'listen-on' statement +# from the dns forwarding service and will add the corresponding +# listen-address nodes instead. This is required as PowerDNS can only listen +# on interface addresses and not on interface names. + +import sys + +from ipaddress import ip_interface +from vyos.configtree import ConfigTree +from vyos.interfaces import get_type_of_interface + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) + +base = ['service', 'dns', 'forwarding'] +if not config.exists(base): + # Nothing to do + sys.exit(0) +else: + if config.exists(base + ['listen-on']): + listen_intf = config.return_values(base + ['listen-on']) + # Delete node with abandoned command + config.delete(base + ['listen-on']) + + # retrieve interface addresses for every configured listen-on interface + listen_addr = [] + for intf in listen_intf: + # we need to treat vif and vif-s interfaces differently, + # both "real interfaces" use dots for vlan identifiers - those + # need to be exchanged with vif and vif-s identifiers + if intf.count('.') == 1: + # this is a regular VLAN interface + intf = intf.split('.')[0] + ' vif ' + intf.split('.')[1] + elif intf.count('.') == 2: + # this is a QinQ VLAN interface + intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2] + + path = ['interfaces', get_type_of_interface(intf), intf, 'address'] + + # retrieve corresponding interface addresses in CIDR format + # those need to be converted in pure IP addresses without network information + for addr in config.return_values(path): + listen_addr.append( ip_interface(addr).ip ) + + for addr in listen_addr: + config.set(base + ['listen-address'], value=addr, replace=False) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) -- cgit v1.2.3 From dbdd50e96f5af8f59d884f03df1cdeed9bac39d1 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 20 Aug 2019 12:02:49 +0200 Subject: powerdns: T1595: remove 'listen-on' CLI option --- interface-definitions/dns-forwarding.xml | 9 ------- src/conf_mode/dns_forwarding.py | 41 -------------------------------- 2 files changed, 50 deletions(-) diff --git a/interface-definitions/dns-forwarding.xml b/interface-definitions/dns-forwarding.xml index 08a221ebe..a88c174e3 100644 --- a/interface-definitions/dns-forwarding.xml +++ b/interface-definitions/dns-forwarding.xml @@ -132,15 +132,6 @@ - - - Interface to listen for DNS queries [DEPRECATED] - - - - - - Maximum amount of time negative entries are cached diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py index 86683e72c..9e81c7294 100755 --- a/src/conf_mode/dns_forwarding.py +++ b/src/conf_mode/dns_forwarding.py @@ -87,7 +87,6 @@ default_config_data = { 'cache_size': 10000, 'export_hosts_file': 'yes', 'listen_on': [], - 'interfaces': [], 'name_servers': [], 'negative_ttl': 3600, 'domains': [], @@ -168,46 +167,6 @@ def get_config(arguments): if conf.exists('dnssec'): dns['dnssec'] = conf.return_value('dnssec') - ## Hacks and tricks - - # The old VyOS syntax that comes from dnsmasq was "listen-on $interface". - # pdns wants addresses instead, so we emulate it by looking up all addresses - # of a given interface and writing them to the config - if conf.exists('listen-on'): - print("WARNING: since VyOS 1.2.0, \"service dns forwarding listen-on\" is a limited compatibility option.") - print("It will only make DNS forwarder listen on addresses assigned to the interface at the time of commit") - print("which means it will NOT work properly with VRRP/clustering or addresses received from DHCP.") - print("Please reconfigure your system with \"service dns forwarding listen-address\" instead.") - - interfaces = conf.return_values('listen-on') - - listen4 = [] - listen6 = [] - for interface in interfaces: - try: - addrs = netifaces.ifaddresses(interface) - except ValueError: - print( - "WARNING: interface {0} does not exist".format(interface)) - continue - - if netifaces.AF_INET in addrs.keys(): - for ip4 in addrs[netifaces.AF_INET]: - listen4.append(ip4['addr']) - - if netifaces.AF_INET6 in addrs.keys(): - for ip6 in addrs[netifaces.AF_INET6]: - listen6.append(ip6['addr']) - - if (not listen4) and (not (listen6)): - print( - "WARNING: interface {0} has no configured addresses".format(interface)) - - dns['listen_on'] = dns['listen_on'] + listen4 + listen6 - - # Save interfaces in the dict for the reference - dns['interfaces'] = interfaces - # Add name servers received from DHCP if conf.exists('dhcp'): interfaces = [] -- cgit v1.2.3