diff options
-rw-r--r-- | data/templates/ssh/sshd_config.tmpl | 142 | ||||
-rw-r--r-- | interface-definitions/include/interface-ipv4.xml.i | 11 | ||||
-rw-r--r-- | interface-definitions/include/interface-ipv6.xml.i | 10 | ||||
-rw-r--r-- | interface-definitions/interfaces-tunnel.xml.in | 9 | ||||
-rw-r--r-- | interface-definitions/ssh.xml.in | 32 | ||||
-rw-r--r-- | python/vyos/ifconfig/vxlan.py | 28 | ||||
-rw-r--r-- | python/vyos/ifconfig/wireguard.py | 58 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-tunnel.py | 11 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-wireguard.py | 9 | ||||
-rwxr-xr-x | src/conf_mode/ssh.py | 91 |
10 files changed, 189 insertions, 212 deletions
diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl index 08fe56655..1c136bb23 100644 --- a/data/templates/ssh/sshd_config.tmpl +++ b/data/templates/ssh/sshd_config.tmpl @@ -1,6 +1,10 @@ ### Autogenerated by ssh.py ### +# https://linux.die.net/man/5/sshd_config + +# # Non-configurable defaults +# Protocol 2 HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_dsa_key @@ -22,99 +26,89 @@ TCPKeepAlive yes Banner /etc/issue.net Subsystem sftp /usr/lib/openssh/sftp-server UsePAM yes +PermitRootLogin no + +# +# User configurable section +# -# Specifies whether sshd should look up the remote host name, -# and to check that the resolved host name for the remote IP +# Look up remote host name and check that the resolved host name for the remote IP # address maps back to the very same IP address. -UseDNS {{ host_validation }} +UseDNS {{ "no" if disable_host_validation is defined else "yes" }} -# Specifies the port number that sshd listens on. The default is 22. -# Multiple options of this type are permitted. -{% for p in port %} -Port {{ p }} -{% endfor %} +# Specifies the port number that sshd(8) listens on +{% if port is string %} +Port {{ port }} +{% else %} +{% for value in port %} +Port {{ value }} +{% endfor %} +{% endif %} # Gives the verbosity level that is used when logging messages from sshd -LogLevel {{ log_level }} - -# Specifies whether root can log in using ssh -PermitRootLogin no +LogLevel {{ loglevel }} # Specifies whether password authentication is allowed -PasswordAuthentication {{ password_authentication }} +PasswordAuthentication {{ "no" if disable_password_authentication is defined else "yes" }} -{% if listen_on %} +{% if listen_address %} # Specifies the local addresses sshd should listen on -{% for a in listen_on %} -ListenAddress {{ a }} -{% endfor %} -{{ "\n" }} -{% endif %} - -{%- if ciphers %} -# Specifies the ciphers allowed. Multiple ciphers must be comma-separated. -# -# NOTE: As of now, there is no 'multi' node for 'ciphers', thus we have only one :/ -Ciphers {{ ciphers | join(",") }} -{{ "\n" }} -{% endif %} - -{%- if mac %} -# Specifies the available MAC (message authentication code) algorithms. The MAC -# algorithm is used for data integrity protection. Multiple algorithms must be -# comma-separated. -# -# NOTE: As of now, there is no 'multi' node for 'mac', thus we have only one :/ -MACs {{ mac | join(",") }} -{{ "\n" }} -{% endif %} - -{%- if key_exchange %} -# Specifies the available KEX (Key Exchange) algorithms. Multiple algorithms must -# be comma-separated. -# -# NOTE: As of now, there is no 'multi' node for 'key-exchange', thus we have only one :/ -KexAlgorithms {{ key_exchange | join(",") }} -{{ "\n" }} +{% if listen_address is string %} +ListenAddress {{ listen_address }} +{% else %} +{% for address in listen_address %} +ListenAddress {{ value }} +{% endfor %} +{% endif %} {% endif %} -{%- if allow_users %} -# This keyword can be followed by a list of user name patterns, separated by spaces. -# If specified, login is allowed only for user names that match one of the patterns. -# Only user names are valid, a numerical user ID is not recognized. -AllowUsers {{ allow_users | join(" ") }} -{{ "\n" }} +{% if ciphers %} +# Specifies the ciphers allowed for protocol version 2 +{% set value = ciphers if ciphers is string else ciphers | join(',') %} +Ciphers {{ value }} {% endif %} -{%- if allow_groups %} -# This keyword can be followed by a list of group name patterns, separated by spaces. -# If specified, login is allowed only for users whose primary group or supplementary -# group list matches one of the patterns. Only group names are valid, a numerical group -# ID is not recognized. -AllowGroups {{ allow_groups | join(" ") }} -{{ "\n" }} +{% if mac %} +# Specifies the available MAC (message authentication code) algorithms +{% set value = mac if mac is string else mac | join(',') %} +MACs {{ value }} {% endif %} -{%- if deny_users %} -# This keyword can be followed by a list of user name patterns, separated by spaces. -# Login is disallowed for user names that match one of the patterns. Only user names -# are valid, a numerical user ID is not recognized. -DenyUsers {{ deny_users | join(" ") }} -{{ "\n" }} +{% if key_exchange %} +# Specifies the available Key Exchange algorithms +{% set value = key_exchange if key_exchange is string else key_exchange | join(',') %} +KexAlgorithms {{ value }} {% endif %} -{%- if deny_groups %} -# This keyword can be followed by a list of group name patterns, separated by spaces. +{% if access_control is defined %} +{% if access_control.allow is defined %} +{% if access_control.allow.user is defined %} +# If specified, login is allowed only for user names that match +{% set value = access_control.allow.user if access_control.allow.user is string else access_control.allow.user | join(' ') %} +AllowUsers {{ value }} +{% endif %} +{% if access_control.allow.group is defined %} +# If specified, login is allowed only for users whose primary group or supplementary group list matches +{% set value = access_control.allow.group if access_control.allow.group is string else access_control.allow.group | join(' ') %} +AllowGroups {{ value }} +{% endif %} +{% endif %} +{% if access_control.deny is defined %} +{% if access_control.deny.user is defined %} +# Login is disallowed for user names that match +{% set value = access_control.deny.user if access_control.deny.user is string else access_control.deny.user | join(' ') %} +DenyUsers {{ value }} +{% endif %} +{% if access_control.deny.group is defined %} # Login is disallowed for users whose primary group or supplementary group list matches -# one of the patterns. Only group names are valid, a numerical group ID is not recognized. -DenyGroups {{ deny_groups | join(" ") }} -{{ "\n" }} +{% set value = access_control.deny.group if access_control.deny.group is string else access_control.deny.group | join(' ') %} +DenyGroups {{ value }} +{% endif %} +{% endif %} {% endif %} -{%- if client_keepalive %} +{% if client_keepalive_interval %} # Sets a timeout interval in seconds after which if no data has been received from the client, -# sshd will send a message through the encrypted channel to request a response from the client. -# The default is 0, indicating that these messages will not be sent to the client. -# This option applies to protocol version 2 only. -ClientAliveInterval {{ client_keepalive }} +# sshd(8) will send a message through the encrypted channel to request a response from the client +ClientAliveInterval {{ client_keepalive_interval }} {% endif %} diff --git a/interface-definitions/include/interface-ipv4.xml.i b/interface-definitions/include/interface-ipv4.xml.i new file mode 100644 index 000000000..15932a9d3 --- /dev/null +++ b/interface-definitions/include/interface-ipv4.xml.i @@ -0,0 +1,11 @@ +<node name="ip"> + <properties> + <help>IPv4 routing parameters</help> + </properties> + <children> + #include <include/interface-disable-arp-filter.xml.i> + #include <include/interface-enable-arp-accept.xml.i> + #include <include/interface-enable-arp-announce.xml.i> + #include <include/interface-enable-arp-ignore.xml.i> + </children> +</node> diff --git a/interface-definitions/include/interface-ipv6.xml.i b/interface-definitions/include/interface-ipv6.xml.i new file mode 100644 index 000000000..23362f75a --- /dev/null +++ b/interface-definitions/include/interface-ipv6.xml.i @@ -0,0 +1,10 @@ +<node name="ipv6"> + <properties> + <help>IPv6 routing parameters</help> + </properties> + <children> + #include <include/ipv6-address.xml.i> + #include <include/ipv6-disable-forwarding.xml.i> + #include <include/ipv6-dup-addr-detect-transmits.xml.i> + </children> +</node> diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in index a38a73e15..64520ce99 100644 --- a/interface-definitions/interfaces-tunnel.xml.in +++ b/interface-definitions/interfaces-tunnel.xml.in @@ -22,13 +22,8 @@ #include <include/interface-disable-link-detect.xml.i> #include <include/interface-vrf.xml.i> #include <include/interface-mtu-64-8024.xml.i> - <node name="ipv6"> - <children> - #include <include/ipv6-address.xml.i> - #include <include/ipv6-disable-forwarding.xml.i> - #include <include/ipv6-dup-addr-detect-transmits.xml.i> - </children> - </node> + #include <include/interface-ipv4.xml.i> + #include <include/interface-ipv6.xml.i> <leafNode name="local-ip"> <properties> <help>Local IP address for this tunnel</help> diff --git a/interface-definitions/ssh.xml.in b/interface-definitions/ssh.xml.in index 4e8cf28eb..1b20f5776 100644 --- a/interface-definitions/ssh.xml.in +++ b/interface-definitions/ssh.xml.in @@ -76,8 +76,12 @@ <properties> <help>Allowed ciphers</help> <completionHelp> - <script>ssh -Q cipher | tr '\n' ' '</script> + <!-- generated by ssh -Q cipher | tr '\n' ' ' as this will not change dynamically --> + <list>3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com</list> </completionHelp> + <constraint> + <regex>^(3des-cbc|aes128-cbc|aes192-cbc|aes256-cbc|rijndael-cbc@lysator.liu.se|aes128-ctr|aes192-ctr|aes256-ctr|aes128-gcm@openssh.com|aes256-gcm@openssh.com|chacha20-poly1305@openssh.com)$</regex> + </constraint> <multi/> </properties> </leafNode> @@ -97,9 +101,13 @@ <properties> <help>Allowed key exchange (KEX) algorithms</help> <completionHelp> - <script>ssh -Q kex | tr '\n' ' '</script> + <!-- generated by ssh -Q kex | tr '\n' ' ' as this will not change dynamically --> + <list>diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 curve25519-sha256 curve25519-sha256@libssh.org</list> </completionHelp> <multi/> + <constraint> + <regex>^(diffie-hellman-group1-sha1|diffie-hellman-group14-sha1|diffie-hellman-group14-sha256|diffie-hellman-group16-sha512|diffie-hellman-group18-sha512|diffie-hellman-group-exchange-sha1|diffie-hellman-group-exchange-sha256|ecdh-sha2-nistp256|ecdh-sha2-nistp384|ecdh-sha2-nistp521|curve25519-sha256|curve25519-sha256@libssh.org)$</regex> + </constraint> </properties> </leafNode> <leafNode name="listen-address"> @@ -123,6 +131,9 @@ <leafNode name="loglevel"> <properties> <help>Log level</help> + <completionHelp> + <list>QUIET FATAL ERROR INFO VERBOSE</list> + </completionHelp> <valueHelp> <format>QUIET</format> <description>stay silent</description> @@ -143,14 +154,22 @@ <format>VERBOSE</format> <description>enable logging of failed login attempts</description> </valueHelp> + <constraint> + <regex>^(QUIET|FATAL|ERROR|INFO|VERBOSE)$</regex> + </constraint> </properties> + <defaultValue>INFO</defaultValue> </leafNode> <leafNode name="mac"> <properties> <help>Allowed message authentication code (MAC) algorithms</help> <completionHelp> - <script>ssh -Q mac | tr '\n' ' '</script> + <!-- generated by ssh -Q mac | tr '\n' ' ' as this will not change dynamically --> + <list>hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com</list> </completionHelp> + <constraint> + <regex>^(hmac-sha1|hmac-sha1-96|hmac-sha2-256|hmac-sha2-512|hmac-md5|hmac-md5-96|umac-64@openssh.com|umac-128@openssh.com|hmac-sha1-etm@openssh.com|hmac-sha1-96-etm@openssh.com|hmac-sha2-256-etm@openssh.com|hmac-sha2-512-etm@openssh.com|hmac-md5-etm@openssh.com|hmac-md5-96-etm@openssh.com|umac-64-etm@openssh.com|umac-128-etm@openssh.com)$</regex> + </constraint> <multi/> </properties> </leafNode> @@ -166,10 +185,15 @@ <validator name="numeric" argument="--range 1-65535"/> </constraint> </properties> + <defaultValue>22</defaultValue> </leafNode> <leafNode name="client-keepalive-interval"> <properties> - <help>how often send keep alives in seconds</help> + <help>Enable transmission of keepalives from server to client</help> + <valueHelp> + <format>1-65535</format> + <description>Time interval in seconds for keepalive message</description> + </valueHelp> <constraint> <validator name="numeric" argument="--range 1-65535"/> </constraint> diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py index f9f2e38e9..cd9026bf8 100644 --- a/python/vyos/ifconfig/vxlan.py +++ b/python/vyos/ifconfig/vxlan.py @@ -66,24 +66,26 @@ class VXLANIf(Interface): 'ifname': 'add', 'vni': 'id', 'port': 'dstport', - 'src_address': 'nolearning local', + 'src_address': 'local', + 'src_interface': 'dev', } def _create(self): - cmdline = set() - if self.config['remote']: - cmdline = ('ifname', 'type', 'remote', 'src_interface', 'vni', 'port') - - elif self.config['src_address']: - cmdline = ('ifname', 'type', 'src_address', 'vni', 'port') + cmdline = ['ifname', 'type', 'vni', 'port'] - elif self.config['group'] and self.config['src_interface']: - cmdline = ('ifname', 'type', 'group', 'src_interface', 'vni', 'port') + if self.config['src_address']: + cmdline.append('src_address') - else: - ifname = self.config['ifname'] - raise ConfigError( - f'VXLAN "{ifname}" is missing mandatory underlay interface for a multicast network.') + if self.config['remote']: + cmdline.append('remote') + + if self.config['group'] or self.config['src_interface']: + if self.config['group'] and self.config['src_interface']: + cmdline.append('group', 'src_interface') + else: + ifname = self.config['ifname'] + raise ConfigError( + f'VXLAN "{ifname}" is missing mandatory underlay multicast group or source interface for a multicast network.') cmd = 'ip link' for key in cmdline: diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py index 027b5ea8c..a90a66ac3 100644 --- a/python/vyos/ifconfig/wireguard.py +++ b/python/vyos/ifconfig/wireguard.py @@ -149,10 +149,10 @@ class WireGuardIf(Interface): default = { 'type': 'wireguard', 'port': 0, - 'private-key': None, + 'private_key': None, 'pubkey': None, - 'psk': '/dev/null', - 'allowed-ips': [], + 'psk': '', + 'allowed_ips': [], 'fwmark': 0x00, 'endpoint': None, 'keepalive': 0 @@ -166,8 +166,8 @@ class WireGuardIf(Interface): } } options = Interface.options + \ - ['port', 'private-key', 'pubkey', 'psk', - 'allowed-ips', 'fwmark', 'endpoint', 'keepalive'] + ['port', 'private_key', 'pubkey', 'psk', + 'allowed_ips', 'fwmark', 'endpoint', 'keepalive'] """ Wireguard interface class, contains a comnfig dictionary since @@ -180,44 +180,44 @@ class WireGuardIf(Interface): >>> from vyos.ifconfig import WireGuardIf as wg_if >>> wg_intfc = wg_if("wg01") >>> print (wg_intfc.wg_config) - {'private-key': None, 'keepalive': 0, 'endpoint': None, 'port': 0, - 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} + {'private_key': None, 'keepalive': 0, 'endpoint': None, 'port': 0, + 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} >>> wg_intfc.wg_config['keepalive'] = 100 >>> print (wg_intfc.wg_config) - {'private-key': None, 'keepalive': 100, 'endpoint': None, 'port': 0, - 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} + {'private_key': None, 'keepalive': 100, 'endpoint': None, 'port': 0, + 'allowed_ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} """ def update(self): - if not self.config['private-key']: + if not self.config['private_key']: raise ValueError("private key required") else: # fmask permission check? pass - cmd = "wg set {} ".format(self.config['ifname']) - cmd += "listen-port {} ".format(self.config['port']) - cmd += "fwmark {} ".format(str(self.config['fwmark'])) - cmd += "private-key {} ".format(self.config['private-key']) - cmd += "peer {} ".format(self.config['pubkey']) - cmd += " preshared-key {} ".format(self.config['psk']) - cmd += " allowed-ips " - for aip in self.config['allowed-ips']: - if aip != self.config['allowed-ips'][-1]: - cmd += aip + "," - else: - cmd += aip + cmd = 'wg set {ifname}'.format(**self.config) + cmd += ' listen-port {port}'.format(**self.config) + cmd += ' fwmark "{fwmark}" '.format(**self.config) + cmd += ' private-key {private_key}'.format(**self.config) + cmd += ' peer {pubkey}'.format(**self.config) + cmd += ' persistent-keepalive {keepalive}'.format(**self.config) + cmd += ' allowed-ips {}'.format(', '.join(self.config['allowed-ips'])) + if self.config['endpoint']: - cmd += " endpoint '{}'".format(self.config['endpoint']) - cmd += " persistent-keepalive {}".format(self.config['keepalive']) + cmd += ' endpoint "{endpoint}"'.format(**self.config) + + psk_file = '' + if self.config['psk']: + psk_file = '/tmp/{ifname}.psk'.format(**self.config) + with open(psk_file, 'w') as f: + f.write(self.config['psk']) + cmd += f' preshared-key {psk_file}' self._cmd(cmd) - # remove psk since it isn't required anymore and is saved in the cli - # config only !! - if self.config['psk'] != '/dev/null': - if os.path.exists(self.config['psk']): - os.remove(self.config['psk']) + # PSK key file is not required to be stored persistently as its backed by CLI + if os.path.exists(psk_file): + os.remove(psk_file) def remove_peer(self, peerkey): """ diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py index c9964d41c..c13f77d91 100755 --- a/src/conf_mode/interfaces-tunnel.py +++ b/src/conf_mode/interfaces-tunnel.py @@ -254,6 +254,10 @@ default_config_data = { 'ip': False, 'ipv6': False, 'nhrp': [], + 'arp_filter': 1, + 'arp_accept': 0, + 'arp_announce': 0, + 'arp_ignore': 0, 'ipv6_accept_ra': 1, 'ipv6_autoconf': 0, 'ipv6_forwarding': 1, @@ -307,6 +311,10 @@ mapping = { 'link_detect': ('disable-link-detect', False, 2), 'vrf': ('vrf', False, None), 'addresses-add': ('address', True, None), + 'arp_filter': ('ip disable-arp-filter', False, 0), + 'arp_accept': ('ip enable-arp-accept', False, 1), + 'arp_announce': ('ip enable-arp-announce', False, 1), + 'arp_ignore': ('ip enable-arp-ignore', False, 1), 'ipv6_autoconf': ('ipv6 address autoconf', False, 1), 'ipv6_forwarding': ('ipv6 disable-forwarding', False, 0), 'ipv6_dad_transmits:': ('ipv6 dup-addr-detect-transmits', False, None) @@ -474,6 +482,8 @@ def verify(conf): kls = get_class(options) valid = kls.updates + ['alias', 'addresses-add', 'addresses-del', 'vrf', 'state'] + valid += ['arp_filter', 'arp_accept', 'arp_announce', 'arp_ignore'] + valid += ['ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'] if changes['section'] == 'create': valid.extend(['type',]) @@ -645,6 +655,7 @@ def apply(conf): # set other interface properties for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast', + 'arp_accept', 'arp_filter', 'arp_announce', 'arp_ignore', 'ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'): if not options[option]: # should never happen but better safe diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index ab3e073ae..c24c9a7ce 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -275,7 +275,7 @@ def apply(wg): # peer pubkey # setting up the wg interface - w.config['private-key'] = c['pk'] + w.config['private_key'] = c['pk'] for peer in wg['peer']: # peer pubkey @@ -300,13 +300,8 @@ def apply(wg): if peer['persistent_keepalive']: w.config['keepalive'] = peer['persistent_keepalive'] - # maybe move it into ifconfig.py - # preshared-key - needs to be read from a file if peer['psk']: - psk_file = '/config/auth/wireguard/psk' - with open(psk_file, 'w') as f: - f.write(peer['psk']) - w.config['psk'] = psk_file + w.config['psk'] = peer['psk'] w.update() diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 43fa2ff39..1ca2c8b4c 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -20,89 +20,28 @@ from netifaces import interfaces from sys import exit from vyos.config import Config +from vyos.configdict import dict_merge from vyos import ConfigError from vyos.util import call from vyos.template import render - +from vyos.xml import defaults from vyos import airbag airbag.enable() config_file = r'/etc/ssh/sshd_config' systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' -default_config_data = { - 'port' : ['22'], - 'log_level': 'INFO', - 'password_authentication': 'yes', - 'host_validation': 'yes', - 'vrf': '' -} - def get_config(): - ssh = default_config_data conf = Config() base = ['service', 'ssh'] if not conf.exists(base): return None - else: - conf.set_level(base) - - tmp = ['access-control', 'allow', 'user'] - if conf.exists(tmp): - ssh['allow_users'] = conf.return_values(tmp) - - tmp = ['access-control', 'allow', 'group'] - if conf.exists(tmp): - ssh['allow_groups'] = conf.return_values(tmp) - - tmp = ['access-control', 'deny' 'user'] - if conf.exists(tmp): - ssh['deny_users'] = conf.return_values(tmp) - - tmp = ['access-control', 'deny', 'group'] - if conf.exists(tmp): - ssh['deny_groups'] = conf.return_values(tmp) - - tmp = ['ciphers'] - if conf.exists(tmp): - ssh['ciphers'] = conf.return_values(tmp) - - tmp = ['key-exchange'] - if conf.exists(tmp): - ssh['key_exchange'] = conf.return_values(tmp) - - if conf.exists(['disable-host-validation']): - ssh['host_validation'] = 'no' - - if conf.exists(['disable-password-authentication']): - ssh['password_authentication'] = 'no' - - tmp = ['listen-address'] - if conf.exists(tmp): - # We can listen on both IPv4 and IPv6 addresses - # Maybe there could be a check in the future if the configured IP address - # is configured on this system at all? - ssh['listen_on'] = conf.return_values(tmp) - - tmp = ['loglevel'] - if conf.exists(tmp): - ssh['log_level'] = conf.return_value(tmp) - - tmp = ['mac'] - if conf.exists(tmp): - ssh['mac'] = conf.return_values(tmp) - tmp = ['port'] - if conf.exists(tmp): - ssh['port'] = conf.return_values(tmp) - - tmp = ['client-keepalive-interval'] - if conf.exists(tmp): - ssh['client_keepalive'] = conf.return_value(tmp) - - tmp = ['vrf'] - if conf.exists(tmp): - ssh['vrf'] = conf.return_value(tmp) + ssh = conf.get_config_dict(base, key_mangling=('-', '_')) + # 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) + ssh = dict_merge(default_values, ssh) return ssh @@ -110,18 +49,18 @@ def verify(ssh): if not ssh: return None - if 'loglevel' in ssh.keys(): - allowed_loglevel = 'QUIET, FATAL, ERROR, INFO, VERBOSE' - if not ssh['loglevel'] in allowed_loglevel: - raise ConfigError('loglevel must be one of "{0}"\n'.format(allowed_loglevel)) - - if ssh['vrf'] and ssh['vrf'] not in interfaces(): + if 'vrf' in ssh.keys() and ssh['vrf'] not in interfaces(): raise ConfigError('VRF "{vrf}" does not exist'.format(**ssh)) return None def generate(ssh): if not ssh: + if os.path.isfile(config_file): + os.unlink(config_file) + if os.path.isfile(systemd_override): + os.unlink(systemd_override) + return None render(config_file, 'ssh/sshd_config.tmpl', ssh, trim_blocks=True) @@ -133,10 +72,6 @@ def apply(ssh): if not ssh: # SSH access is removed in the commit call('systemctl stop ssh.service') - if os.path.isfile(config_file): - os.unlink(config_file) - if os.path.isfile(systemd_override): - os.unlink(systemd_override) # Reload systemd manager configuration call('systemctl daemon-reload') |