diff options
27 files changed, 449 insertions, 54 deletions
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2 index 641dac44a..679ba8b04 100644 --- a/data/templates/frr/bgpd.frr.j2 +++ b/data/templates/frr/bgpd.frr.j2 @@ -470,6 +470,38 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }} {% endfor %} {% endif %} ! +{% if bmp is vyos_defined %} +{% if bmp.mirror_buffer_limit is vyos_defined %} + bmp mirror buffer-limit {{ bmp.mirror_buffer_limit }} + ! +{% endif %} +{% if bmp.target is vyos_defined %} +{% for bmp, bmp_config in bmp.target.items() %} + bmp targets {{ bmp }} +{% if bmp_config.mirror is vyos_defined %} + bmp mirror +{% endif %} +{% if bmp_config.monitor is vyos_defined %} +{% if bmp_config.monitor.ipv4_unicast.pre_policy is vyos_defined %} + bmp monitor ipv4 unicast pre-policy +{% endif %} +{% if bmp_config.monitor.ipv4_unicast.post_policy is vyos_defined %} + bmp monitor ipv4 unicast post-policy +{% endif %} +{% if bmp_config.monitor.ipv6_unicast.pre_policy is vyos_defined %} + bmp monitor ipv6 unicast pre-policy +{% endif %} +{% if bmp_config.monitor.ipv6_unicast.post_policy is vyos_defined %} + bmp monitor ipv6 unicast post-policy +{% endif %} +{% endif %} +{% if bmp_config.address is vyos_defined %} + bmp connect {{ bmp_config.address }} port {{ bmp_config.port }} min-retry {{ bmp_config.min_retry }} max-retry {{ bmp_config.max_retry }} +{% endif %} +{% endfor %} + exit +{% endif %} +{% endif %} {% if peer_group is vyos_defined %} {% for peer, config in peer_group.items() %} {{ bgp_neighbor(peer, config, true) }} diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl index a65f0868a..c637e18bc 100644 --- a/data/templates/frr/daemons.frr.tmpl +++ b/data/templates/frr/daemons.frr.tmpl @@ -108,7 +108,6 @@ valgrind_enable=no frr_profile="traditional" -#MAX_FDS=1024 +MAX_FDS={{ descriptors }} #FRR_NO_ROOT="yes" - diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2 index a75ee9904..defb76fba 100644 --- a/data/templates/load-balancing/haproxy.cfg.j2 +++ b/data/templates/load-balancing/haproxy.cfg.j2 @@ -50,13 +50,19 @@ defaults {% if service is vyos_defined %} {% for front, front_config in service.items() %} frontend {{ front }} -{% set ssl_front = 'ssl crt /run/haproxy/' ~ front_config.ssl.certificate ~ '.pem' if front_config.ssl.certificate is vyos_defined else '' %} +{% set ssl_front = [] %} +{% if front_config.ssl.certificate is vyos_defined and front_config.ssl.certificate is iterable %} +{% for cert in front_config.ssl.certificate %} +{% set _ = ssl_front.append('crt /run/haproxy/' ~ cert ~ '.pem') %} +{% endfor %} +{% endif %} +{% set ssl_directive = 'ssl' if ssl_front else '' %} {% if front_config.listen_address is vyos_defined %} {% for address in front_config.listen_address %} - bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_front }} + bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_directive }} {{ ssl_front | join(' ') }} {% endfor %} {% else %} - bind :::{{ front_config.port }} v4v6 {{ ssl_front }} + bind :::{{ front_config.port }} v4v6 {{ ssl_directive }} {{ ssl_front | join(' ') }} {% endif %} {% if front_config.redirect_http_to_https is vyos_defined %} http-request redirect scheme https unless { ssl_fc } @@ -161,4 +167,3 @@ backend {{ back }} {% endfor %} {% endif %} - diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2 index 80ba357bc..b5e890c32 100644 --- a/data/templates/ocserv/ocserv_config.j2 +++ b/data/templates/ocserv/ocserv_config.j2 @@ -121,12 +121,12 @@ select-group = {{ grp }} {% endfor %} {% endif %} - +{% if http_security_headers is vyos_defined %} # HTTP security headers included-http-headers = Strict-Transport-Security: max-age=31536000 ; includeSubDomains included-http-headers = X-Frame-Options: deny included-http-headers = X-Content-Type-Options: nosniff -included-http-headers = Content-Security-Policy: default-src ´none´ +included-http-headers = Content-Security-Policy: default-src "none" included-http-headers = X-Permitted-Cross-Domain-Policies: none included-http-headers = Referrer-Policy: no-referrer included-http-headers = Clear-Site-Data: "cache","cookies","storage" @@ -136,3 +136,4 @@ included-http-headers = Cross-Origin-Resource-Policy: same-origin included-http-headers = X-XSS-Protection: 0 included-http-headers = Pragma: no-cache included-http-headers = Cache-control: no-store, no-cache +{% endif %} diff --git a/debian/control b/debian/control index 816d41944..08adc8a68 100644 --- a/debian/control +++ b/debian/control @@ -279,6 +279,8 @@ Depends: # For "run monitor traffic" tcpdump, # End "run monitor traffic" +# For "show hardware dmi" + dmidecode, # For "run show hardware storage smart" smartmontools, # For "run show hardware scsi" diff --git a/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i new file mode 100644 index 000000000..261d60232 --- /dev/null +++ b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i @@ -0,0 +1,14 @@ +<!-- include start from bgp/bmp-monitor-afi-policy.xml.i --> +<leafNode name="pre-policy"> + <properties> + <help>Send state before policy and filter processing</help> + <valueless/> + </properties> +</leafNode> +<leafNode name="post-policy"> + <properties> + <help>Send state with policy and filters applied</help> + <valueless/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index c379ba95c..dce61ee77 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -909,6 +909,92 @@ </node> </children> </node> +<node name="bmp"> + <properties> + <help>BGP Monitoring Protocol (BMP)</help> + </properties> + <children> + <leafNode name="mirror-buffer-limit"> + <properties> + <help>Maximum memory used for buffered mirroring messages (in bytes)</help> + <valueHelp> + <format>u32:0-4294967294</format> + <description>Limit in bytes</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 0-4294967294"/> + </constraint> + </properties> + </leafNode> + <tagNode name="target"> + <properties> + <help>BMP target</help> + </properties> + <children> + #include <include/address-ipv4-ipv6-single.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>5000</defaultValue> + </leafNode> + <leafNode name="min-retry"> + <properties> + <help>Minimum connection retry interval (in milliseconds)</help> + <valueHelp> + <format>u32:100-86400000</format> + <description>Minimum connection retry interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-86400000"/> + </constraint> + </properties> + <defaultValue>1000</defaultValue> + </leafNode> + <leafNode name="max-retry"> + <properties> + <help>Maximum connection retry interval</help> + <valueHelp> + <format>u32:100-4294967295</format> + <description>Maximum connection retry interval</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 100-86400000"/> + </constraint> + </properties> + <defaultValue>2000</defaultValue> + </leafNode> + <leafNode name="mirror"> + <properties> + <help>Send BMP route mirroring messages</help> + <valueless/> + </properties> + </leafNode> + <node name="monitor"> + <properties> + <help>Send BMP route monitoring messages</help> + </properties> + <children> + <node name="ipv4-unicast"> + <properties> + <help>Address family IPv4 unicast</help> + </properties> + <children> + #include <include/bgp/bmp-monitor-afi-policy.xml.i> + </children> + </node> + <node name="ipv6-unicast"> + <properties> + <help>Address family IPv6 unicast</help> + </properties> + <children> + #include <include/bgp/bmp-monitor-afi-policy.xml.i> + </children> + </node> + </children> + </node> + </children> + </tagNode> + </children> +</node> <tagNode name="interface"> <properties> <help>Configure interface related parameters, e.g. MPLS</help> diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i index 954e4f23e..e1f0c6cb6 100644 --- a/interface-definitions/include/firewall/action.xml.i +++ b/interface-definitions/include/firewall/action.xml.i @@ -3,7 +3,7 @@ <properties> <help>Rule action</help> <completionHelp> - <list>accept continue jump reject return drop queue synproxy</list> + <list>accept continue jump reject return drop queue offload synproxy</list> </completionHelp> <valueHelp> <format>accept</format> @@ -34,11 +34,15 @@ <description>Enqueue packet to userspace</description> </valueHelp> <valueHelp> + <format>offload</format> + <description>Offload packet via flowtable</description> + </valueHelp> + <valueHelp> <format>synproxy</format> <description>Synproxy connections</description> </valueHelp> <constraint> - <regex>(accept|continue|jump|reject|return|drop|queue|synproxy)</regex> + <regex>(accept|continue|jump|reject|return|drop|queue|offload|synproxy)</regex> </constraint> </properties> </leafNode> diff --git a/interface-definitions/include/firewall/ipv4-custom-name.xml.i b/interface-definitions/include/firewall/ipv4-custom-name.xml.i index 9d6ecfaf2..c6420fe1f 100644 --- a/interface-definitions/include/firewall/ipv4-custom-name.xml.i +++ b/interface-definitions/include/firewall/ipv4-custom-name.xml.i @@ -33,6 +33,7 @@ <children> #include <include/firewall/common-rule-ipv4.xml.i> #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/offload-target.xml.i> #include <include/firewall/outbound-interface.xml.i> </children> </tagNode> diff --git a/interface-definitions/include/firewall/ipv6-custom-name.xml.i b/interface-definitions/include/firewall/ipv6-custom-name.xml.i index 81610babf..2cc45a60c 100644 --- a/interface-definitions/include/firewall/ipv6-custom-name.xml.i +++ b/interface-definitions/include/firewall/ipv6-custom-name.xml.i @@ -33,6 +33,7 @@ <children> #include <include/firewall/common-rule-ipv6.xml.i> #include <include/firewall/inbound-interface.xml.i> + #include <include/firewall/offload-target.xml.i> #include <include/firewall/outbound-interface.xml.i> </children> </tagNode> diff --git a/interface-definitions/include/pki/certificate-multi.xml.i b/interface-definitions/include/pki/certificate-multi.xml.i new file mode 100644 index 000000000..c49c5d9b2 --- /dev/null +++ b/interface-definitions/include/pki/certificate-multi.xml.i @@ -0,0 +1,15 @@ +<!-- include start from pki/certificate-multi.xml.i --> +<leafNode name="certificate"> + <properties> + <help>Certificate in PKI configuration</help> + <completionHelp> + <path>pki certificate</path> + </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of certificate in PKI configuration</description> + </valueHelp> + <multi/> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in index 564c335ec..8f6bd3a99 100644 --- a/interface-definitions/load-balancing-haproxy.xml.in +++ b/interface-definitions/load-balancing-haproxy.xml.in @@ -49,7 +49,7 @@ <help>SSL Certificate, SSL Key and CA</help> </properties> <children> - #include <include/pki/certificate.xml.i> + #include <include/pki/certificate-multi.xml.i> </children> </node> </children> diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in index 9fe23ed75..76001b392 100644 --- a/interface-definitions/system-frr.xml.in +++ b/interface-definitions/system-frr.xml.in @@ -15,6 +15,20 @@ <valueless/> </properties> </leafNode> + <leafNode name="descriptors"> + <properties> + <help>Number of open file descriptors a process is allowed to use</help> + <valueHelp> + <format>u32:1024-8192</format> + <description>Number of file descriptors</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1024-8192"/> + </constraint> + <constraintErrorMessage>Port number must be in range 1024 to 8192</constraintErrorMessage> + </properties> + <defaultValue>1024</defaultValue> + </leafNode> <leafNode name="irdp"> <properties> <help>Enable ICMP Router Discovery Protocol support</help> diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in index 75c64a99a..736084f8b 100644 --- a/interface-definitions/vpn-openconnect.xml.in +++ b/interface-definitions/vpn-openconnect.xml.in @@ -260,6 +260,12 @@ </leafNode> </children> </node> + <leafNode name="http-security-headers"> + <properties> + <help>Enable HTTP security headers</help> + <valueless/> + </properties> + </leafNode> <node name="ssl"> <properties> <help>SSL Certificate, SSL Key and CA</help> diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1586710db..56dcde214 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -115,7 +115,7 @@ class Interface(Control): }, 'vrf': { 'shellcmd': 'ip -json -detail link list dev {ifname}', - 'format': lambda j: jmespath.search('[*].master | [0]', json.loads(j)), + 'format': lambda j: jmespath.search('[?linkinfo.info_slave_kind == `vrf`].master | [0]', json.loads(j)), }, } diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py index f8e0fd1bf..b8a2c0f35 100644 --- a/python/vyos/system/disk.py +++ b/python/vyos/system/disk.py @@ -31,12 +31,17 @@ class DiskDetails: def disk_cleanup(drive_path: str) -> None: """Clean up disk partition table (MBR and GPT) + Remove partition and device signatures. Zeroize primary and secondary headers - first and last 17408 bytes (512 bytes * 34 LBA) on a drive Args: drive_path (str): path to a drive that needs to be cleaned """ + partitions: list[str] = partition_list(drive_path) + for partition in partitions: + run(f'wipefs -af {partition}') + run(f'wipefs -af {drive_path}') run(f'sgdisk -Z {drive_path}') diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py index 0ac16af9a..2692aaea1 100644 --- a/python/vyos/system/grub.py +++ b/python/vyos/system/grub.py @@ -138,6 +138,8 @@ def version_list(root_dir: str = '') -> list[str]: versions_list: list[str] = [] for file in versions_files: versions_list.append(file.stem) + versions_list.sort(reverse=True) + return versions_list diff --git a/python/vyos/system/raid.py b/python/vyos/system/raid.py index 13b99fa69..5b33d34da 100644 --- a/python/vyos/system/raid.py +++ b/python/vyos/system/raid.py @@ -19,7 +19,7 @@ from pathlib import Path from shutil import copy from dataclasses import dataclass -from vyos.utils.process import cmd +from vyos.utils.process import cmd, run from vyos.system import disk @@ -44,18 +44,11 @@ def raid_create(raid_members: list[str], """ raid_devices_num: int = len(raid_members) raid_members_str: str = ' '.join(raid_members) - if Path('/sys/firmware/efi').exists(): - for part in raid_members: - drive: str = disk.partition_parent(part) - command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}' - cmd(command) - else: - for part in raid_members: - drive: str = disk.partition_parent(part) - command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}' - cmd(command) for part in raid_members: - command: str = f'mdadm --zero-superblock {part}' + drive: str = disk.partition_parent(part) + # set partition type GUID for raid member; cf. + # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs + command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}' cmd(command) command: str = f'mdadm --create /dev/{raid_name} -R --metadata=1.0 \ --raid-devices={raid_devices_num} --level={raid_level} \ @@ -72,6 +65,20 @@ def raid_create(raid_members: list[str], return raid +def clear(): + """Deactivate all RAID arrays""" + command: str = 'mdadm --examine --scan' + raid_config = cmd(command) + if not raid_config: + return + command: str = 'mdadm --run /dev/md?*' + run(command) + command: str = 'mdadm --assemble --scan --auto=yes --symlink=no' + run(command) + command: str = 'mdadm --stop --scan' + run(command) + + def update_initramfs() -> None: """Update initramfs""" mdadm_script = '/etc/initramfs-tools/scripts/local-top/mdadm' diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 066ed707b..5cfddb269 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -753,5 +753,41 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') + def test_zone_flow_offload(self): + self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0']) + self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware']) + + # QEMU virtual NIC does not support hw-tc-offload + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software']) + + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) + + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'action', 'offload']) + self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest']) + + self.cli_commit() + + nftables_search = [ + ['chain NAME_smoketest'], + ['flow add @VYOS_FLOWTABLE_smoketest'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + nftables_search = [ + ['chain NAME6_smoketest'], + ['flow add @VYOS_FLOWTABLE_smoketest'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_filter') + + # Check conntrack + self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK') + self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 97dab255e..8102a3153 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -15,6 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest +from subprocess import run from base_vyostest_shim import VyOSUnitTestSHIM @@ -1148,5 +1149,60 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' locator {locator_name}', frrconfig) self.assertIn(f' sid vpn per-vrf export {sid}', frrconfig) + def test_bgp_25_bmp(self): + target_name = 'instance-bmp' + target_address = '127.0.0.1' + target_port = '5000' + min_retry = '1024' + max_retry = '2048' + monitor_ipv4 = 'pre-policy' + monitor_ipv6 = 'pre-policy' + mirror_buffer = '32000000' + bmp_path = base_path + ['bmp'] + target_path = bmp_path + ['target', target_name] + bgpd_bmp_pid = process_named_running('bgpd', 'bmp') + command = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'restart', 'bgp'] + + self.cli_set(bmp_path) + # by default the 'bmp' module not loaded for the bgpd + # expect Error + if not bgpd_bmp_pid: + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # add required 'bmp' module to bgpd and restart bgpd + self.cli_delete(bmp_path) + self.cli_set(['system', 'frr', 'bmp']) + self.cli_commit() + # restart bgpd to apply "-M bmp" and update PID + run(command, input='Y', text=True) + self.daemon_pid = process_named_running(PROCESS_NAME) + + # set bmp config but not set address + self.cli_set(target_path + ['port', target_port]) + # address is not set, expect Error + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # config other bmp options + self.cli_set(target_path + ['address', target_address]) + self.cli_set(bmp_path + ['mirror-buffer-limit', mirror_buffer]) + self.cli_set(target_path + ['port', target_port]) + self.cli_set(target_path + ['min-retry', min_retry]) + self.cli_set(target_path + ['max-retry', max_retry]) + self.cli_set(target_path + ['mirror']) + self.cli_set(target_path + ['monitor', 'ipv4-unicast', monitor_ipv4]) + self.cli_set(target_path + ['monitor', 'ipv6-unicast', monitor_ipv6]) + self.cli_commit() + + # Verify bgpd bmp configuration + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + self.assertIn(f'bmp mirror buffer-limit {mirror_buffer}', frrconfig) + self.assertIn(f'bmp targets {target_name}', frrconfig) + self.assertIn(f'bmp mirror', frrconfig) + self.assertIn(f'bmp monitor ipv4 unicast {monitor_ipv4}', frrconfig) + self.assertIn(f'bmp monitor ipv6 unicast {monitor_ipv6}', frrconfig) + self.assertIn(f'bmp connect {target_address} port {target_port} min-retry {min_retry} max-retry {max_retry}', frrconfig) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py index 3eb0cd0ab..a2ce58bf6 100755 --- a/smoketest/scripts/cli/test_system_frr.py +++ b/smoketest/scripts/cli/test_system_frr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -16,13 +16,13 @@ import re import unittest + from base_vyostest_shim import VyOSUnitTestSHIM from vyos.utils.file import read_file config_file = '/etc/frr/daemons' base_path = ['system', 'frr'] - def daemons_config_parse(daemons_config): # create regex for parsing daemons options regex_daemon_config = re.compile( @@ -33,13 +33,20 @@ def daemons_config_parse(daemons_config): for daemon in regex_daemon_config.finditer(daemons_config): daemon_name = daemon.group('daemon_name') daemon_options = daemon.group('daemon_options') - daemons_config_dict[daemon_name] = daemon_options + daemons_config_dict[daemon_name] = daemon_options.lstrip() # return daemons config return (daemons_config_dict) class TestSystemFRR(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemFRR, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) def tearDown(self): self.cli_delete(base_path) @@ -64,7 +71,7 @@ class TestSystemFRR(VyOSUnitTestSHIM.TestCase): else: self.assertFalse(snmp_enabled) - def test_frr_snmp_addandremove(self): + def test_frr_snmp_add_remove(self): # test enabling and disabling of SNMP integration test_daemon_names = ['ospfd', 'bgpd'] for test_daemon_name in test_daemon_names: @@ -124,7 +131,7 @@ class TestSystemFRR(VyOSUnitTestSHIM.TestCase): irdp_enabled = regex_irdp.match(daemons_config_dict['zebra']) self.assertTrue(irdp_enabled) - def test_frr_bmpandsnmp(self): + def test_frr_bmp_and_snmp(self): # test empty config section self.cli_set(base_path + ['bmp']) self.cli_set(base_path + ['snmp', 'bgpd']) @@ -141,6 +148,15 @@ class TestSystemFRR(VyOSUnitTestSHIM.TestCase): self.assertTrue(bmp_enabled) self.assertTrue(snmp_enabled) + def test_frr_file_descriptors(self): + file_descriptors = '4096' + + self.cli_set(base_path + ['descriptors', file_descriptors]) + self.cli_commit() + + # read the config file and check content + daemons_config = read_file(config_file) + self.assertIn(f'MAX_FDS={file_descriptors}', daemons_config) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index 04abeb1aa..c4502fada 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -141,5 +141,26 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): otp_config = read_file(otp_file) self.assertIn(f'HOTP/T30/6 {user} - {otp}', otp_config) + + # Verify HTTP security headers + self.cli_set(base_path + ['http-security-headers']) + self.cli_commit() + + daemon_config = read_file(config_file) + + self.assertIn('included-http-headers = Strict-Transport-Security: max-age=31536000 ; includeSubDomains', daemon_config) + self.assertIn('included-http-headers = X-Frame-Options: deny', daemon_config) + self.assertIn('included-http-headers = X-Content-Type-Options: nosniff', daemon_config) + self.assertIn('included-http-headers = Content-Security-Policy: default-src "none"', daemon_config) + self.assertIn('included-http-headers = X-Permitted-Cross-Domain-Policies: none', daemon_config) + self.assertIn('included-http-headers = Referrer-Policy: no-referrer', daemon_config) + self.assertIn('included-http-headers = Clear-Site-Data: "cache","cookies","storage"', daemon_config) + self.assertIn('included-http-headers = Cross-Origin-Embedder-Policy: require-corp', daemon_config) + self.assertIn('included-http-headers = Cross-Origin-Opener-Policy: same-origin', daemon_config) + self.assertIn('included-http-headers = Cross-Origin-Resource-Policy: same-origin', daemon_config) + self.assertIn('included-http-headers = X-XSS-Protection: 0', daemon_config) + self.assertIn('included-http-headers = Pragma: no-cache', daemon_config) + self.assertIn('included-http-headers = Cache-control: no-store, no-cache', daemon_config) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py index ec4311bb5..333ebc66c 100755 --- a/src/conf_mode/load-balancing-haproxy.py +++ b/src/conf_mode/load-balancing-haproxy.py @@ -108,17 +108,19 @@ def generate(lb): if 'ssl' in front_config: if 'certificate' in front_config['ssl']: - cert_name = front_config['ssl']['certificate'] - pki_cert = lb['pki']['certificate'][cert_name] - cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem') - cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key') + cert_names = front_config['ssl']['certificate'] - with open(cert_file_path, 'w') as f: - f.write(wrap_certificate(pki_cert['certificate'])) + for cert_name in cert_names: + pki_cert = lb['pki']['certificate'][cert_name] + cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem') + cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key') - if 'private' in pki_cert and 'key' in pki_cert['private']: - with open(cert_key_path, 'w') as f: - f.write(wrap_private_key(pki_cert['private']['key'])) + with open(cert_file_path, 'w') as f: + f.write(wrap_certificate(pki_cert['certificate'])) + + if 'private' in pki_cert and 'key' in pki_cert['private']: + with open(cert_key_path, 'w') as f: + f.write(wrap_private_key(pki_cert['private']['key'])) if 'ca_certificate' in front_config['ssl']: ca_name = front_config['ssl']['ca_certificate'] diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py index 557f0a9e9..bf807fa5f 100755 --- a/src/conf_mode/protocols_bgp.py +++ b/src/conf_mode/protocols_bgp.py @@ -30,6 +30,7 @@ from vyos.template import render_to_string from vyos.utils.dict import dict_search from vyos.utils.network import get_interface_vrf from vyos.utils.network import is_addr_assigned +from vyos.utils.process import process_named_running from vyos import ConfigError from vyos import frr from vyos import airbag @@ -49,8 +50,13 @@ def get_config(config=None): # eqivalent of the C foo ? 'a' : 'b' statement base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path - bgp = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + bgp = conf.get_config_dict( + base, + key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True, + ) bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'], key_mangling=('-', '_'), @@ -247,6 +253,19 @@ def verify(bgp): if 'system_as' not in bgp: raise ConfigError('BGP system-as number must be defined!') + # Verify BMP + if 'bmp' in bgp: + # check bmp flag "bgpd -d -F traditional --daemon -A 127.0.0.1 -M rpki -M bmp" + if not process_named_running('bgpd', 'bmp'): + raise ConfigError( + f'"bmp" flag is not found in bgpd. Configure "set system frr bmp" and restart bgp process' + ) + # check bmp target + if 'target' in bgp['bmp']: + for target, target_config in bgp['bmp']['target'].items(): + if 'address' not in target_config: + raise ConfigError(f'BMP target "{target}" address must be defined!') + # Verify vrf on interface and bgp section if 'interface' in bgp: for interface in bgp['interface']: diff --git a/src/conf_mode/system_frr.py b/src/conf_mode/system_frr.py index 6727b63c2..07f291000 100755 --- a/src/conf_mode/system_frr.py +++ b/src/conf_mode/system_frr.py @@ -40,7 +40,9 @@ def get_config(config=None): conf = Config() base = ['system', 'frr'] - frr_config = conf.get_config_dict(base, get_first_key=True) + frr_config = conf.get_config_dict(base, key_mangling=('-', '_'), + get_first_key=True, + with_recursive_defaults=True) return frr_config diff --git a/src/helpers/simple-download.py b/src/helpers/simple-download.py new file mode 100755 index 000000000..501af75f5 --- /dev/null +++ b/src/helpers/simple-download.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +import sys +from argparse import ArgumentParser +from vyos.remote import download + +parser = ArgumentParser() +parser.add_argument('--local-file', help='local file', required=True) +parser.add_argument('--remote-path', help='remote path', required=True) + +args = parser.parse_args() + +try: + download(args.local_file, args.remote_path, + check_space=True, raise_error=True) +except Exception as e: + print(e) + sys.exit(1) + +sys.exit() diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py index 9452c5e28..6a8797aec 100755 --- a/src/op_mode/image_installer.py +++ b/src/op_mode/image_installer.py @@ -22,6 +22,7 @@ from pathlib import Path from shutil import copy, chown, rmtree, copytree from glob import glob from sys import exit +from os import environ from time import sleep from typing import Union from urllib.parse import urlparse @@ -83,6 +84,8 @@ DIR_KERNEL_SRC: str = '/boot/' FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs' ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso' +external_download_script = '/usr/libexec/vyos/simple-download.py' + # default boot variables DEFAULT_BOOT_VARS: dict[str, str] = { 'timeout': '5', @@ -179,6 +182,7 @@ def create_partitions(target_disk: str, target_size: int, rootfs_size: int = available_size print(MSG_INFO_INSTALL_PARTITONING) + raid.clear() disk.disk_cleanup(target_disk) disk_details: disk.DiskDetails = disk.parttable_create(target_disk, rootfs_size) @@ -459,8 +463,23 @@ def validate_signature(file_path: str, sign_type: str) -> None: else: print('Signature is valid') - -def image_fetch(image_path: str, no_prompt: bool = False) -> Path: +def download_file(local_file: str, remote_path: str, vrf: str, + username: str, password: str, + progressbar: bool = False, check_space: bool = False): + environ['REMOTE_USERNAME'] = username + environ['REMOTE_PASSWORD'] = password + if vrf is None: + download(local_file, remote_path, progressbar=progressbar, + check_space=check_space, raise_error=True) + else: + vrf_cmd = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \ + ip vrf exec {vrf} {external_download_script} \ + --local-file {local_file} --remote-path {remote_path}' + cmd(vrf_cmd) + +def image_fetch(image_path: str, vrf: str = None, + username: str = '', password: str = '', + no_prompt: bool = False) -> Path: """Fetch an ISO image Args: @@ -473,14 +492,17 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path: # check a type of path if urlparse(image_path).scheme: # download an image - download(ISO_DOWNLOAD_PATH, image_path, True, True, - raise_error=True) + download_file(ISO_DOWNLOAD_PATH, image_path, vrf, + username, password, + progressbar=True, check_space=True) + # download a signature sign_file = (False, '') for sign_type in ['minisig', 'asc']: try: - download(f'{ISO_DOWNLOAD_PATH}.{sign_type}', - f'{image_path}.{sign_type}', raise_error=True) + download_file(f'{ISO_DOWNLOAD_PATH}.{sign_type}', + f'{image_path}.{sign_type}', vrf, + username, password) sign_file = (True, sign_type) break except Exception: @@ -501,8 +523,8 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path: return local_path else: raise FileNotFoundError - except Exception: - print(f'The image cannot be fetched from: {image_path}') + except Exception as e: + print(f'The image cannot be fetched from: {image_path} {e}') exit(1) @@ -731,7 +753,8 @@ def install_image() -> None: @compat.grub_cfg_update -def add_image(image_path: str, no_prompt: bool = False) -> None: +def add_image(image_path: str, vrf: str = None, username: str = '', + password: str = '', no_prompt: bool = False) -> None: """Add a new image Args: @@ -741,7 +764,7 @@ def add_image(image_path: str, no_prompt: bool = False) -> None: exit(MSG_ERR_LIVE) # fetch an image - iso_path: Path = image_fetch(image_path, no_prompt) + iso_path: Path = image_fetch(image_path, vrf, username, password, no_prompt) try: # mount an ISO Path(DIR_ISO_MOUNT).mkdir(mode=0o755, parents=True) @@ -841,10 +864,15 @@ def parse_arguments() -> Namespace: choices=['install', 'add'], required=True, help='action to perform with an image') + parser.add_argument('--vrf', + help='vrf name for image download') parser.add_argument('--no-prompt', action='store_true', help='perform action non-interactively') - parser.add_argument( - '--image-path', + parser.add_argument('--username', default='', + help='username for image download') + parser.add_argument('--password', default='', + help='password for image download') + parser.add_argument('--image-path', help='a path (HTTP or local file) to an image that needs to be installed' ) # parser.add_argument('--image_new_name', help='a new name for image') @@ -862,7 +890,8 @@ if __name__ == '__main__': if args.action == 'install': install_image() if args.action == 'add': - add_image(args.image_path, args.no_prompt) + add_image(args.image_path, args.vrf, + args.username, args.password, args.no_prompt) exit() |