diff options
-rw-r--r-- | data/templates/macsec/wpa_supplicant.conf.tmpl | 21 | ||||
-rw-r--r-- | interface-definitions/interfaces-macsec.xml.in | 16 | ||||
-rw-r--r-- | op-mode-definitions/connect-disconnect.xml | 4 | ||||
-rw-r--r-- | op-mode-definitions/show-interfaces-pppoe.xml | 4 | ||||
-rwxr-xr-x | src/completion/list_pppoe_peers.sh | 6 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-macsec.py | 55 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-pppoe.py | 2 | ||||
-rwxr-xr-x | src/conf_mode/system-login.py | 93 |
8 files changed, 137 insertions, 64 deletions
diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl index eee215418..a614d23f5 100644 --- a/data/templates/macsec/wpa_supplicant.conf.tmpl +++ b/data/templates/macsec/wpa_supplicant.conf.tmpl @@ -47,6 +47,7 @@ network={ # 1: Integrity only macsec_integ_only={{ '0' if security_encrypt else '1' }} +{% if security_encrypt %} # mka_cak, mka_ckn, and mka_priority: IEEE 802.1X/MACsec pre-shared key mode # This allows to configure MACsec with a pre-shared key using a (CAK,CKN) pair. # In this mode, instances of wpa_supplicant can act as MACsec peers. The peer @@ -61,5 +62,25 @@ network={ # mka_priority (Priority of MKA Actor) is in 0..255 range with 255 being # default priority mka_priority={{ security_mka_priority }} +{% endif %} +{% if security_replay_window %} + # macsec_replay_protect: IEEE 802.1X/MACsec replay protection + # This setting applies only when MACsec is in use, i.e., + # - macsec_policy is enabled + # - the key server has decided to enable MACsec + # 0: Replay protection disabled (default) + # 1: Replay protection enabled + macsec_replay_protect={{ '1' if security_replay_window else '0' }} + + # macsec_replay_window: IEEE 802.1X/MACsec replay protection window + # This determines a window in which replay is tolerated, to allow receipt + # of frames that have been misordered by the network. + # This setting applies only when MACsec replay protection active, i.e., + # - macsec_replay_protect is enabled + # - the key server has decided to enable MACsec + # 0: No replay window, strict check (default) + # 1..2^32-1: number of packets that could be misordered + macsec_replay_window={{ security_replay_window }} +{% endif %} } diff --git a/interface-definitions/interfaces-macsec.xml.in b/interface-definitions/interfaces-macsec.xml.in index 3bd0a6fd5..36605ab59 100644 --- a/interface-definitions/interfaces-macsec.xml.in +++ b/interface-definitions/interfaces-macsec.xml.in @@ -86,6 +86,22 @@ </leafNode> </children> </node> + <leafNode name="replay-window"> + <properties> + <help>IEEE 802.1X/MACsec replay protection window</help> + <valueHelp> + <format>0</format> + <description>No replay window, strict check</description> + </valueHelp> + <valueHelp> + <format>1-4294967295</format> + <description>Number of packets that could be misordered</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967295" /> + </constraint> + </properties> + </leafNode> </children> </node> #include <include/interface-description.xml.i> diff --git a/op-mode-definitions/connect-disconnect.xml b/op-mode-definitions/connect-disconnect.xml index 77c334180..cbce2d9ef 100644 --- a/op-mode-definitions/connect-disconnect.xml +++ b/op-mode-definitions/connect-disconnect.xml @@ -9,7 +9,7 @@ <properties> <help>Bring up a connection-oriented network interface</help> <completionHelp> - <script>${vyos_completion_dir}/list_pppoe_peers.sh</script> + <path>interfaces pppoe</path> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/connect_disconnect.py --connect "$3"</command> @@ -25,7 +25,7 @@ <properties> <help>Take down a connection-oriented network interface</help> <completionHelp> - <script>${vyos_completion_dir}/list_pppoe_peers.sh</script> + <path>interfaces pppoe</path> </completionHelp> </properties> <command>sudo ${vyos_op_scripts_dir}/connect_disconnect.py --disconnect "$3"</command> diff --git a/op-mode-definitions/show-interfaces-pppoe.xml b/op-mode-definitions/show-interfaces-pppoe.xml index f9903ee08..cd72bff7d 100644 --- a/op-mode-definitions/show-interfaces-pppoe.xml +++ b/op-mode-definitions/show-interfaces-pppoe.xml @@ -8,7 +8,7 @@ <properties> <help>Show PPPoE interface information</help> <completionHelp> - <script>${vyos_completion_dir}/list_pppoe_peers.sh</script> + <path>interfaces pppoe</path> </completionHelp> </properties> <command>${vyos_op_scripts_dir}/show_interfaces.py --intf="$4"</command> @@ -17,7 +17,7 @@ <properties> <help>Show specified wirelessmodem interface statistics</help> <completionHelp> - <script>${vyos_completion_dir}/list_pppoe_peers.sh</script> + <path>interfaces pppoe</path> </completionHelp> </properties> <command>/usr/sbin/pppstats $4</command> diff --git a/src/completion/list_pppoe_peers.sh b/src/completion/list_pppoe_peers.sh deleted file mode 100755 index 382a29264..000000000 --- a/src/completion/list_pppoe_peers.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -if [ -d /etc/ppp/peers ]; then - cd /etc/ppp/peers - ls pppoe* -fi diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 6a3bb49fe..7d6f238f3 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -22,7 +22,7 @@ from netifaces import interfaces from vyos.config import Config from vyos.configdict import list_diff -from vyos.ifconfig import MACsecIf, Interface +from vyos.ifconfig import MACsecIf from vyos.template import render from vyos.util import call from vyos.validate import is_member @@ -39,6 +39,7 @@ default_config_data = { 'security_mka_cak': '', 'security_mka_ckn': '', 'security_mka_priority': '255', + 'security_replay_window': '', 'intf': '', 'source_interface': '', 'is_bridge_member': False, @@ -48,6 +49,7 @@ default_config_data = { # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' + def get_config(): macsec = deepcopy(default_config_data) conf = Config() @@ -68,7 +70,8 @@ def get_config(): # When stopping wpa_supplicant we need to stop it via the physical # interface - thus we need to retrieve ir from the effective config if conf.exists_effective(base_path + ['source-interface']): - macsec['source_interface'] = conf.return_effective_value(base_path + ['source-interface']) + macsec['source_interface'] = conf.return_effective_value( + base_path + ['source-interface']) return macsec @@ -97,15 +100,23 @@ def get_config(): # Secure Connectivity Association Key if conf.exists(['security', 'mka', 'cak']): - macsec['security_mka_cak'] = conf.return_value(['security', 'mka', 'cak']) + macsec['security_mka_cak'] = conf.return_value( + ['security', 'mka', 'cak']) # Secure Connectivity Association Name if conf.exists(['security', 'mka', 'ckn']): - macsec['security_mka_ckn'] = conf.return_value(['security', 'mka', 'ckn']) + macsec['security_mka_ckn'] = conf.return_value( + ['security', 'mka', 'ckn']) # MACsec Key Agreement protocol (MKA) actor priority if conf.exists(['security', 'mka', 'priority']): - macsec['security_mka_priority'] = conf.return_value(['security', 'mka', 'priority']) + macsec['security_mka_priority'] = conf.return_value( + ['security', 'mka', 'priority']) + + # IEEE 802.1X/MACsec replay protection + if conf.exists(['security', 'replay-window']): + macsec['security_replay_window'] = conf.return_value( + ['security', 'replay-window']) # Physical interface if conf.exists(['source-interface']): @@ -123,18 +134,19 @@ def get_config(): return macsec + def verify(macsec): if macsec['deleted']: if macsec['is_bridge_member']: raise ConfigError( - f'Interface "{intf}" cannot be deleted as it is a ' - f'member of bridge "{is_bridge_member}"!'.format(**macsec)) + 'Interface "{intf}" cannot be deleted as it is a ' + 'member of bridge "{is_bridge_member}"!'.format(**macsec)) return None if not macsec['source_interface']: - raise ConfigError( - 'Physical source interface must be set for MACsec "{intf}"'.format(**macsec)) + raise ConfigError('Physical source interface must be set for ' + 'MACsec "{intf}"'.format(**macsec)) if not macsec['security_cipher']: raise ConfigError( @@ -142,16 +154,17 @@ def verify(macsec): if macsec['security_encrypt']: if not (macsec['security_mka_cak'] and macsec['security_mka_ckn']): - raise ConfigError('MACsec security keys mandartory when encryption is enabled') + raise ConfigError( + 'MACsec security keys mandartory when encryption is enabled') if macsec['vrf']: if macsec['vrf'] not in interfaces(): raise ConfigError('VRF "{vrf}" does not exist'.format(**macsec)) if macsec['is_bridge_member']: - raise ConfigError( - 'Interface "{intf}" cannot be member of VRF "{vrf}" and ' - 'bridge "{is_bridge_member}" at the same time!'.format(**macsec)) + raise ConfigError('Interface "{intf}" cannot be member of VRF ' + '"{vrf}" and bridge "{is_bridge_member}" at ' + 'the same time!'.format(**macsec)) if macsec['is_bridge_member'] and macsec['address']: raise ConfigError( @@ -160,14 +173,18 @@ def verify(macsec): return None + def generate(macsec): - render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.tmpl', macsec, permission=0o640) + render(wpa_suppl_conf.format(**macsec), + 'macsec/wpa_supplicant.conf.tmpl', macsec, permission=0o640) return None + def apply(macsec): # Remove macsec interface if macsec['deleted']: - call('systemctl stop wpa_supplicant-macsec@{source_interface}.service'.format(**macsec)) + call('systemctl stop wpa_supplicant-macsec@{source_interface}' + .format(**macsec)) MACsecIf(macsec['intf']).remove() # delete configuration on interface removal @@ -184,8 +201,8 @@ def apply(macsec): conf['source_interface'] = macsec['source_interface'] conf['security_cipher'] = macsec['security_cipher'] - # It is safe to "re-create" the interface always, there is a sanity check - # that the interface will only be create if its non existent + # It is safe to "re-create" the interface always, there is a sanity + # check that the interface will only be create if its non existent i = MACsecIf(macsec['intf'], **conf) # update interface description used e.g. within SNMP @@ -208,10 +225,12 @@ def apply(macsec): if not macsec['disable']: i.set_admin_state('up') - call('systemctl restart wpa_supplicant-macsec@{source_interface}.service'.format(**macsec)) + call('systemctl restart wpa_supplicant-macsec@{source_interface}' + .format(**macsec)) return None + if __name__ == '__main__': try: c = get_config() diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py index d58ce99cc..6cde850c9 100755 --- a/src/conf_mode/interfaces-pppoe.py +++ b/src/conf_mode/interfaces-pppoe.py @@ -236,7 +236,7 @@ def apply(pppoe): # bail out early return None - if pppoe['disable']: + if not pppoe['disable']: # Dial PPPoE connection call('systemctl restart ppp@{intf}.service'.format(**pppoe)) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 09c5422eb..e6dfd544b 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -24,13 +24,11 @@ from stat import S_IRUSR, S_IWUSR, S_IRWXU, S_IRGRP, S_IXGRP from sys import exit from vyos.config import Config -from vyos.configdict import list_diff -from vyos import ConfigError +from vyos.template import render from vyos.util import cmd from vyos.util import call from vyos.util import DEVNULL -from vyos.template import render - +from vyos import ConfigError radius_config_file = "/etc/pam_radius_auth.conf" @@ -43,8 +41,9 @@ default_config_data = { 'radius_vrf': '' } + def get_local_users(): - """Returns list of dynamically allocated users (see Debian Policy Manual)""" + """Return list of dynamically allocated users (see Debian Policy Manual)""" local_users = [] for p in getpwall(): username = p[0] @@ -55,6 +54,7 @@ def get_local_users(): return local_users + def get_config(): login = default_config_data conf = Config() @@ -71,7 +71,7 @@ def get_config(): user = { 'name': username, 'password_plaintext': '', - 'password_encrypted': '!', + 'password_encred': '!', 'public_keys': [], 'full_name': '', 'home_dir': '/home/' + username, @@ -80,11 +80,13 @@ def get_config(): # Plaintext password if conf.exists(['authentication', 'plaintext-password']): - user['password_plaintext'] = conf.return_value(['authentication', 'plaintext-password']) + user['password_plaintext'] = conf.return_value( + ['authentication', 'plaintext-password']) # Encrypted password if conf.exists(['authentication', 'encrypted-password']): - user['password_encrypted'] = conf.return_value(['authentication', 'encrypted-password']) + user['password_encrypted'] = conf.return_value( + ['authentication', 'encrypted-password']) # User real name if conf.exists(['full-name']): @@ -102,7 +104,8 @@ def get_config(): 'options': '', 'type': '' } - conf.set_level(base_level + ['user', username, 'authentication', 'public-keys', id]) + conf.set_level(base_level + ['user', username, 'authentication', + 'public-keys', id]) # Public Key portion if conf.exists(['key']): @@ -174,21 +177,24 @@ def get_config(): # system is rebooted. login['del_users'] = [tmp for tmp in all_users if tmp not in cli_users] - return login + def verify(login): cur_user = os.environ['SUDO_USER'] if cur_user in login['del_users']: - raise ConfigError('Attempting to delete current user: {}'.format(cur_user)) + raise ConfigError( + 'Attempting to delete current user: {}'.format(cur_user)) for user in login['add_users']: for key in user['public_keys']: if not key['type']: - raise ConfigError('SSH public key type missing for "{}"!'.format(key['name'])) + raise ConfigError( + 'SSH public key type missing for "{name}"!'.format(**key)) if not key['key']: - raise ConfigError('SSH public key for id "{}" missing!'.format(key['name'])) + raise ConfigError( + 'SSH public key for id "{name}" missing!'.format(**key)) # At lease one RADIUS server must not be disabled if len(login['radius_server']) > 0: @@ -205,28 +211,35 @@ def verify(login): return None + def generate(login): # calculate users encrypted password for user in login['add_users']: if user['password_plaintext']: - user['password_encrypted'] = crypt(user['password_plaintext'], METHOD_SHA512) + user['password_encrypted'] = crypt( + user['password_plaintext'], METHOD_SHA512) user['password_plaintext'] = '' - # remove old plaintext password - # and set new encrypted password - os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password '' >/dev/null".format(user['name'])) - os.system("vyos_libexec_dir=/usr/libexec/vyos /opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}' >/dev/null".format(user['name'], user['password_encrypted'])) + # remove old plaintext password and set new encrypted password + env = os.environ.copy() + env['vyos_libexec_dir'] = '/usr/libexec/vyos' + + call("/opt/vyatta/sbin/my_set system login user '{name}' " + "authentication plaintext-password '{password_plaintext}'" + .format(**user), env=env) - # env = os.environ.copy() - # env['vyos_libexec_dir'] = '/usr/libexec/vyos' + call("/opt/vyatta/sbin/my_set system login user '{name}' " + "authentication encrypted-password '{password_encrypted}'" + .format(**user), env=env) - # call("/opt/vyatta/sbin/my_set system login user '{}' authentication plaintext-password ''".format(user['name']), - # env=env) - # call("/opt/vyatta/sbin/my_set system login user '{}' authentication encrypted-password '{}'".format(user['name'], user['password_encrypted']), - # env=env) + elif user['password_encrypted']: + # unset encrypted password so we do not update it with the same + # value again and thus it will not appear in system logs + user['password_encrypted'] = '' if len(login['radius_server']) > 0: - render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', login, trim_blocks=True) + render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', + login, trim_blocks=True) uid = getpwnam('root').pw_uid gid = getpwnam('root').pw_gid @@ -238,6 +251,7 @@ def generate(login): return None + def apply(login): for user in login['add_users']: # make new user using vyatta shell and make home directory (-m), @@ -248,10 +262,13 @@ def apply(login): # update existing account command = "usermod" + # all accounts use /bin/vbash + command += " -s /bin/vbash" # we need to use '' quotes when passing formatted data to the shell # else it will not work as some data parts are lost in translation - command += " -p '{}'".format(user['password_encrypted']) - command += " -s /bin/vbash" + if user['password_encrypted']: + command += " -p '{}'".format(user['password_encrypted']) + if user['full_name']: command += " -c '{}'".format(user['full_name']) @@ -267,10 +284,11 @@ def apply(login): uid = getpwnam(user['name']).pw_uid gid = getpwnam(user['name']).pw_gid - # we should not rely on the home dir value stored in user['home_dir'] - # as if a crazy user will choose username root or any other system - # user this will fail. should be deny using root at all? + # we should not rely on the value stored in user['home_dir'], as a + # crazy user will choose username root or any other system user + # which will fail. Should we deny using root at all? home_dir = getpwnam(user['name']).pw_dir + # install ssh keys ssh_key_dir = home_dir + '/.ssh' if not os.path.isdir(ssh_key_dir): @@ -278,7 +296,7 @@ def apply(login): os.chown(ssh_key_dir, uid, gid) os.chmod(ssh_key_dir, S_IRWXU | S_IRGRP | S_IXGRP) - ssh_key_file = ssh_key_dir + '/authorized_keys'; + ssh_key_file = ssh_key_dir + '/authorized_keys' with open(ssh_key_file, 'w') as f: f.write("# Automatically generated by VyOS\n") f.write("# Do not edit, all changes will be lost\n") @@ -288,14 +306,17 @@ def apply(login): if id['options']: line = '{} '.format(id['options']) - line += '{} {} {}\n'.format(id['type'], id['key'], id['name']) + line += '{} {} {}\n'.format(id['type'], + id['key'], id['name']) f.write(line) os.chown(ssh_key_file, uid, gid) os.chmod(ssh_key_file, S_IRUSR | S_IWUSR) except Exception as e: - raise ConfigError('Adding user "{}" raised an exception: {}'.format(user['name'], e)) + print(e) + raise ConfigError('Adding user "{name}" raised exception' + .format(**user)) for user in login['del_users']: try: @@ -308,7 +329,7 @@ def apply(login): call(f'userdel -r {user}', stderr=DEVNULL) except Exception as e: - raise ConfigError('Deleting user "{}" raised an exception: {}'.format(user, e)) + raise ConfigError(f'Deleting user "{user}" raised exception: {e}') # # RADIUS configuration @@ -351,10 +372,12 @@ def apply(login): cmd(command) except Exception as e: - raise ConfigError('Removing RADIUS configuration failed.\n{}'.format(e)) + raise ConfigError( + 'Removing RADIUS configuration failed.\n{}'.format(e)) return None + if __name__ == '__main__': try: c = get_config() |