diff options
author | Christian Poessinger <christian@poessinger.com> | 2019-09-28 14:03:20 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2019-09-28 14:03:20 +0200 |
commit | e541ffc4f34ced045b89bd039f391d1322ff5f00 (patch) | |
tree | 83b329953d9b277238237aac9e5638a9f2ea149f | |
parent | ce8cc3514acdc1f2c06bcd3ef7f3ef32561df9c8 (diff) | |
parent | 6ac5271e93d06712f6e318d2f6b96280ae16f040 (diff) | |
download | vyos-1x-e541ffc4f34ced045b89bd039f391d1322ff5f00.tar.gz vyos-1x-e541ffc4f34ced045b89bd039f391d1322ff5f00.zip |
Merge branch 'current' of github.com:vyos/vyos-1x into equuleus
* 'current' of github.com:vyos/vyos-1x:
T1694: delete the now broken tests for NTP.
Jenkins: assume dependencies are available in DOcker container
OpenVPN: T1512: always enable compat-names option
Interface: T1695: Syntax error in interface-dummy.py - Missing colon
T1694 NTPd: Do not listen on all interfaces by default
openvpn: T1691: interface is not always created - take care when setting alias
openvpn: T1691: add artifical abort when waiting on interface
ipoe-server: T1690 - restart op-mode command for service ipoe-server
ipoe-server: T1692 - ipoe-server verify function error
pppoe-server: T1690 - restart op-mode commands for pppoe-server
T1685 Adding ethernet valueHelp for vif,vif-s,vif-c
wireguard: T1681 - code cleanup and maintenace.
-rw-r--r-- | interface-definitions/interfaces-ethernet.xml | 12 | ||||
-rw-r--r-- | op-mode-definitions/ipoe-server.xml | 10 | ||||
-rw-r--r-- | op-mode-definitions/pppoe-server.xml | 10 | ||||
-rwxr-xr-x | src/conf_mode/interface-dummy.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/interface-openvpn.py | 34 | ||||
-rwxr-xr-x | src/conf_mode/interface-wireguard.py | 335 | ||||
-rwxr-xr-x | src/conf_mode/ipoe_server.py | 3 | ||||
-rwxr-xr-x | src/conf_mode/ntp.py | 3 | ||||
-rw-r--r-- | src/tests/test_ntp.py | 259 |
9 files changed, 238 insertions, 430 deletions
diff --git a/interface-definitions/interfaces-ethernet.xml b/interface-definitions/interfaces-ethernet.xml index 9244f3b5f..e4a56b216 100644 --- a/interface-definitions/interfaces-ethernet.xml +++ b/interface-definitions/interfaces-ethernet.xml @@ -394,6 +394,10 @@ <tagNode name="vif-s"> <properties> <help>QinQ TAG-S Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>0-4094</format> + <description>QinQ TAG-S Virtual Local Area Network (VLAN) ID</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> @@ -551,6 +555,10 @@ <tagNode name="vif-c"> <properties> <help>QinQ TAG-C Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>0-4094</format> + <description>QinQ TAG-C Virtual Local Area Network (VLAN) ID</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> @@ -692,6 +700,10 @@ <tagNode name="vif"> <properties> <help>Virtual Local Area Network (VLAN) ID</help> + <valueHelp> + <format>0-4094</format> + <description>Virtual Local Area Network (VLAN) ID</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> diff --git a/op-mode-definitions/ipoe-server.xml b/op-mode-definitions/ipoe-server.xml index ea14e9a5c..369ceebea 100644 --- a/op-mode-definitions/ipoe-server.xml +++ b/op-mode-definitions/ipoe-server.xml @@ -23,4 +23,14 @@ </node> </children> </node> + <node name="restart"> + <children> + <leafNode name="ipoe-server"> + <properties> + <help>show ipoe-server status</help> + </properties> + <command>if [ -e /var/run/accel_ipoe.pid ]; then /usr/bin/accel-cmd restart -p 2002; else echo "ipoe-server not running"; fi</command> + </leafNode> + </children> + </node> </interfaceDefinition> diff --git a/op-mode-definitions/pppoe-server.xml b/op-mode-definitions/pppoe-server.xml index a2366c710..1f6f7f844 100644 --- a/op-mode-definitions/pppoe-server.xml +++ b/op-mode-definitions/pppoe-server.xml @@ -29,4 +29,14 @@ </node> </children> </node> + <node name="restart"> + <children> + <leafNode name="pppoe-server"> + <properties> + <help>Restarts pppoe-server</help> + </properties> + <command>if [ -e /var/run/accel_pppoe.pid ]; then /usr/bin/accel-cmd restart -p 2001; else echo "pppoe-server not running"; fi</command> + </leafNode> + </children> + </node> </interfaceDefinition> diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interface-dummy.py index 16b716e61..eb0145f65 100755 --- a/src/conf_mode/interface-dummy.py +++ b/src/conf_mode/interface-dummy.py @@ -98,7 +98,7 @@ def apply(dummy): # disable interface on demand if dummy['disable']: d.set_state('down') - else + else: d.set_state('up') return None diff --git a/src/conf_mode/interface-openvpn.py b/src/conf_mode/interface-openvpn.py index 57d565749..a988e1ab1 100755 --- a/src/conf_mode/interface-openvpn.py +++ b/src/conf_mode/interface-openvpn.py @@ -225,6 +225,20 @@ auth-retry nointeract client-config-dir /opt/vyatta/etc/openvpn/ccd/{{ intf }} {% endif %} +# DEPRECATED This option will be removed in OpenVPN 2.5 +# Until OpenVPN v2.3 the format of the X.509 Subject fields was formatted like this: +# /C=US/L=Somewhere/CN=John Doe/emailAddress=john@example.com In addition the old +# behaviour was to remap any character other than alphanumeric, underscore ('_'), +# dash ('-'), dot ('.'), and slash ('/') to underscore ('_'). The X.509 Subject +# string as returned by the tls_id environmental variable, could additionally +# contain colon (':') or equal ('='). When using the --compat-names option, this +# old formatting and remapping will be re-enabled again. This is purely implemented +# for compatibility reasons when using older plug-ins or scripts which does not +# handle the new formatting or UTF-8 characters. +# +# See https://phabricator.vyos.net/T1512 +compat-names + {% for option in options -%} {{ option }} {% endfor -%} @@ -903,9 +917,25 @@ def apply(openvpn): # better late then sorry ... but we can only set interface alias after # OpenVPN has been launched and created the interface + cnt = 0 while openvpn['intf'] not in interfaces(): - sleep(0.250) # 250ms - Interface(openvpn['intf']).set_alias(openvpn['description']) + # If VPN tunnel can't be established because the peer/server isn't + # (temporarily) available, the vtun interface never becomes registered + # with the kernel, and the commit would hang if there is no bail out + # condition + cnt += 1 + if cnt == 50: + break + + # sleep 250ms + sleep(0.250) + + try: + # we need to catch the exception if the interface is not up due to + # reason stated above + Interface(openvpn['intf']).set_alias(openvpn['description']) + except: + pass return None diff --git a/src/conf_mode/interface-wireguard.py b/src/conf_mode/interface-wireguard.py index 3fd29ad4d..0be8b7b0f 100755 --- a/src/conf_mode/interface-wireguard.py +++ b/src/conf_mode/interface-wireguard.py @@ -13,44 +13,41 @@ # # 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 sys import os import re -import syslog as sl import subprocess +from copy import deepcopy +from netifaces import interfaces -from vyos.config import Config from vyos import ConfigError +from vyos.config import Config +from vyos.configdict import list_diff from vyos.ifconfig import WireGuardIf -try: - ifname = str(os.environ['VYOS_TAGNODE_VALUE']) - intfc = WireGuardIf(ifname) -except KeyError: - print("Interface not specified") - sys.exit(1) - kdir = r'/config/auth/wireguard' + def _check_kmod(): if not os.path.exists('/sys/module/wireguard'): - sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") if os.system('sudo modprobe wireguard') != 0: - sl.syslog(sl.LOG_NOTICE, "modprobe wireguard failed") raise ConfigError("modprobe wireguard failed") def _migrate_default_keys(): if os.path.exists('{}/private.key'.format(kdir)) and not os.path.exists('{}/default/private.key'.format(kdir)): - sl.syslog(sl.LOG_NOTICE, "migrate keypair to default") old_umask = os.umask(0o027) location = '{}/default'.format(kdir) subprocess.call(['sudo mkdir -p ' + location], shell=True) subprocess.call(['sudo chgrp vyattacfg ' + location], shell=True) subprocess.call(['sudo chmod 750 ' + location], shell=True) - os.rename('{}/private.key'.format(kdir),'{}/private.key'.format(location)) - os.rename('{}/public.key'.format(kdir),'{}/public.key'.format(location)) + os.rename('{}/private.key'.format(kdir), + '{}/private.key'.format(location)) + os.rename('{}/public.key'.format(kdir), + '{}/public.key'.format(location)) os.umask(old_umask) @@ -59,47 +56,82 @@ def get_config(): if not c.exists('interfaces wireguard'): return None - config_data = { - ifname: { - 'addr': '', - 'descr': ifname, - 'lport': None, - 'status': 'exists', - 'state': 'enabled', - 'fwmark': 0x00, - 'mtu': 1420, - 'peer': {}, - 'pk' : '{}/default/private.key'.format(kdir) - } + dflt_cnf = { + 'intfc': '', + 'addr': [], + 'addr_remove': [], + 'descr': '', + 'lport': None, + 'delete': False, + 'state': 'up', + 'fwmark': 0x00, + 'mtu': 1420, + 'peer': {}, + 'peer_remove': [], + 'pk': '{}/default/private.key'.format(kdir) } + if os.getenv('VYOS_TAGNODE_VALUE'): + ifname = str(os.environ['VYOS_TAGNODE_VALUE']) + wg = deepcopy(dflt_cnf) + wg['intfc'] = ifname + wg['descr'] = ifname + else: + print("ERROR: VYOS_TAGNODE_VALUE undefined") + sys.exit(1) + c.set_level('interfaces wireguard') - if not c.exists_effective(ifname): - config_data[ifname]['status'] = 'create' + # interface removal state if not c.exists(ifname) and c.exists_effective(ifname): - config_data[ifname]['status'] = 'delete' - - if config_data[ifname]['status'] != 'delete': - if c.exists(ifname + ' address'): - config_data[ifname]['addr'] = c.return_values(ifname + ' address') - if c.exists(ifname + ' disable'): - config_data[ifname]['state'] = 'disable' - if c.exists(ifname + ' port'): - config_data[ifname]['lport'] = c.return_value(ifname + ' port') - if c.exists(ifname + ' fwmark'): - config_data[ifname]['fwmark'] = c.return_value(ifname + ' fwmark') - if c.exists(ifname + ' description'): - config_data[ifname]['descr'] = c.return_value( - ifname + ' description') - if c.exists(ifname + ' mtu'): - config_data[ifname]['mtu'] = c.return_value(ifname + ' mtu') - if c.exists(ifname + ' private-key'): - config_data[ifname]['pk'] = "{0}/{1}/private.key".format(kdir,c.return_value(ifname + ' private-key')) - if c.exists(ifname + ' peer'): - for p in c.list_nodes(ifname + ' peer'): - if not c.exists(ifname + ' peer ' + p + ' disable'): - config_data[ifname]['peer'].update( + wg['delete'] = True + + if not wg['delete']: + c.set_level('interfaces wireguard {}'.format(ifname)) + if c.exists('address'): + wg['addr'] = c.return_values('address') + + # determine addresses which need to be removed + eff_addr = c.return_effective_values('address') + wg['addr_remove'] = list_diff(eff_addr, wg['addr']) + + # ifalias description + if c.exists('description'): + wg['descr'] = c.return_value('description') + + # link state + if c.exists('disable'): + wg['state'] = 'down' + + # local port to listen on + if c.exists('port'): + wg['lport'] = c.return_value('port') + + # fwmark value + if c.exists('fwmark'): + wg['fwmark'] = c.return_value('fwmark') + + # mtu + if c.exists('mtu'): + wg['mtu'] = c.return_value('mtu') + + # private key + if c.exists('private-key'): + wg['pk'] = "{0}/{1}/private.key".format( + kdir, c.return_value('private-key')) + + # peer removal, wg identifies peers by its pubkey + peer_eff = c.list_effective_nodes('peer') + peer_rem = list_diff(peer_eff, c.list_nodes('peer')) + for p in peer_rem: + wg['peer_remove'].append( + c.return_effective_value('peer {} pubkey'.format(p))) + + # peer settings + if c.exists('peer'): + for p in c.list_nodes('peer'): + if not c.exists('peer ' + p + ' disable'): + wg['peer'].update( { p: { 'allowed-ips': [], @@ -108,46 +140,61 @@ def get_config(): } } ) - if c.exists(ifname + ' peer ' + p + ' pubkey'): - config_data[ifname]['peer'][p]['pubkey'] = c.return_value( - ifname + ' peer ' + p + ' pubkey') - if c.exists(ifname + ' peer ' + p + ' allowed-ips'): - config_data[ifname]['peer'][p]['allowed-ips'] = c.return_values( - ifname + ' peer ' + p + ' allowed-ips') - if c.exists(ifname + ' peer ' + p + ' endpoint'): - config_data[ifname]['peer'][p]['endpoint'] = c.return_value( - ifname + ' peer ' + p + ' endpoint') - if c.exists(ifname + ' peer ' + p + ' persistent-keepalive'): - config_data[ifname]['peer'][p]['persistent-keepalive'] = c.return_value( - ifname + ' peer ' + p + ' persistent-keepalive') - if c.exists(ifname + ' peer ' + p + ' preshared-key'): - config_data[ifname]['peer'][p]['psk'] = c.return_value( - ifname + ' peer ' + p + ' preshared-key') - - return config_data + # peer allowed-ips + if c.exists('peer ' + p + ' allowed-ips'): + wg['peer'][p]['allowed-ips'] = c.return_values( + 'peer ' + p + ' allowed-ips') + # peer endpoint + if c.exists('peer ' + p + ' endpoint'): + wg['peer'][p]['endpoint'] = c.return_value( + 'peer ' + p + ' endpoint') + # persistent-keepalive + if c.exists('peer ' + p + ' persistent-keepalive'): + wg['peer'][p]['persistent-keepalive'] = c.return_value( + 'peer ' + p + ' persistent-keepalive') + # preshared-key + if c.exists('peer ' + p + ' preshared-key'): + wg['peer'][p]['psk'] = c.return_value( + 'peer ' + p + ' preshared-key') + # peer pubkeys + key_eff = c.return_effective_value( + 'peer {peer} pubkey'.format(peer=p)) + key_cfg = c.return_value( + 'peer {peer} pubkey'.format(peer=p)) + wg['peer'][p]['pubkey'] = key_cfg + + # on a pubkey change we need to remove the pubkey first + # peers are identified by pubkey, so key update means + # peer removal and re-add + if key_eff != key_cfg and key_eff != None: + wg['peer_remove'].append(key_cfg) + + return wg + def verify(c): if not c: return None - if not os.path.exists(c[ifname]['pk']): + if not os.path.exists(c['pk']): raise ConfigError( "No keys found, generate them by executing: \'run generate wireguard [keypair|named-keypairs]\'") - if c[ifname]['status'] != 'delete': - if not c[ifname]['addr']: + if not c['delete']: + if not c['addr']: raise ConfigError("ERROR: IP address required") - if not c[ifname]['peer']: + if not c['peer']: raise ConfigError("ERROR: peer required") - for p in c[ifname]['peer']: - if not c[ifname]['peer'][p]['allowed-ips']: + for p in c['peer']: + if not c['peer'][p]['allowed-ips']: raise ConfigError("ERROR: allowed-ips required for peer " + p) - if not c[ifname]['peer'][p]['pubkey']: + if not c['peer'][p]['pubkey']: raise ConfigError("peer pubkey required for peer " + p) def apply(c): - # no wg config left, delete all wireguard devices, if any + # no wg configs left, remove all interface from system + # maybe move it into ifconfig.py if not c: net_devs = os.listdir('/sys/class/net/') for dev in net_devs: @@ -156,120 +203,74 @@ def apply(c): if re.search("DEVTYPE=wireguard", buf, re.I | re.M): wg_intf = re.sub("INTERFACE=", "", re.search( "INTERFACE=.*", buf, re.I | re.M).group(0)) - sl.syslog(sl.LOG_NOTICE, "removing interface " + wg_intf) subprocess.call( ['ip l d dev ' + wg_intf + ' >/dev/null'], shell=True) return None - # interface removal - if c[ifname]['status'] == 'delete': - sl.syslog(sl.LOG_NOTICE, "removing interface " + ifname) + # init wg class + intfc = WireGuardIf(c['intfc']) + + # single interface removal + if c['delete']: intfc.remove() return None - c_eff = Config() - c_eff.set_level('interfaces wireguard') + # remove IP addresses + for ip in c['addr_remove']: + intfc.del_addr(ip) - # interface state - if c[ifname]['state'] == 'disable': - sl.syslog(sl.LOG_NOTICE, "disable interface " + ifname) - intfc.set_state('down') - else: - if not intfc.get_state() == 'up': - sl.syslog(sl.LOG_NOTICE, "enable interface " + ifname) - intfc.set_state('up') - - # IP address - if not c_eff.exists_effective(ifname + ' address'): - for ip in c[ifname]['addr']: - intfc.add_addr(ip) - else: - addr_eff = c_eff.return_effective_values(ifname + ' address') - addr_rem = list(set(addr_eff) - set(c[ifname]['addr'])) - addr_add = list(set(c[ifname]['addr']) - set(addr_eff)) - - if len(addr_rem) != 0: - for ip in addr_rem: - sl.syslog( - sl.LOG_NOTICE, "remove IP address {0} from {1}".format(ip, ifname)) - intfc.del_addr(ip) - - if len(addr_add) != 0: - for ip in addr_add: - sl.syslog( - sl.LOG_NOTICE, "add IP address {0} to {1}".format(ip, ifname)) - intfc.add_addr(ip) - - # interface MTU - if c[ifname]['mtu'] != 1420: - intfc.set_mtu(int(c[ifname]['mtu'])) - else: - # default is set to 1420 in config_data - intfc.set_mtu(int(c[ifname]['mtu'])) - - # ifalias for snmp from description - descr_eff = c_eff.return_effective_value(ifname + ' description') - if descr_eff != c[ifname]['descr']: - intfc.set_alias(str(c[ifname]['descr'])) - - # peer deletion - peer_eff = c_eff.list_effective_nodes(ifname + ' peer') - peer_cnf = [] + # add IP addresses + for ip in c['addr']: + intfc.add_addr(ip) - try: - for p in c[ifname]['peer']: - peer_cnf.append(p) - except KeyError: - pass - - peer_rem = list(set(peer_eff) - set(peer_cnf)) - for p in peer_rem: - pkey = c_eff.return_effective_value(ifname + ' peer ' + p + ' pubkey') - intfc.remove_peer(pkey) - - # peer key update - for p in peer_eff: - if p in peer_cnf: - ekey = c_eff.return_effective_value( - ifname + ' peer ' + p + ' pubkey') - nkey = c[ifname]['peer'][p]['pubkey'] - if nkey != ekey: - sl.syslog( - sl.LOG_NOTICE, "peer {0} pubkey changed from {1} to {2} on interface {3}".format(p, ekey, nkey, ifname)) - intfc.remove_peer(ekey) - - intfc.config['private-key'] = c[ifname]['pk'] - for p in c[ifname]['peer']: - intfc.config['pubkey'] = str(c[ifname]['peer'][p]['pubkey']) - intfc.config['allowed-ips'] = (c[ifname]['peer'][p]['allowed-ips']) - - # listen-port - if c[ifname]['lport']: - intfc.config['port'] = c[ifname]['lport'] + # interface mtu + intfc.mtu = int(c['mtu']) + # ifalias for snmp from description + intfc.ifalias = str(c['descr']) + + # remove peers + if c['peer_remove']: + for pkey in c['peer_remove']: + intfc.remove_peer(pkey) + + # peer pubkey + # setting up the wg interface + intfc.config['private-key'] = c['pk'] + for p in c['peer']: + # peer pubkey + intfc.config['pubkey'] = str(c['peer'][p]['pubkey']) + # peer allowed-ips + intfc.config['allowed-ips'] = c['peer'][p]['allowed-ips'] + # local listen port + if c['lport']: + intfc.config['port'] = c['lport'] # fwmark - if c[ifname]['fwmark']: - intfc.config['fwmark'] = c[ifname]['fwmark'] - + if c['fwmark']: + intfc.config['fwmark'] = c['fwmark'] # endpoint - if c[ifname]['peer'][p]['endpoint']: - intfc.config['endpoint'] = c[ifname]['peer'][p]['endpoint'] + if c['peer'][p]['endpoint']: + intfc.config['endpoint'] = c['peer'][p]['endpoint'] # persistent-keepalive - if 'persistent-keepalive' in c[ifname]['peer'][p]: - intfc.config['keepalive'] = c[ifname][ - 'peer'][p]['persistent-keepalive'] + if 'persistent-keepalive' in c['peer'][p]: + intfc.config['keepalive'] = c['peer'][p]['persistent-keepalive'] + # maybe move it into ifconfig.py # preshared-key - needs to be read from a file - if 'psk' in c[ifname]['peer'][p]: + if 'psk' in c['peer'][p]: psk_file = '/config/auth/wireguard/psk' old_umask = os.umask(0o077) - open(psk_file, 'w').write(str(c[ifname]['peer'][p]['psk'])) + open(psk_file, 'w').write(str(c['peer'][p]['psk'])) os.umask(old_umask) intfc.config['psk'] = psk_file - intfc.update() + # interface state + intfc.state = c['state'] + + return None + if __name__ == '__main__': try: _check_kmod() diff --git a/src/conf_mode/ipoe_server.py b/src/conf_mode/ipoe_server.py index a60379760..1662e45e6 100755 --- a/src/conf_mode/ipoe_server.py +++ b/src/conf_mode/ipoe_server.py @@ -369,6 +369,9 @@ def verify(c): if c == None or not c: return None + if not c['interfaces']: + raise ConfigError("service ipoe-server interface requires a value") + for intfc in c['interfaces']: if not c['interfaces'][intfc]['range']: raise ConfigError("service ipoe-server interface " + intfc + " client-subnet needs a value") diff --git a/src/conf_mode/ntp.py b/src/conf_mode/ntp.py index f706d502f..8f32e6e81 100755 --- a/src/conf_mode/ntp.py +++ b/src/conf_mode/ntp.py @@ -42,6 +42,8 @@ restrict default noquery nopeer notrap nomodify restrict 127.0.0.1 restrict -6 ::1 +# Do not listen on any interface address by default +interface ignore wildcard # # Configurable section # @@ -63,7 +65,6 @@ restrict {{ n.address }} mask {{ n.netmask }} nomodify notrap nopeer {% if listen_address -%} # NTP should listen on configured addresses only -interface ignore wildcard {% for a in listen_address -%} interface listen {{ a }} {% endfor -%} diff --git a/src/tests/test_ntp.py b/src/tests/test_ntp.py deleted file mode 100644 index 1cde490b4..000000000 --- a/src/tests/test_ntp.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 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 tempfile -import unittest -from unittest import TestCase, mock -import ipaddress -from contextlib import ExitStack -import textwrap - -from vyos import ConfigError -from vyos.config import Config -try: - from src.conf_mode import ntp -except ModuleNotFoundError: # for unittest.main() - import sys - sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) - from src.conf_mode import ntp - - -class TestNtp(TestCase): - - def test_get_config(self): - tests = [ - { - 'name': 'empty', - 'config': { - 'system ntp': None, - }, - 'expected': None, - }, - { - 'name': 'full-options', - 'config': { - 'system ntp': 'yes', - 'allow-clients address': ['192.0.2.0/24'], - 'listen-address': ['198.51.100.0/24'], - 'server': ['example.com'], - 'server example.com noselect': 'yes', - 'server example.com preempt': 'yes', - 'server example.com prefer': 'yes', - }, - 'expected': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.0'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': ['noselect', 'preempt', 'prefer']} - ] - }, - }, - { - 'name': 'non-options', - 'config': { - 'system ntp': 'yes', - 'allow-clients address': ['192.0.2.0/24'], - 'listen-address': ['198.51.100.0/24'], - 'server': ['example.com'], - }, - 'expected': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.0'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': []} - ] - }, - }, - ] - for case in tests: - def mocked_fn(path): - return case['config'].get(path) - - with self.subTest(msg = case['name']): - m = { - 'return_value': mock.Mock(side_effect = mocked_fn), - 'return_values': mock.Mock(side_effect = mocked_fn), - 'list_nodes': mock.Mock(side_effect = mocked_fn), - 'exists': mock.Mock(side_effect = mocked_fn), - } - with mock.patch.multiple(Config, **m): - actual = ntp.get_config() - self.assertEqual(actual, case['expected']) - - def test_verify(self): - tests = [ - { - 'name': 'none', - 'config': None, - 'expected': None - }, - { - 'name': 'valid', - 'config': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': ['noselect', 'preempt', 'prefer']} - ] - }, - 'expected': None, - }, - { - 'name': 'not configure servers', - 'config': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }], - 'servers': [] - }, - 'expected': ConfigError, - }, - { - 'name': 'does not exist in the network', - 'config': { - 'allowed_networks': [{ - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/50', # invalid netmask - }], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': 'example.com', 'options': []} - ] - }, - 'expected': ConfigError, - }, - ] - for case in tests: - with self.subTest(msg = case['name']): - if case['expected'] is not None: - with self.assertRaises(case['expected']): - ntp.verify(case['config']) - else: - ntp.verify(case['config']) - - def test_generate(self): - tests = [ - { - 'name': 'empty', - 'config': None, - 'expected': '', - }, - { - 'name': 'valid', - 'config': { - 'allowed_networks': [ - { - 'address': ipaddress.ip_address('192.0.2.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '192.0.2.0/24', - }, - { - 'address': ipaddress.ip_address('198.51.100.1'), - 'netmask': ipaddress.ip_address('255.255.255.0'), - 'network': '198.51.100.0/24', - }, - ], - 'listen_address': ['198.51.100.0/24'], - 'servers': [ - {'name': '1.example.com', 'options': ['noselect', 'preempt', 'prefer']}, - {'name': '2.example.com', 'options': []}, - ] - }, - 'expected': textwrap.dedent(''' - ### Autogenerated by ntp.py ### - - # - # Non-configurable defaults - # - driftfile /var/lib/ntp/ntp.drift - # By default, only allow ntpd to query time sources, ignore any incoming requests - restrict default noquery nopeer notrap nomodify - # Local users have unrestricted access, allowing reconfiguration via ntpdc - restrict 127.0.0.1 - restrict -6 ::1 - - # - # Configurable section - # - - # Server configuration for: 1.example.com - server 1.example.com iburst noselect preempt prefer - # Server configuration for: 2.example.com - server 2.example.com iburst - - - # Client configuration for network: 192.0.2.0/24 - restrict 192.0.2.1 mask 255.255.255.0 nomodify notrap nopeer - - # Client configuration for network: 198.51.100.0/24 - restrict 198.51.100.1 mask 255.255.255.0 nomodify notrap nopeer - - - - # NTP should listen on configured addresses only - interface ignore wildcard - interface listen 198.51.100.0/24 - - '''), - }, - ] - - for case in tests: - with self.subTest(msg = case['name']): - with tempfile.NamedTemporaryFile() as fp: - ntp.config_file = fp.name - - ntp.generate(case['config']) - actual = fp.file.read().decode('ascii') - print(actual) - self.assertEqual(case['expected'], actual) - - def test_apply(self): - with tempfile.NamedTemporaryFile(delete = False) as fp: - ntp.config_file = fp.name - with mock.patch('os.system') as os_system: - ntp.apply({}) # some configure - os_system.assert_has_calls([ - mock.call('sudo systemctl restart ntp.service'), - ]) - self.assertTrue(os.path.exists(fp.name)) - - ntp.apply(None) # empty configure - os_system.assert_has_calls([ - mock.call('sudo systemctl stop ntp.service'), - ]) - self.assertFalse(os.path.exists(fp.name)) - -if __name__ == "__main__": - unittest.main() |