diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/conf_mode/dhcp_server.py | 4 | ||||
-rwxr-xr-x | src/conf_mode/firewall.py | 35 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-macsec.py | 74 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-openvpn.py | 26 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-wireguard.py | 6 | ||||
-rwxr-xr-x | src/conf_mode/interfaces-wireless.py | 2 | ||||
-rwxr-xr-x | src/etc/netplug/linkdown.d/dhclient | 65 | ||||
-rwxr-xr-x | src/etc/netplug/linkup.d/dhclient | 64 | ||||
-rwxr-xr-x | src/etc/netplug/linkup.d/vyos-python-helper | 4 | ||||
-rwxr-xr-x | src/etc/netplug/netplug | 41 | ||||
-rw-r--r-- | src/etc/netplug/netplugd.conf | 3 | ||||
-rwxr-xr-x | src/etc/netplug/vyos-netplug-dhcp-client | 62 | ||||
-rwxr-xr-x | src/op_mode/pki.py | 3 |
13 files changed, 196 insertions, 193 deletions
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py index 280057f04..c4c72aae9 100755 --- a/src/conf_mode/dhcp_server.py +++ b/src/conf_mode/dhcp_server.py @@ -296,6 +296,10 @@ def generate(dhcp): render(config_file, 'dhcp-server/dhcpd.conf.j2', dhcp, formater=lambda _: _.replace(""", '"')) + # Clean up configuration test file + if os.path.exists(tmp_file): + os.unlink(tmp_file) + return None def apply(dhcp): diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index e946704b3..8ad3f27fc 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -351,39 +351,6 @@ def apply_sysfs(firewall): with open(path, 'w') as f: f.write(value) -def post_apply_trap(firewall): - if 'first_install' in firewall: - return None - - if not process_named_running('snmpd'): - return None - - trap_username = os.getlogin() - - for host, target_conf in firewall['trap_targets'].items(): - community = target_conf['community'] if 'community' in target_conf else 'public' - port = int(target_conf['port']) if 'port' in target_conf else 162 - - base_cmd = f'snmptrap -v2c -c {community} {host}:{port} 0 {snmp_trap_mib}::{snmp_trap_name} ' - - for change_type, changes in firewall['trap_diff'].items(): - for path_str, value in changes.items(): - objects = [ - f'mgmtEventUser s "{trap_username}"', - f'mgmtEventSource i {snmp_event_source}', - f'mgmtEventType i {snmp_change_type[change_type]}' - ] - - if change_type == 'add': - objects.append(f'mgmtEventCurrCfg s "{path_str} {value}"') - elif change_type == 'delete': - objects.append(f'mgmtEventPrevCfg s "{path_str} {value}"') - elif change_type == 'change': - objects.append(f'mgmtEventPrevCfg s "{path_str} {value[0]}"') - objects.append(f'mgmtEventCurrCfg s "{path_str} {value[1]}"') - - cmd(base_cmd + ' '.join(objects)) - def apply(firewall): install_result, output = rc_cmd(f'nft -f {nftables_conf}') if install_result == 1: @@ -408,8 +375,6 @@ def apply(firewall): print('Updating GeoIP. Please wait...') geoip_update(firewall) - post_apply_trap(firewall) - return None if __name__ == '__main__': diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py index 3f86e2638..0a927ac88 100755 --- a/src/conf_mode/interfaces-macsec.py +++ b/src/conf_mode/interfaces-macsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -43,6 +43,14 @@ airbag.enable() # XXX: wpa_supplicant works on the source interface wpa_suppl_conf = '/run/wpa_supplicant/{source_interface}.conf' +# Constants +## gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit +GCM_AES_128_LEN: int = 32 +GCM_128_KEY_ERROR = 'gcm-aes-128 requires a 128bit long key!' +## gcm-aes-256 requires a 256bit long key - 64 characters (string) = 32byte = 256bit +GCM_AES_256_LEN: int = 64 +GCM_256_KEY_ERROR = 'gcm-aes-256 requires a 256bit long key!' + def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the @@ -89,18 +97,54 @@ def verify(macsec): raise ConfigError('Cipher suite must be set for MACsec "{ifname}"'.format(**macsec)) if dict_search('security.encrypt', macsec) != None: - if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: - raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') + # Check that only static or MKA config is present + if dict_search('security.static', macsec) != None and (dict_search('security.mka.cak', macsec) != None or dict_search('security.mka.ckn', macsec) != None): + raise ConfigError('Only static or MKA can be used!') + + # Logic to check static configuration + if dict_search('security.static', macsec) != None: + # tx-key must be defined + if dict_search('security.static.key', macsec) == None: + raise ConfigError('Static MACsec tx-key must be defined.') + + tx_len = len(dict_search('security.static.key', macsec)) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and tx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and tx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Make sure at least one peer is defined + if 'peer' not in macsec['security']['static']: + raise ConfigError('Must have at least one peer defined for static MACsec') + + # For every enabled peer, make sure a MAC and rx-key is defined + for peer, peer_config in macsec['security']['static']['peer'].items(): + if 'disable' not in peer_config and ('mac' not in peer_config or 'key' not in peer_config): + raise ConfigError('Every enabled MACsec static peer must have a MAC address and rx-key defined.') + + # check rx-key length against cipher suite + rx_len = len(peer_config['key']) + + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and rx_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) + + if dict_search('security.cipher', macsec) == 'gcm-aes-256' and rx_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) + + # Logic to check MKA configuration + else: + if dict_search('security.mka.cak', macsec) == None or dict_search('security.mka.ckn', macsec) == None: + raise ConfigError('Missing mandatory MACsec security keys as encryption is enabled!') - cak_len = len(dict_search('security.mka.cak', macsec)) + cak_len = len(dict_search('security.mka.cak', macsec)) - if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != 32: - # gcm-aes-128 requires a 128bit long key - 32 characters (string) = 16byte = 128bit - raise ConfigError('gcm-aes-128 requires a 128bit long key!') + if dict_search('security.cipher', macsec) == 'gcm-aes-128' and cak_len != GCM_AES_128_LEN: + raise ConfigError(GCM_128_KEY_ERROR) - elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != 64: - # gcm-aes-128 requires a 128bit long key - 64 characters (string) = 32byte = 256bit - raise ConfigError('gcm-aes-128 requires a 256bit long key!') + elif dict_search('security.cipher', macsec) == 'gcm-aes-256' and cak_len != GCM_AES_256_LEN: + raise ConfigError(GCM_256_KEY_ERROR) if 'source_interface' in macsec: # MACsec adds a 40 byte overhead (32 byte MACsec + 8 bytes VLAN 802.1ad @@ -115,7 +159,9 @@ def verify(macsec): def generate(macsec): - render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) + # Only generate wpa_supplicant config if using MKA + if dict_search('security.mka.cak', macsec): + render(wpa_suppl_conf.format(**macsec), 'macsec/wpa_supplicant.conf.j2', macsec) return None @@ -142,8 +188,10 @@ def apply(macsec): i = MACsecIf(**macsec) i.update(macsec) - if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: - call(f'systemctl reload-or-restart {systemd_service}') + # Only reload/restart if using MKA + if dict_search('security.mka.cak', macsec): + if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec: + call(f'systemctl reload-or-restart {systemd_service}') return None diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 26b217d98..1d0feb56f 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -166,17 +166,23 @@ def verify_pki(openvpn): raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}') if tls: - if 'ca_certificate' not in tls: - raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}') - - for ca_name in tls['ca_certificate']: - if ca_name not in pki['ca']: - raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') + if (mode in ['server', 'client']) and ('ca_certificate' not in tls): + raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface},\ + it is required in server and client modes') + else: + if ('ca_certificate' not in tls) and ('peer_fingerprint' not in tls): + raise ConfigError('Either "tls ca-certificate" or "tls peer-fingerprint" is required\ + on openvpn interface {interface} in site-to-site mode') - if len(tls['ca_certificate']) > 1: - sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) - if not verify_ca_chain(sorted_chain, pki['ca']): - raise ConfigError(f'CA certificates are not a valid chain') + if 'ca_certificate' in tls: + for ca_name in tls['ca_certificate']: + if ca_name not in pki['ca']: + raise ConfigError(f'Invalid CA certificate on openvpn interface {interface}') + + if len(tls['ca_certificate']) > 1: + sorted_chain = sort_ca_chain(tls['ca_certificate'], pki['ca']) + if not verify_ca_chain(sorted_chain, pki['ca']): + raise ConfigError(f'CA certificates are not a valid chain') if mode != 'client' and 'auth_key' not in tls: if 'certificate' not in tls: diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py index 446399255..122d9589a 100755 --- a/src/conf_mode/interfaces-wireguard.py +++ b/src/conf_mode/interfaces-wireguard.py @@ -90,7 +90,6 @@ def verify(wireguard): # run checks on individual configured WireGuard peer public_keys = [] - for tmp in wireguard['peer']: peer = wireguard['peer'][tmp] @@ -107,8 +106,9 @@ def verify(wireguard): if peer['public_key'] in public_keys: raise ConfigError(f'Duplicate public-key defined on peer "{tmp}"') - if 'disable' not in peer and is_wireguard_key_pair(wireguard['private_key'], peer['public_key']): - raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"') + if 'disable' not in peer: + if is_wireguard_key_pair(wireguard['private_key'], peer['public_key']): + raise ConfigError(f'Peer "{tmp}" has the same public key as the interface "{wireguard["ifname"]}"') public_keys.append(peer['public_key']) diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py index e49ad25ac..29ab9713f 100755 --- a/src/conf_mode/interfaces-wireless.py +++ b/src/conf_mode/interfaces-wireless.py @@ -116,7 +116,7 @@ def verify(wifi): raise ConfigError('You must specify a WiFi mode') if 'ssid' not in wifi and wifi['type'] != 'monitor': - raise ConfigError('SSID must be configured') + raise ConfigError('SSID must be configured unless type is set to "monitor"!') if wifi['type'] == 'access-point': if 'country_code' not in wifi: diff --git a/src/etc/netplug/linkdown.d/dhclient b/src/etc/netplug/linkdown.d/dhclient deleted file mode 100755 index 555ff9134..000000000 --- a/src/etc/netplug/linkdown.d/dhclient +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/perl -# -# Module: dhclient -# -# **** License **** -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 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. -# -# A copy of the GNU General Public License is available as -# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution -# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. -# You can also obtain it by writing to the Free Software Foundation, -# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# This code was originally developed by Vyatta, Inc. -# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc. -# All Rights Reserved. -# -# Author: Mohit Mehta -# Date: November 2008 -# Description: Script to release lease on link down -# -# **** End License **** -# - -use lib "/opt/vyatta/share/perl5/"; -use Vyatta::Config; -use Vyatta::Misc; - -use strict; -use warnings; - -sub stop_dhclient { - my $intf = shift; - my $dhcp_daemon = '/sbin/dhclient'; - my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf); - my $release_cmd = "sudo $dhcp_daemon -q -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file -r $intf 2> /dev/null;"; - $release_cmd .= "sudo rm -f $intf_process_id_file 2> /dev/null"; - system ($release_cmd); -} - - -# -# main -# - -my $dev=shift; - -# only do this if interface is configured to use dhcp for getting IP address -if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) { - # do a dhcp lease release for interface - stop_dhclient($dev); -} - -exit 0; - -# end of file - diff --git a/src/etc/netplug/linkup.d/dhclient b/src/etc/netplug/linkup.d/dhclient deleted file mode 100755 index 8e50715fd..000000000 --- a/src/etc/netplug/linkup.d/dhclient +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/perl -# -# Module: dhclient -# -# **** License **** -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 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. -# -# A copy of the GNU General Public License is available as -# `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution -# or on the World Wide Web at `http://www.gnu.org/copyleft/gpl.html'. -# You can also obtain it by writing to the Free Software Foundation, -# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, -# MA 02110-1301, USA. -# -# This code was originally developed by Vyatta, Inc. -# Portions created by Vyatta are Copyright (C) 2008 Vyatta, Inc. -# All Rights Reserved. -# -# Author: Mohit Mehta -# Date: November 2008 -# Description: Script to renew lease on link up -# -# **** End License **** -# - -use lib "/opt/vyatta/share/perl5/"; -use Vyatta::Config; -use Vyatta::Misc; - -use strict; -use warnings; - -sub run_dhclient { - my $intf = shift; - my $dhcp_daemon = '/sbin/dhclient'; - my ($intf_config_file, $intf_process_id_file, $intf_leases_file) = Vyatta::Misc::generate_dhclient_intf_files($intf); - my $cmd = "sudo $dhcp_daemon -pf $intf_process_id_file -x $intf 2> /dev/null; sudo rm -f $intf_process_id_file 2> /dev/null;"; - $cmd .= "sudo $dhcp_daemon -q -nw -cf $intf_config_file -pf $intf_process_id_file -lf $intf_leases_file $intf 2> /dev/null &"; - system ($cmd); -} - -# -# main -# - -my $dev=shift; - -# only do this if interface is configured to use dhcp for getting IP address -if (Vyatta::Misc::is_dhcp_enabled($dev, "outside_cli")) { - # do a dhcp lease renew for interface - run_dhclient($dev); -} - -exit 0; - -# end of file - diff --git a/src/etc/netplug/linkup.d/vyos-python-helper b/src/etc/netplug/linkup.d/vyos-python-helper new file mode 100755 index 000000000..9c59c58ad --- /dev/null +++ b/src/etc/netplug/linkup.d/vyos-python-helper @@ -0,0 +1,4 @@ +#!/bin/sh +PYTHON3=$(which python3) +# Call the real python script and forward commandline arguments +$PYTHON3 /etc/netplug/vyos-netplug-dhcp-client "${@:1}" diff --git a/src/etc/netplug/netplug b/src/etc/netplug/netplug new file mode 100755 index 000000000..60b65e8c9 --- /dev/null +++ b/src/etc/netplug/netplug @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +dev="$1" +action="$2" + +case "$action" in +in) + run-parts --arg $dev --arg in /etc/netplug/linkup.d + ;; +out) + run-parts --arg $dev --arg out /etc/netplug/linkdown.d + ;; + +# probe loads and initialises the driver for the interface and brings the +# interface into the "up" state, so that it can generate netlink(7) events. +# This interferes with "admin down" for an interface. Thus, commented out. An +# "admin up" is treated as a "link up" and thus, "link up" action is executed. +# To execute "link down" action on "admin down", run appropriate script in +# /etc/netplug/linkdown.d +#probe) +# ;; + +*) + exit 1 + ;; +esac diff --git a/src/etc/netplug/netplugd.conf b/src/etc/netplug/netplugd.conf new file mode 100644 index 000000000..ab4d826d6 --- /dev/null +++ b/src/etc/netplug/netplugd.conf @@ -0,0 +1,3 @@ +eth* +br* +bond* diff --git a/src/etc/netplug/vyos-netplug-dhcp-client b/src/etc/netplug/vyos-netplug-dhcp-client new file mode 100755 index 000000000..55d15a163 --- /dev/null +++ b/src/etc/netplug/vyos-netplug-dhcp-client @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import sys + +from time import sleep + +from vyos.configquery import ConfigTreeQuery +from vyos.ifconfig import Section +from vyos.utils.boot import boot_configuration_complete +from vyos.utils.commit import commit_in_progress +from vyos.utils.process import call +from vyos import airbag +airbag.enable() + +if len(sys.argv) < 3: + airbag.noteworthy("Must specify both interface and link status!") + sys.exit(1) + +if not boot_configuration_complete(): + airbag.noteworthy("System bootup not yet finished...") + sys.exit(1) + +while commit_in_progress(): + sleep(1) + +interface = sys.argv[1] +in_out = sys.argv[2] +config = ConfigTreeQuery() + +interface_path = ['interfaces'] + Section.get_config_path(interface).split() + +for _, interface_config in config.get_config_dict(interface_path).items(): + # Bail out early if we do not have an IP address configured + if 'address' not in interface_config: + continue + # Bail out early if interface ist administrative down + if 'disable' in interface_config: + continue + systemd_action = 'start' + if in_out == 'out': + systemd_action = 'stop' + # Start/Stop DHCP service + if 'dhcp' in interface_config['address']: + call(f'systemctl {systemd_action} dhclient@{interface}.service') + # Start/Stop DHCPv6 service + if 'dhcpv6' in interface_config['address']: + call(f'systemctl {systemd_action} dhcp6c@{interface}.service') diff --git a/src/op_mode/pki.py b/src/op_mode/pki.py index aff4ad1ae..35c7ce0e2 100755 --- a/src/op_mode/pki.py +++ b/src/op_mode/pki.py @@ -25,7 +25,6 @@ from cryptography import x509 from cryptography.x509.oid import ExtendedKeyUsageOID from vyos.config import Config -from vyos.configquery import ConfigTreeQuery from vyos.pki import encode_certificate, encode_public_key, encode_private_key, encode_dh_parameters from vyos.pki import get_certificate_fingerprint from vyos.pki import create_certificate, create_certificate_request, create_certificate_revocation_list @@ -43,7 +42,7 @@ CERT_REQ_END = '-----END CERTIFICATE REQUEST-----' auth_dir = '/config/auth' # Helper Functions -conf = ConfigTreeQuery() +conf = Config() def get_default_values(): # Fetch default x509 values base = ['pki', 'x509', 'default'] |