summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/frr/bgpd.frr.j232
-rw-r--r--data/templates/frr/daemons.frr.tmpl3
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j213
-rw-r--r--data/templates/ocserv/ocserv_config.j25
-rw-r--r--debian/control2
-rw-r--r--interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i14
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i86
-rw-r--r--interface-definitions/include/firewall/action.xml.i8
-rw-r--r--interface-definitions/include/firewall/ipv4-custom-name.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv6-custom-name.xml.i1
-rw-r--r--interface-definitions/include/pki/certificate-multi.xml.i15
-rw-r--r--interface-definitions/load-balancing-haproxy.xml.in2
-rw-r--r--interface-definitions/system-frr.xml.in14
-rw-r--r--interface-definitions/vpn-openconnect.xml.in6
-rw-r--r--python/vyos/ifconfig/interface.py2
-rw-r--r--python/vyos/system/disk.py5
-rw-r--r--python/vyos/system/grub.py2
-rw-r--r--python/vyos/system/raid.py31
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py36
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py56
-rwxr-xr-xsmoketest/scripts/cli/test_system_frr.py26
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py21
-rwxr-xr-xsrc/conf_mode/load-balancing-haproxy.py20
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py23
-rwxr-xr-xsrc/conf_mode/system_frr.py4
-rwxr-xr-xsrc/helpers/simple-download.py20
-rwxr-xr-xsrc/op_mode/image_installer.py55
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()