summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/dns_forwarding.py62
-rwxr-xr-xsrc/conf_mode/host_name.py196
-rwxr-xr-xsrc/conf_mode/interface-bridge.py9
-rwxr-xr-xsrc/conf_mode/interface-dummy.py113
-rwxr-xr-xsrc/conf_mode/interface-loopback.py102
-rwxr-xr-xsrc/conf_mode/interface-openvpn.py31
6 files changed, 263 insertions, 250 deletions
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index 3ca77adee..9e81c7294 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,10 +83,10 @@ dnssec={{ dnssec }}
"""
default_config_data = {
+ 'allow_from': [],
'cache_size': 10000,
'export_hosts_file': 'yes',
'listen_on': [],
- 'interfaces': [],
'name_servers': [],
'negative_ttl': 3600,
'domains': [],
@@ -121,6 +121,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
@@ -164,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 = []
@@ -216,12 +179,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 +192,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 +204,6 @@ def verify(dns):
return None
-
def generate(dns):
# bail out early - looks like removal from running config
if dns is None:
@@ -251,16 +215,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/conf_mode/host_name.py b/src/conf_mode/host_name.py
index 2fad57db6..bb1ec9597 100755
--- a/src/conf_mode/host_name.py
+++ b/src/conf_mode/host_name.py
@@ -30,57 +30,12 @@ import argparse
import jinja2
import vyos.util
+import vyos.hostsd_client
from vyos.config import Config
from vyos import ConfigError
-parser = argparse.ArgumentParser()
-parser.add_argument("--dhclient", action="store_true",
- help="Started from dhclient-script")
-
-config_file_hosts = '/etc/hosts'
-config_file_resolv = '/etc/resolv.conf'
-
-config_tmpl_hosts = """
-### Autogenerated by host_name.py ###
-127.0.0.1 localhost
-127.0.1.1 {{ hostname }}{% if domain_name %}.{{ domain_name }} {{ hostname }}{% endif %}
-
-# The following lines are desirable for IPv6 capable hosts
-::1 localhost ip6-localhost ip6-loopback
-fe00::0 ip6-localnet
-ff00::0 ip6-mcastprefix
-ff02::1 ip6-allnodes
-ff02::2 ip6-allrouters
-
-# static hostname mappings
-{%- if static_host_mapping['hostnames'] %}
-{% for hn in static_host_mapping['hostnames'] -%}
-{{static_host_mapping['hostnames'][hn]['ipaddr']}}\t{{static_host_mapping['hostnames'][hn]['alias']}}\t{{hn}}
-{% endfor -%}
-{%- endif %}
-
-### modifications from other scripts should be added below
-
-"""
-
-config_tmpl_resolv = """
-### Autogenerated by host_name.py ###
-{% for ns in nameserver -%}
-nameserver {{ ns }}
-{% endfor -%}
-
-{%- if domain_name %}
-domain {{ domain_name }}
-{%- endif %}
-
-{%- if domain_search %}
-search {{ domain_search | join(" ") }}
-{%- endif %}
-
-"""
-
default_config_data = {
'hostname': 'vyos',
'domain_name': '',
@@ -89,32 +44,10 @@ default_config_data = {
'no_dhcp_ns': False
}
-# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX!
-def get_resolvers(file):
- resolv = {}
- try:
- with open(file, 'r') as resolvconf:
- lines = [line.split('#', 1)[0].rstrip()
- for line in resolvconf.readlines()]
- resolvers = [line.split()[1]
- for line in lines if 'nameserver' in line]
- domains = [line.split()[1] for line in lines if 'search' in line]
- resolv['resolvers'] = resolvers
- resolv['domains'] = domains
- return resolv
- except IOError:
- return []
-
-
-def get_config(arguments):
+def get_config():
conf = Config()
hosts = copy.deepcopy(default_config_data)
- if arguments.dhclient:
- conf.exists = conf.exists_effective
- conf.return_value = conf.return_effective_value
- conf.return_values = conf.return_effective_values
-
if conf.exists("system host-name"):
hosts['hostname'] = conf.return_value("system host-name")
# This may happen if the config is not loaded yet,
@@ -136,19 +69,15 @@ def get_config(arguments):
hosts['no_dhcp_ns'] = conf.exists('system disable-dhcp-nameservers')
# system static-host-mapping
- hosts['static_host_mapping'] = {'hostnames': {}}
+ hosts['static_host_mapping'] = []
if conf.exists('system static-host-mapping host-name'):
for hn in conf.list_nodes('system static-host-mapping host-name'):
- hosts['static_host_mapping']['hostnames'][hn] = {
- 'ipaddr': conf.return_value('system static-host-mapping host-name ' + hn + ' inet'),
- 'alias': ''
- }
-
- if conf.exists('system static-host-mapping host-name ' + hn + ' alias'):
- a = conf.return_values(
- 'system static-host-mapping host-name ' + hn + ' alias')
- hosts['static_host_mapping']['hostnames'][hn]['alias'] = " ".join(a)
+ mapping = {}
+ mapping['host'] = hn
+ mapping['address'] = conf.return_value('system static-host-mapping host-name {0} inet'.format(hn))
+ mapping['aliases'] = conf.return_values('system static-host-mapping host-name {0} alias'.format(hn))
+ hosts['static_host_mapping'].append(mapping)
return hosts
@@ -180,83 +109,43 @@ def verify(config):
'The search list is currently limited to 256 characters')
# static mappings alias hostname
- if config['static_host_mapping']['hostnames']:
- for hn in config['static_host_mapping']['hostnames']:
- if not config['static_host_mapping']['hostnames'][hn]['ipaddr']:
- raise ConfigError('IP address required for ' + hn)
- for hn_alias in config['static_host_mapping']['hostnames'][hn]['alias'].split(' '):
- if not hostname_regex.match(hn_alias) and len(hn_alias) != 0:
- raise ConfigError('Invalid hostname alias ' + hn_alias)
+ if config['static_host_mapping']:
+ for m in config['static_host_mapping']:
+ if not m['address']:
+ raise ConfigError('IP address required for ' + m['host'])
+ for a in m['aliases']:
+ if not hostname_regex.match(a) and len(a) != 0:
+ raise ConfigError('Invalid alias \'{0}\' in mapping {1}'.format(a, m['host']))
return None
def generate(config):
+ pass
+
+def apply(config):
if config is None:
return None
- # If "system disable-dhcp-nameservers" is __configured__ all DNS resolvers
- # received via dhclient should not be added into the final 'resolv.conf'.
- #
- # We iterate over every resolver file and retrieve the received nameservers
- # for later adjustment of the system nameservers
- dhcp_ns = []
- dhcp_sd = []
- for file in glob.glob('/etc/resolv.conf.dhclient-new*'):
- for key, value in get_resolvers(file).items():
- ns = [r for r in value if key == 'resolvers']
- dhcp_ns.extend(ns)
- sd = [d for d in value if key == 'domains']
- dhcp_sd.extend(sd)
-
- if not config['no_dhcp_ns']:
- config['nameserver'] += dhcp_ns
- config['domain_search'] += dhcp_sd
-
- # Prune duplicate values
- # Not order preserving, but then when multiple DHCP clients are used,
- # there can't be guarantees about the order anyway
- dhcp_ns = list(set(dhcp_ns))
- dhcp_sd = list(set(dhcp_sd))
-
- # We have third party scripts altering /etc/hosts, too.
- # One example are the DHCP hostname update scripts thus we need to cache in
- # every modification first - so changing domain-name, domain-search or hostname
- # during runtime works
- old_hosts = ""
- with open(config_file_hosts, 'r') as f:
- # Skips text before the beginning of our marker.
- # NOTE: Marker __MUST__ match the one specified in config_tmpl_hosts
- for line in f:
- if line.strip() == '### modifications from other scripts should be added below':
- break
-
- for line in f:
- # This additional line.strip() filters empty lines
- if line.strip():
- old_hosts += line
-
- # Add an additional newline
- old_hosts += '\n'
-
- tmpl = jinja2.Template(config_tmpl_hosts)
- config_text = tmpl.render(config)
-
- with open(config_file_hosts, 'w') as f:
- f.write(config_text)
- f.write(old_hosts)
-
- tmpl = jinja2.Template(config_tmpl_resolv)
- config_text = tmpl.render(config)
- with open(config_file_resolv, 'w') as f:
- f.write(config_text)
+ ## Send the updated data to vyos-hostsd
- return None
+ # vyos-hostsd uses "tags" to identify data sources
+ tag = "static"
+ try:
+ client = vyos.hostsd_client.Client()
-def apply(config):
- if config is None:
- return None
+ client.set_host_name(config['hostname'], config['domain_name'], config['domain_search'])
+
+ client.delete_name_servers(tag)
+ client.add_name_servers(tag, config['nameserver'])
+
+ client.delete_hosts(tag)
+ client.add_hosts(tag, config['static_host_mapping'])
+ except vyos.hostsd_client.VyOSHostsdError as e:
+ raise ConfigError(str(e))
+
+ ## Actually update the hostname -- vyos-hostsd doesn't do that
# No domain name -- the Debian way.
hostname_new = config['hostname']
@@ -283,22 +172,9 @@ def apply(config):
if __name__ == '__main__':
- args = parser.parse_args()
-
- if args.dhclient:
- # There's a big chance it was triggered by a commit still in progress
- # so we need to wait until the new values are in the running config
- vyos.util.wait_for_commit_lock()
-
-
try:
- c = get_config(args)
- # If it's called from dhclient, then either:
- # a) verification was already done at commit time
- # b) it's run on an unconfigured system, e.g. by cloud-init
- # Therefore, verification is either redundant or useless
- if not args.dhclient:
- verify(c)
+ c = get_config()
+ verify(c)
generate(c)
apply(c)
except ConfigError as e:
diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py
index 543349e7b..65b5c4066 100755
--- a/src/conf_mode/interface-bridge.py
+++ b/src/conf_mode/interface-bridge.py
@@ -178,9 +178,6 @@ def get_config():
return bridge
def verify(bridge):
- if bridge is None:
- return None
-
conf = Config()
for br in conf.list_nodes('interfaces bridge'):
# it makes no sense to verify ourself in this case
@@ -195,15 +192,9 @@ def verify(bridge):
return None
def generate(bridge):
- if bridge is None:
- return None
-
return None
def apply(bridge):
- if bridge is None:
- return None
-
cmd = ''
if bridge['deleted']:
# bridges need to be shutdown first
diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interface-dummy.py
new file mode 100755
index 000000000..ff9d57c89
--- /dev/null
+++ b/src/conf_mode/interface-dummy.py
@@ -0,0 +1,113 @@
+#!/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 <http://www.gnu.org/licenses/>.
+#
+#
+
+import os
+import sys
+import copy
+
+import vyos.configinterface as VyIfconfig
+
+from vyos.config import Config
+from vyos import ConfigError
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'deleted': False,
+ 'description': '',
+ 'disable': False,
+ 'intf': ''
+}
+
+def diff(first, second):
+ second = set(second)
+ return [item for item in first if item not in second]
+
+def get_config():
+ dummy = copy.deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ dummy['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # Check if interface has been removed
+ if not conf.exists('interfaces dummy ' + dummy['intf']):
+ dummy['deleted'] = True
+ return dummy
+
+ # set new configuration level
+ conf.set_level('interfaces dummy ' + dummy['intf'])
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ dummy['address'] = conf.return_values('address')
+
+ # retrieve interface description
+ if conf.exists('description'):
+ dummy['description'] = conf.return_value('description')
+
+ # Disable this interface
+ if conf.exists('disable'):
+ dummy['disable'] = True
+
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed from the interface
+ eff_addr = conf.return_effective_values('address')
+ act_addr = conf.return_values('address')
+ dummy['address_remove'] = diff(eff_addr, act_addr)
+
+ return dummy
+
+def verify(dummy):
+ return None
+
+def generate(dummy):
+ return None
+
+def apply(dummy):
+ # Remove dummy interface
+ if dummy['deleted']:
+ VyIfconfig.remove_interface(dummy['intf'])
+ else:
+ # Interface will only be added if it yet does not exist
+ VyIfconfig.add_interface('dummy', dummy['intf'])
+
+ # update interface description used e.g. within SNMP
+ VyIfconfig.set_description(dummy['intf'], dummy['description'])
+
+ # Configure interface address(es)
+ for addr in dummy['address_remove']:
+ VyIfconfig.remove_interface_address(dummy['intf'], addr)
+
+ for addr in dummy['address']:
+ VyIfconfig.add_interface_address(dummy['intf'], addr)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/interface-loopback.py b/src/conf_mode/interface-loopback.py
new file mode 100755
index 000000000..445a9af64
--- /dev/null
+++ b/src/conf_mode/interface-loopback.py
@@ -0,0 +1,102 @@
+#!/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 <http://www.gnu.org/licenses/>.
+#
+#
+
+import os
+import sys
+import copy
+
+import vyos.configinterface as VyIfconfig
+
+from vyos.config import Config
+from vyos import ConfigError
+
+default_config_data = {
+ 'address': [],
+ 'address_remove': [],
+ 'deleted': False,
+ 'description': '',
+}
+
+def diff(first, second):
+ second = set(second)
+ return [item for item in first if item not in second]
+
+def get_config():
+ loopback = copy.deepcopy(default_config_data)
+ conf = Config()
+
+ # determine tagNode instance
+ try:
+ loopback['intf'] = os.environ['VYOS_TAGNODE_VALUE']
+ except KeyError as E:
+ print("Interface not specified")
+
+ # Check if interface has been removed
+ if not conf.exists('interfaces loopback ' + loopback['intf']):
+ loopback['deleted'] = True
+
+ # set new configuration level
+ conf.set_level('interfaces loopback ' + loopback['intf'])
+
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ loopback['address'] = conf.return_values('address')
+
+ # retrieve interface description
+ if conf.exists('description'):
+ loopback['description'] = conf.return_value('description')
+
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed from the interface
+ eff_addr = conf.return_effective_values('address')
+ act_addr = conf.return_values('address')
+ loopback['address_remove'] = diff(eff_addr, act_addr)
+
+ return loopback
+
+def verify(loopback):
+ return None
+
+def generate(loopback):
+ return None
+
+def apply(loopback):
+ # Remove loopback interface
+ if not loopback['deleted']:
+ # update interface description used e.g. within SNMP
+ VyIfconfig.set_description(loopback['intf'], loopback['description'])
+
+ # Configure interface address(es)
+ for addr in loopback['address']:
+ VyIfconfig.add_interface_address(loopback['intf'], addr)
+
+ # Remove interface address(es)
+ for addr in loopback['address_remove']:
+ VyIfconfig.remove_interface_address(loopback['intf'], addr)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interface-openvpn.py
index e4bde7bb0..a8313378b 100755
--- a/src/conf_mode/interface-openvpn.py
+++ b/src/conf_mode/interface-openvpn.py
@@ -219,10 +219,6 @@ client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }}
{% for option in options -%}
{{ option }}
{% endfor -%}
-
-{%- if server_2fa_authy_key %}
-plugin /usr/lib/authy/authy-openvpn.so https://api.authy.com/protected/json {{ server_2fa_authy_key }} nopam
-{% endif %}
"""
client_tmpl = """
@@ -269,8 +265,6 @@ default_config_data = {
'remote_address': '',
'remote_host': [],
'remote_port': '',
- 'server_2fa_authy_key': '',
- 'server_2fa_authy': [],
'client': [],
'server_domain': '',
'server_max_conn': '',
@@ -453,31 +447,6 @@ def get_config():
if conf.exists('replace-default-route local'):
openvpn['redirect_gateway'] = 'local def1'
- # Two Factor Authentication providers
- # currently limited to authy
- if conf.exists('2-factor-authentication authy api-key'):
- openvpn['server_2fa_authy_key'] = conf.return_value('2-factor-authentication authy api-key')
-
- # Authy users (must be email address)
- for user in conf.list_nodes('server 2-factor-authentication authy user'):
- # set configuration level
- conf.set_level('interfaces openvpn ' + openvpn['intf'] + ' 2-factor-authentication authy user ' + user)
- data = {
- 'user': user,
- 'country_code': '',
- 'mobile_number': ''
- }
-
- # Country calling codes
- if conf.exists('country-calling-code'):
- data['country_code'] = conf.return_value('country-calling-code')
-
- # Mobile phone number
- if conf.exists('phone-number'):
- data['mobile_number'] = conf.return_value('phone-number')
-
- openvpn['server_2fa_authy'].append(data)
-
# Topology for clients
if conf.exists('server topology'):
openvpn['server_topology'] = conf.return_value('server topology')