summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/accel-ppp/ipoe.config.j28
-rw-r--r--debian/vyos-1x-smoketest.install1
-rw-r--r--interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i2
-rw-r--r--interface-definitions/include/interface/address-ipv4-ipv6.xml.i2
-rw-r--r--interface-definitions/include/pki/ca-certificate-multi.xml.i15
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in2
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in2
-rw-r--r--interface-definitions/interfaces-pseudo-ethernet.xml.in2
-rw-r--r--interface-definitions/interfaces-vti.xml.in16
-rw-r--r--interface-definitions/service-ipoe-server.xml.in5
-rw-r--r--op-mode-definitions/show-ip.xml.in6
-rw-r--r--python/vyos/ifconfig/vti.py6
-rw-r--r--python/vyos/pki.py61
-rw-r--r--python/vyos/util.py4
-rwxr-xr-xsmoketest/bin/vyos-configtest-pki100
-rw-r--r--smoketest/configs/dialup-router-medium-vpn5
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py3
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py74
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vti.py34
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py43
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py4
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py14
-rwxr-xr-xsrc/migration-scripts/interfaces/24-to-2537
-rwxr-xr-xsrc/op_mode/show_ip_external.sh19
-rwxr-xr-xsrc/system/vyos-event-handler.py12
25 files changed, 398 insertions, 79 deletions
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index 3c0d47b27..6df12db2c 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -128,10 +128,16 @@ bind={{ radius_source_address }}
{% if radius_dynamic_author %}
dae-server={{ radius_dynamic_author.server }}:{{ radius_dynamic_author.port }},{{ radius_dynamic_author.key }}
{% endif %}
-{% if radius_shaper_attr %}
+
+{% if radius_shaper_enable %}
[shaper]
verbose=1
+{% if radius_shaper_attr %}
attr={{ radius_shaper_attr }}
+{% endif %}
+{% if radius_shaper_multiplier %}
+rate-multiplier={{ radius_shaper_multiplier }}
+{% endif %}
{% if radius_shaper_vendor %}
vendor={{ radius_shaper_vendor }}
{% endif %}
diff --git a/debian/vyos-1x-smoketest.install b/debian/vyos-1x-smoketest.install
index 3739763b9..406fef4be 100644
--- a/debian/vyos-1x-smoketest.install
+++ b/debian/vyos-1x-smoketest.install
@@ -1,4 +1,5 @@
usr/bin/vyos-smoketest
usr/bin/vyos-configtest
+usr/bin/vyos-configtest-pki
usr/libexec/vyos/tests/smoke
usr/libexec/vyos/tests/config
diff --git a/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i
index b9dd59bea..5057ed9ae 100644
--- a/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i
+++ b/interface-definitions/include/interface/address-ipv4-ipv6-dhcp.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from address-ipv4-ipv6-dhcp.xml.i -->
+<!-- include start from interface/address-ipv4-ipv6-dhcp.xml.i -->
<leafNode name="address">
<properties>
<help>IP address</help>
diff --git a/interface-definitions/include/interface/address-ipv4-ipv6.xml.i b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i
index 519622050..d689da5aa 100644
--- a/interface-definitions/include/interface/address-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/interface/address-ipv4-ipv6.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from address-ipv4-ipv6.xml.i -->
+<!-- include start from interface/address-ipv4-ipv6.xml.i -->
<leafNode name="address">
<properties>
<help>IP address</help>
diff --git a/interface-definitions/include/pki/ca-certificate-multi.xml.i b/interface-definitions/include/pki/ca-certificate-multi.xml.i
new file mode 100644
index 000000000..646131b54
--- /dev/null
+++ b/interface-definitions/include/pki/ca-certificate-multi.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from pki/ca-certificate-multi.xml.i -->
+<leafNode name="ca-certificate">
+ <properties>
+ <help>Certificate Authority chain in PKI configuration</help>
+ <completionHelp>
+ <path>pki ca</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of CA in PKI configuration</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index f1cbf8468..6cbd91ff4 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -741,7 +741,7 @@
</properties>
</leafNode>
#include <include/pki/certificate.xml.i>
- #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/ca-certificate-multi.xml.i>
<leafNode name="dh-params">
<properties>
<help>Diffie Hellman parameters (server only)</help>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 664914baa..9674cfc0e 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -4,7 +4,7 @@
<children>
<tagNode name="pppoe" owner="${vyos_conf_scripts_dir}/interfaces-pppoe.py">
<properties>
- <help>Point-to-Point Protocol over Ethernet (PPPoE)</help>
+ <help>Point-to-Point Protocol over Ethernet (PPPoE) Interface</help>
<priority>322</priority>
<constraint>
<regex>pppoe[0-9]+</regex>
diff --git a/interface-definitions/interfaces-pseudo-ethernet.xml.in b/interface-definitions/interfaces-pseudo-ethernet.xml.in
index 6b62f4c61..53e6445fa 100644
--- a/interface-definitions/interfaces-pseudo-ethernet.xml.in
+++ b/interface-definitions/interfaces-pseudo-ethernet.xml.in
@@ -4,7 +4,7 @@
<children>
<tagNode name="pseudo-ethernet" owner="${vyos_conf_scripts_dir}/interfaces-pseudo-ethernet.py">
<properties>
- <help>Pseudo Ethernet</help>
+ <help>Pseudo Ethernet Interface (Macvlan)</help>
<priority>321</priority>
<constraint>
<regex>peth[0-9]+</regex>
diff --git a/interface-definitions/interfaces-vti.xml.in b/interface-definitions/interfaces-vti.xml.in
index b471c3b92..aa83a04b2 100644
--- a/interface-definitions/interfaces-vti.xml.in
+++ b/interface-definitions/interfaces-vti.xml.in
@@ -4,7 +4,7 @@
<children>
<tagNode name="vti" owner="${vyos_conf_scripts_dir}/interfaces-vti.py">
<properties>
- <help>Virtual Tunnel interface</help>
+ <help>Virtual Tunnel Interface (XFRM)</help>
<priority>381</priority>
<constraint>
<regex>vti[0-9]+</regex>
@@ -16,19 +16,7 @@
</valueHelp>
</properties>
<children>
- <leafNode name="address">
- <properties>
- <help>IP address</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>IPv4 address and prefix length</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-host"/>
- </constraint>
- <multi/>
- </properties>
- </leafNode>
+ #include <include/interface/address-ipv4-ipv6.xml.i>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/ipv4-options.xml.i>
diff --git a/interface-definitions/service-ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in
index e222467b1..cd3aa3638 100644
--- a/interface-definitions/service-ipoe-server.xml.in
+++ b/interface-definitions/service-ipoe-server.xml.in
@@ -213,6 +213,11 @@
</tagNode>
</children>
</tagNode>
+ <node name="radius">
+ <children>
+ #include <include/accel-ppp/radius-additions-rate-limit.xml.i>
+ </children>
+ </node>
#include <include/radius-server-ipv4.xml.i>
#include <include/accel-ppp/radius-additions.xml.i>
</children>
diff --git a/op-mode-definitions/show-ip.xml.in b/op-mode-definitions/show-ip.xml.in
index d21c38ccc..d342ac192 100644
--- a/op-mode-definitions/show-ip.xml.in
+++ b/op-mode-definitions/show-ip.xml.in
@@ -7,12 +7,6 @@
<help>Show IPv4 networking information</help>
</properties>
<children>
- <node name="external">
- <properties>
- <help>Show IPv4 external address</help>
- </properties>
- <command>${vyos_op_scripts_dir}/show_ip_external.sh</command>
- </node>
<node name="neighbors">
<properties>
<help>Show IPv4 neighbor (ARP) table</help>
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
index c50cd5ce9..dc99d365a 100644
--- a/python/vyos/ifconfig/vti.py
+++ b/python/vyos/ifconfig/vti.py
@@ -1,4 +1,4 @@
-# Copyright 2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2021-2022 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
@@ -53,3 +53,7 @@ class VTIIf(Interface):
self._cmd(cmd.format(**self.config))
self.set_interface('admin_state', 'down')
+
+ def get_mac(self):
+ """ Get a synthetic MAC address. """
+ return self.get_mac_synthetic()
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index fd91fc9bf..cd15e3878 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -332,6 +332,54 @@ def verify_certificate(cert, ca_cert):
except InvalidSignature:
return False
+def verify_crl(crl, ca_cert):
+ # Verify CRL was signed by specified CA
+ if ca_cert.subject != crl.issuer:
+ return False
+
+ ca_public_key = ca_cert.public_key()
+ try:
+ if isinstance(ca_public_key, rsa.RSAPublicKeyWithSerialization):
+ ca_public_key.verify(
+ crl.signature,
+ crl.tbs_certlist_bytes,
+ padding=padding.PKCS1v15(),
+ algorithm=crl.signature_hash_algorithm)
+ elif isinstance(ca_public_key, dsa.DSAPublicKeyWithSerialization):
+ ca_public_key.verify(
+ crl.signature,
+ crl.tbs_certlist_bytes,
+ algorithm=crl.signature_hash_algorithm)
+ elif isinstance(ca_public_key, ec.EllipticCurvePublicKeyWithSerialization):
+ ca_public_key.verify(
+ crl.signature,
+ crl.tbs_certlist_bytes,
+ signature_algorithm=ec.ECDSA(crl.signature_hash_algorithm))
+ else:
+ return False # We cannot verify it
+ return True
+ except InvalidSignature:
+ return False
+
+def verify_ca_chain(sorted_names, pki_node):
+ if len(sorted_names) == 1: # Single cert, no chain
+ return True
+
+ for name in sorted_names:
+ cert = load_certificate(pki_node[name]['certificate'])
+ verified = False
+ for ca_name in sorted_names:
+ if name == ca_name:
+ continue
+ ca_cert = load_certificate(pki_node[ca_name]['certificate'])
+ if verify_certificate(cert, ca_cert):
+ verified = True
+ break
+ if not verified and name != sorted_names[-1]:
+ # Only permit top-most certificate to fail verify (e.g. signed by public CA not explicitly in chain)
+ return False
+ return True
+
# Certificate chain
def find_parent(cert, ca_certs):
@@ -357,3 +405,16 @@ def find_chain(cert, ca_certs):
chain.append(parent)
return chain
+
+def sort_ca_chain(ca_names, pki_node):
+ def ca_cmp(ca_name1, ca_name2, pki_node):
+ cert1 = load_certificate(pki_node[ca_name1]['certificate'])
+ cert2 = load_certificate(pki_node[ca_name2]['certificate'])
+
+ if verify_certificate(cert1, cert2): # cert1 is child of cert2
+ return -1
+ return 1
+
+ from functools import cmp_to_key
+ return sorted(ca_names, key=cmp_to_key(lambda cert1, cert2: ca_cmp(cert1, cert2, pki_node)))
+
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 0d62fbfe9..bee5d7aec 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -197,7 +197,7 @@ def read_file(fname, defaultonfailure=None):
return defaultonfailure
raise e
-def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None):
+def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=None, append=False):
"""
Write content of data to given fname, should defaultonfailure be not None,
it is returned on failure to read.
@@ -212,7 +212,7 @@ def write_file(fname, data, defaultonfailure=None, user=None, group=None, mode=N
try:
""" Write a file to string """
bytes = 0
- with open(fname, 'w') as f:
+ with open(fname, 'w' if not append else 'a') as f:
bytes = f.write(data)
chown(fname, user, group)
chmod(fname, mode)
diff --git a/smoketest/bin/vyos-configtest-pki b/smoketest/bin/vyos-configtest-pki
new file mode 100755
index 000000000..2f8af0e61
--- /dev/null
+++ b/smoketest/bin/vyos-configtest-pki
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022, 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
+# 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from os import system
+from vyos.pki import create_private_key
+from vyos.pki import create_certificate_request
+from vyos.pki import create_certificate
+from vyos.pki import create_certificate_revocation_list
+from vyos.pki import create_dh_parameters
+from vyos.pki import encode_certificate
+from vyos.pki import encode_dh_parameters
+from vyos.pki import encode_private_key
+
+subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos'}
+ca_subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos CA'}
+subca_subject = {'country': 'DE', 'state': 'BY', 'locality': 'Cloud', 'organization': 'VyOS', 'common_name': 'vyos SubCA'}
+
+ca_cert = '/config/auth/ovpn_test_ca.pem'
+ca_key = '/config/auth/ovpn_test_ca.key'
+ca_cert_chain = '/config/auth/ovpn_test_chain.pem'
+ca_crl = '/config/auth/ovpn_test_ca.crl'
+subca_cert = '/config/auth/ovpn_test_subca.pem'
+subca_csr = '/tmp/subca.csr'
+subca_key = '/config/auth/ovpn_test_subca.key'
+ssl_cert = '/config/auth/ovpn_test_server.pem'
+ssl_key = '/config/auth/ovpn_test_server.key'
+dh_pem = '/config/auth/ovpn_test_dh.pem'
+s2s_key = '/config/auth/ovpn_test_site2site.key'
+auth_key = '/config/auth/ovpn_test_tls_auth.key'
+
+def create_cert(subject, cert_path, key_path, sign_by=None, sign_by_key=None, ca=False, sub_ca=False):
+ priv_key = create_private_key('rsa', 2048)
+ cert_req = create_certificate_request(subject, priv_key)
+ cert = create_certificate(
+ cert_req,
+ sign_by if sign_by else cert_req,
+ sign_by_key if sign_by_key else priv_key,
+ is_ca=ca, is_sub_ca=sub_ca)
+
+ with open(cert_path, 'w') as f:
+ f.write(encode_certificate(cert))
+
+ with open(key_path, 'w') as f:
+ f.write(encode_private_key(priv_key))
+
+ return cert, priv_key
+
+def create_empty_crl(crl_path, sign_by, sign_by_key):
+ crl = create_certificate_revocation_list(sign_by, sign_by_key, [1])
+
+ with open(crl_path, 'w') as f:
+ f.write(encode_certificate(crl))
+
+ return crl
+
+if __name__ == '__main__':
+ # Create Root CA
+ ca_cert_obj, ca_key_obj = create_cert(ca_subject, ca_cert, ca_key, ca=True)
+
+ # Create Empty CRL
+ create_empty_crl(ca_crl, ca_cert_obj, ca_key_obj)
+
+ # Create Intermediate CA
+ subca_cert_obj, subca_key_obj = create_cert(
+ subca_subject, subca_cert, subca_key,
+ sign_by=ca_cert_obj, sign_by_key=ca_key_obj,
+ ca=True, sub_ca=True)
+
+ # Create Chain
+ with open(ca_cert_chain, 'w') as f:
+ f.write(encode_certificate(subca_cert_obj) + "\n")
+ f.write(encode_certificate(ca_cert_obj) + "\n")
+
+ # Create Server Cert
+ create_cert(subject, ssl_cert, ssl_key, sign_by=subca_cert_obj, sign_by_key=subca_key_obj)
+
+ # Create DH params
+ dh_params = create_dh_parameters()
+
+ with open(dh_pem, 'w') as f:
+ f.write(encode_dh_parameters(dh_params))
+
+ # OpenVPN S2S Key
+ system(f'openvpn --genkey secret {s2s_key}')
+
+ # OpenVPN Auth Key
+ system(f'openvpn --genkey secret {auth_key}')
diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn
index 63d955738..56722d222 100644
--- a/smoketest/configs/dialup-router-medium-vpn
+++ b/smoketest/configs/dialup-router-medium-vpn
@@ -120,8 +120,9 @@ interfaces {
persistent-tunnel
remote-host 192.0.2.10
tls {
- ca-cert-file /config/auth/ovpn_test_ca.pem
+ ca-cert-file /config/auth/ovpn_test_chain.pem
cert-file /config/auth/ovpn_test_server.pem
+ crl-file /config/auth/ovpn_test_ca.crl
key-file /config/auth/ovpn_test_server.key
auth-file /config/auth/ovpn_test_tls_auth.key
}
@@ -152,7 +153,7 @@ interfaces {
remote-host 01.foo.com
remote-port 1194
tls {
- ca-cert-file /config/auth/ovpn_test_ca.pem
+ ca-cert-file /config/auth/ovpn_test_chain.pem
auth-file /config/auth/ovpn_test_tls_auth.key
}
}
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 816ba6dcd..8acf52243 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -232,6 +232,9 @@ class BasicInterfaceTest:
for interface in self._interfaces:
base = self._base_path + [interface]
+ # just set the interface base without any option - some interfaces
+ # (VTI) do not require any option to be brought up
+ self.cli_set(base)
for option in self._options.get(interface, []):
self.cli_set(base + option.split())
diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index ca0ead9e8..26d3a23c9 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -86,9 +86,83 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
# validate member interface configuration
for member in self._members:
tmp = get_interface_config(member)
+ # verify member is assigned to the bridge
+ self.assertEqual(interface, tmp['master'])
# Isolated must be enabled as configured above
self.assertTrue(tmp['linkinfo']['info_slave_data']['isolated'])
+ def test_igmp_querier_snooping(self):
+ # Add member interfaces to bridge
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+
+ # assign members to bridge interface
+ for member in self._members:
+ base_member = base + ['member', 'interface', member]
+ self.cli_set(base_member)
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP default configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '0')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '0')
+
+ # Enable IGMP snooping
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+ self.cli_set(base + ['igmp', 'snooping'])
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP snooping configuration
+ # Verify IGMP default configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '1')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '0')
+
+ # Enable IGMP querieer
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+ self.cli_set(base + ['igmp', 'querier'])
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP snooping & querier configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '1')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '1')
+
+ # Disable IGMP
+ for interface in self._interfaces:
+ base = self._base_path + [interface]
+ self.cli_delete(base + ['igmp'])
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ # Verify IGMP snooping & querier configuration
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_snooping')
+ self.assertEqual(tmp, '0')
+ tmp = read_file(f'/sys/class/net/{interface}/bridge/multicast_querier')
+ self.assertEqual(tmp, '0')
+
+ # validate member interface configuration
+ for member in self._members:
+ tmp = get_interface_config(member)
+ # verify member is assigned to the bridge
+ self.assertEqual(interface, tmp['master'])
+
def test_add_remove_bridge_member(self):
# Add member interfaces to bridge and set STP cost/priority
diff --git a/smoketest/scripts/cli/test_interfaces_vti.py b/smoketest/scripts/cli/test_interfaces_vti.py
new file mode 100755
index 000000000..9cbf104f0
--- /dev/null
+++ b/smoketest/scripts/cli/test_interfaces_vti.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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
+# 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.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_interfaces_test import BasicInterfaceTest
+
+class VTIInterfaceTest(BasicInterfaceTest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls._test_ip = True
+ cls._test_ipv6 = True
+ cls._test_mtu = True
+ cls._base_path = ['interfaces', 'vti']
+ cls._interfaces = ['vti10', 'vti20', 'vti30']
+
+ # call base-classes classmethod
+ super(VTIInterfaceTest, cls).setUpClass()
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 4750ca3e8..280a62b9a 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -39,6 +39,8 @@ from vyos.configverify import verify_mirror_redirect
from vyos.ifconfig import VTunIf
from vyos.pki import load_dh_parameters
from vyos.pki import load_private_key
+from vyos.pki import sort_ca_chain
+from vyos.pki import verify_ca_chain
from vyos.pki import wrap_certificate
from vyos.pki import wrap_crl
from vyos.pki import wrap_dh_parameters
@@ -148,8 +150,14 @@ def verify_pki(openvpn):
if 'ca_certificate' not in tls:
raise ConfigError(f'Must specify "tls ca-certificate" on openvpn interface {interface}')
- if tls['ca_certificate'] not in pki['ca']:
- raise ConfigError(f'Invalid 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 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:
@@ -516,21 +524,28 @@ def generate_pki_files(openvpn):
if tls:
if 'ca_certificate' in tls:
- cert_name = tls['ca_certificate']
- pki_ca = pki['ca'][cert_name]
+ cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
+ crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- if 'certificate' in pki_ca:
- cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
- write_file(cert_path, wrap_certificate(pki_ca['certificate']),
- user=user, group=group, mode=0o600)
+ if os.path.exists(cert_path):
+ os.unlink(cert_path)
+
+ if os.path.exists(crl_path):
+ os.unlink(crl_path)
+
+ for cert_name in sort_ca_chain(tls['ca_certificate'], pki['ca']):
+ pki_ca = pki['ca'][cert_name]
+
+ if 'certificate' in pki_ca:
+ write_file(cert_path, wrap_certificate(pki_ca['certificate']) + "\n",
+ user=user, group=group, mode=0o600, append=True)
- if 'crl' in pki_ca:
- for crl in pki_ca['crl']:
- crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
- write_file(crl_path, wrap_crl(crl), user=user, group=group,
- mode=0o600)
+ if 'crl' in pki_ca:
+ for crl in pki_ca['crl']:
+ write_file(crl_path, wrap_crl(crl) + "\n", user=user, group=group,
+ mode=0o600, append=True)
- openvpn['tls']['crl'] = True
+ openvpn['tls']['crl'] = True
if 'certificate' in tls:
cert_name = tls['certificate']
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index cd46cbcb4..01f14df61 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -19,6 +19,7 @@ import os
from sys import exit
from sys import argv
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configverify import verify_prefix_list
@@ -198,6 +199,9 @@ def verify(bgp):
if 'source_interface' in peer_config['interface']:
raise ConfigError(f'"source-interface" option not allowed for neighbor "{peer}"')
+ if 'address_family' not in peer_config:
+ Warning(f'BGP neighbor "{peer}" requires address-family!')
+
for afi in ['ipv4_unicast', 'ipv4_multicast', 'ipv4_labeled_unicast', 'ipv4_flowspec',
'ipv6_unicast', 'ipv6_multicast', 'ipv6_labeled_unicast', 'ipv6_flowspec',
'l2vpn_evpn']:
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 559d1bcd5..61f484129 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -53,6 +53,8 @@ default_config_data = {
'radius_nas_ip': '',
'radius_source_address': '',
'radius_shaper_attr': '',
+ 'radius_shaper_enable': False,
+ 'radius_shaper_multiplier': '',
'radius_shaper_vendor': '',
'radius_dynamic_author': '',
'thread_cnt': get_half_cpus()
@@ -196,6 +198,18 @@ def get_config(config=None):
if conf.exists(['nas-ip-address']):
ipoe['radius_nas_ip'] = conf.return_value(['nas-ip-address'])
+ if conf.exists(['rate-limit', 'attribute']):
+ ipoe['radius_shaper_attr'] = conf.return_value(['rate-limit', 'attribute'])
+
+ if conf.exists(['rate-limit', 'enable']):
+ ipoe['radius_shaper_enable'] = True
+
+ if conf.exists(['rate-limit', 'multiplier']):
+ ipoe['radius_shaper_multiplier'] = conf.return_value(['rate-limit', 'multiplier'])
+
+ if conf.exists(['rate-limit', 'vendor']):
+ ipoe['radius_shaper_vendor'] = conf.return_value(['rate-limit', 'vendor'])
+
if conf.exists(['source-address']):
ipoe['radius_source_address'] = conf.return_value(['source-address'])
diff --git a/src/migration-scripts/interfaces/24-to-25 b/src/migration-scripts/interfaces/24-to-25
index 93ce9215f..4095f2a3e 100755
--- a/src/migration-scripts/interfaces/24-to-25
+++ b/src/migration-scripts/interfaces/24-to-25
@@ -20,6 +20,7 @@
import os
import sys
from vyos.configtree import ConfigTree
+from vyos.pki import CERT_BEGIN
from vyos.pki import load_certificate
from vyos.pki import load_crl
from vyos.pki import load_dh_parameters
@@ -27,6 +28,7 @@ from vyos.pki import load_private_key
from vyos.pki import encode_certificate
from vyos.pki import encode_dh_parameters
from vyos.pki import encode_private_key
+from vyos.pki import verify_crl
from vyos.util import run
def wrapped_pem_to_config_value(pem):
@@ -129,6 +131,8 @@ if config.exists(base):
config.delete(base + [interface, 'tls', 'crypt-file'])
+ ca_certs = {}
+
if config.exists(x509_base + ['ca-cert-file']):
if not config.exists(pki_base + ['ca']):
config.set(pki_base + ['ca'])
@@ -136,20 +140,27 @@ if config.exists(base):
cert_file = config.return_value(x509_base + ['ca-cert-file'])
cert_path = os.path.join(AUTH_DIR, cert_file)
- cert = None
if os.path.isfile(cert_path):
if not os.access(cert_path, os.R_OK):
run(f'sudo chmod 644 {cert_path}')
with open(cert_path, 'r') as f:
- cert_data = f.read()
- cert = load_certificate(cert_data, wrap_tags=False)
-
- if cert:
- cert_pem = encode_certificate(cert)
- config.set(pki_base + ['ca', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
- config.set(x509_base + ['ca-certificate'], value=pki_name)
+ certs_str = f.read()
+ certs_data = certs_str.split(CERT_BEGIN)
+ index = 1
+ for cert_data in certs_data[1:]:
+ cert = load_certificate(CERT_BEGIN + cert_data, wrap_tags=False)
+
+ if cert:
+ ca_certs[f'{pki_name}_{index}'] = cert
+ cert_pem = encode_certificate(cert)
+ config.set(pki_base + ['ca', f'{pki_name}_{index}', 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['ca-certificate'], value=f'{pki_name}_{index}', replace=False)
+ else:
+ print(f'Failed to migrate CA certificate on openvpn interface {interface}')
+
+ index += 1
else:
print(f'Failed to migrate CA certificate on openvpn interface {interface}')
@@ -163,6 +174,7 @@ if config.exists(base):
crl_file = config.return_value(x509_base + ['crl-file'])
crl_path = os.path.join(AUTH_DIR, crl_file)
crl = None
+ crl_ca_name = None
if os.path.isfile(crl_path):
if not os.access(crl_path, os.R_OK):
@@ -172,9 +184,14 @@ if config.exists(base):
crl_data = f.read()
crl = load_crl(crl_data, wrap_tags=False)
- if crl:
+ for ca_name, ca_cert in ca_certs.items():
+ if verify_crl(crl, ca_cert):
+ crl_ca_name = ca_name
+ break
+
+ if crl and crl_ca_name:
crl_pem = encode_certificate(crl)
- config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
+ config.set(pki_base + ['ca', crl_ca_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
else:
print(f'Failed to migrate CRL on openvpn interface {interface}')
diff --git a/src/op_mode/show_ip_external.sh b/src/op_mode/show_ip_external.sh
deleted file mode 100755
index 275d05278..000000000
--- a/src/op_mode/show_ip_external.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env bash
-
-# Detect an external IP address
-# Use random services for checking
-
-
-array=(
- ipinfo.io/ip
- ifconfig.me
- ipecho.net/plain
- icanhazip.com
- v4.ident.me
- checkip.amazonaws.com
-)
-
-size=${#array[@]}
-index=$(($RANDOM % $size))
-
-curl --silent ${array[$index]} | tr -d "[:space:]" && echo
diff --git a/src/system/vyos-event-handler.py b/src/system/vyos-event-handler.py
index 691f674b2..1c85380bc 100755
--- a/src/system/vyos-event-handler.py
+++ b/src/system/vyos-event-handler.py
@@ -15,14 +15,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
-import select
-import re
import json
+import re
+import select
+from copy import deepcopy
from os import getpid, environ
from pathlib import Path
from signal import signal, SIGTERM, SIGINT
-from systemd import journal
from sys import exit
+from systemd import journal
+
from vyos.util import run, dict_search
# Identify this script
@@ -54,7 +56,7 @@ class Analyzer:
script_arguments = dict_search('script.arguments', event_config)
script = f'{script} {script_arguments}'
# Prepare environment
- environment = environ
+ environment = deepcopy(environ)
# Check for additional environment options
if dict_search('script.environment', event_config):
for env_variable, env_value in dict_search(
@@ -69,7 +71,7 @@ class Analyzer:
'pattern_raw':
pattern_raw,
'syslog_id':
- dict_search('filter.syslog_identifier', event_config),
+ dict_search('filter.syslog-identifier', event_config),
'pattern_script': {
'path': script,
'environment': environment