diff options
Diffstat (limited to 'smoketest/scripts/cli')
-rw-r--r-- | smoketest/scripts/cli/base_accel_ppp_test.py | 7 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_container.py | 112 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_system_nameserver.py | 63 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_system_resolvconf.py | 112 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_vpn_ipsec.py | 371 |
5 files changed, 572 insertions, 93 deletions
diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py index 682a0349a..1ea5db898 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -384,9 +384,6 @@ class BasicAccelPPPTest: self.assertEqual(f"fail-time=0", server[5]) def test_accel_ipv4_pool(self): - """ - Test accel-ppp IPv4 pool - """ self.basic_config(is_gateway=False, is_client_pool=False) gateway = "192.0.2.1" subnet = "172.16.0.0/24" @@ -416,9 +413,7 @@ class BasicAccelPPPTest: self.assertEqual(first_pool, conf[self._protocol_section]["ip-pool"]) def test_accel_next_pool(self): - """ - T5099 required specific order - """ + # T5099 required specific order self.basic_config(is_gateway=False, is_client_pool=False) gateway = "192.0.2.1" diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py index b43c05fae..cdf46a6e1 100755 --- a/smoketest/scripts/cli/test_container.py +++ b/smoketest/scripts/cli/test_container.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 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 @@ -19,6 +19,7 @@ import glob import json from base_vyostest_shim import VyOSUnitTestSHIM +from ipaddress import ip_interface from vyos.configsession import ConfigSessionError from vyos.utils.process import cmd @@ -27,8 +28,6 @@ from vyos.utils.file import read_file base_path = ['container'] cont_image = 'busybox:stable' # busybox is included in vyos-build -prefix = '192.168.205.0/24' -net_name = 'NET01' PROCESS_NAME = 'conmon' PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid' @@ -37,10 +36,8 @@ busybox_image_path = '/usr/share/vyos/busybox-stable.tar' def cmd_to_json(command): c = cmd(command + ' --format=json') data = json.loads(c)[0] - return data - class TestContainer(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -52,6 +49,10 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): except: cls.skipTest(cls, reason='busybox image not available') + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + @classmethod def tearDownClass(cls): super(TestContainer, cls).tearDownClass() @@ -70,7 +71,7 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): units = glob.glob('/run/systemd/system/vyos-container-*') self.assertEqual(units, []) - def test_01_basic_container(self): + def test_basic(self): cont_name = 'c1' self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '10.0.2.15/24']) @@ -91,24 +92,101 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertEqual(process_named_running(PROCESS_NAME), pid) - def test_02_container_network(self): - cont_name = 'c2' - cont_ip = '192.168.205.25' + def test_ipv4_network(self): + prefix = '192.0.2.0/24' + base_name = 'ipv4' + net_name = 'NET01' + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) - self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) - self.cli_set(base_path + ['name', cont_name, 'network', net_name, 'address', cont_ip]) - # commit changes + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) self.cli_commit() n = cmd_to_json(f'sudo podman network inspect {net_name}') - json_subnet = n['subnets'][0]['subnet'] + self.assertEqual(n['subnets'][0]['subnet'], prefix) - c = cmd_to_json(f'sudo podman container inspect {cont_name}') - json_ip = c['NetworkSettings']['Networks'][net_name]['IPAddress'] + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'], str(ip_interface(prefix).ip + ii)) - self.assertEqual(json_subnet, prefix) - self.assertEqual(json_ip, cont_ip) + def test_ipv6_network(self): + prefix = '2001:db8::/64' + base_name = 'ipv6' + net_name = 'NET02' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) + + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['subnets'][0]['subnet'], prefix) + + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix).ip + ii)) + + def test_dual_stack_network(self): + prefix4 = '192.0.2.0/24' + prefix6 = '2001:db8::/64' + base_name = 'dual-stack' + net_name = 'net-4-6' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix4]) + self.cli_set(base_path + ['network', net_name, 'prefix', prefix6]) + + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix4).ip + ii)]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix6).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['subnets'][0]['subnet'], prefix4) + self.assertEqual(n['subnets'][1]['subnet'], prefix6) + + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix6).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix6).ip + ii)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix4).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'] , str(ip_interface(prefix4).ip + ii)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_nameserver.py b/smoketest/scripts/cli/test_system_nameserver.py deleted file mode 100755 index 4979a7c72..000000000 --- a/smoketest/scripts/cli/test_system_nameserver.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019-2020 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 re -import unittest - -from base_vyostest_shim import VyOSUnitTestSHIM - -from vyos.configsession import ConfigSessionError - -from vyos.utils.file import read_file - -RESOLV_CONF = '/etc/resolv.conf' - -test_servers = ['192.0.2.10', '2001:db8:1::100'] -base_path = ['system', 'name-server'] - -def get_name_servers(): - resolv_conf = read_file(RESOLV_CONF) - return re.findall(r'\n?nameserver\s+(.*)', resolv_conf) - -class TestSystemNameServer(VyOSUnitTestSHIM.TestCase): - def tearDown(self): - # Delete existing name servers - self.cli_delete(base_path) - self.cli_commit() - - def test_nameserver_add(self): - # Check if server is added to resolv.conf - for s in test_servers: - self.cli_set(base_path + [s]) - self.cli_commit() - - servers = get_name_servers() - for s in servers: - self.assertTrue(s in servers) - - def test_nameserver_delete(self): - # Test if a deleted server disappears from resolv.conf - for s in test_servers: - self.cli_delete(base_path + [s]) - self.cli_commit() - - servers = get_name_servers() - for s in servers: - self.assertTrue(test_server_1 not in servers) - -if __name__ == '__main__': - unittest.main(verbosity=2) - diff --git a/smoketest/scripts/cli/test_system_resolvconf.py b/smoketest/scripts/cli/test_system_resolvconf.py new file mode 100755 index 000000000..d8726a301 --- /dev/null +++ b/smoketest/scripts/cli/test_system_resolvconf.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019-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 +# 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 re +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.utils.file import read_file + +RESOLV_CONF = '/etc/resolv.conf' + +name_servers = ['192.0.2.10', '2001:db8:1::100'] +domain_name = 'vyos.net' +domain_search = ['vyos.net', 'vyos.io'] + +base_path_nameserver = ['system', 'name-server'] +base_path_domainname = ['system', 'domain-name'] +base_path_domainsearch = ['system', 'domain-search'] + +def get_name_servers(): + resolv_conf = read_file(RESOLV_CONF) + return re.findall(r'\n?nameserver\s+(.*)', resolv_conf) + +def get_domain_name(): + resolv_conf = read_file(RESOLV_CONF) + res = re.findall(r'\n?domain\s+(.*)', resolv_conf) + return res[0] if res else None + +def get_domain_searches(): + resolv_conf = read_file(RESOLV_CONF) + res = re.findall(r'\n?search\s+(.*)', resolv_conf) + return res[0].split() if res else [] + +class TestSystemResolvConf(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestSystemResolvConf, cls).setUpClass() + # Clear out current configuration to allow running this test on a live system + cls.cli_delete(cls, base_path_nameserver) + cls.cli_delete(cls, base_path_domainname) + cls.cli_delete(cls, base_path_domainsearch) + + def tearDown(self): + # Delete test entries servers + self.cli_delete(base_path_nameserver) + self.cli_delete(base_path_domainname) + self.cli_delete(base_path_domainsearch) + self.cli_commit() + + def test_nameserver(self): + # Check if server is added to resolv.conf + for s in name_servers: + self.cli_set(base_path_nameserver + [s]) + self.cli_commit() + + for s in get_name_servers(): + self.assertTrue(s in name_servers) + + # Test if a deleted server disappears from resolv.conf + for s in name_servers: + self.cli_delete(base_path_nameserver + [s]) + self.cli_commit() + + for s in get_name_servers(): + self.assertTrue(s not in name_servers) + + def test_domainname(self): + # Check if domain-name is added to resolv.conf + self.cli_set(base_path_domainname + [domain_name]) + self.cli_commit() + + self.assertEqual(get_domain_name(), domain_name) + + # Test if domain-name disappears from resolv.conf + self.cli_delete(base_path_domainname + [domain_name]) + self.cli_commit() + + self.assertTrue(get_domain_name() is None) + + def test_domainsearch(self): + # Check if domain-search is added to resolv.conf + for s in domain_search: + self.cli_set(base_path_domainsearch + [s]) + self.cli_commit() + + for s in get_domain_searches(): + self.assertTrue(s in domain_search) + + # Test if domain-search disappears from resolv.conf + for s in domain_search: + self.cli_delete(base_path_domainsearch + [s]) + self.cli_commit() + + for s in get_domain_searches(): + self.assertTrue(s not in domain_search) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index 17b1b395c..17e12bcaf 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -18,6 +18,8 @@ import os import unittest from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError from vyos.utils.process import call from vyos.utils.process import process_named_running from vyos.utils.file import read_file @@ -44,6 +46,7 @@ secret = 'MYSECRETKEY' PROCESS_NAME = 'charon-systemd' regex_uuid4 = '[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}' +ca_name = 'MyVyOS-CA' ca_pem = """ MIICMDCCAdegAwIBAgIUBCzIjYvD7SPbx5oU18IYg7NVxQ0wCgYIKoZIzj0EAwIw ZzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv @@ -59,6 +62,7 @@ CgYIKoZIzj0EAwIDRwAwRAIgX1spXjrUc10r3g/Zm4O31LU5O08J2vVqFo94zHE5 0VgCIG4JK9Zg5O/yn4mYksZux7efiHRUzL2y2TXQ9IqrqM8W """ +int_ca_name = 'MyVyOS-IntCA' int_ca_pem = """ MIICYDCCAgWgAwIBAgIUcFx2BVYErHI+SneyPYHijxXt1cgwCgYIKoZIzj0EAwIw ZzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv @@ -75,6 +79,7 @@ CCqGSM49BAMCA0kAMEYCIQCnqWbElgOL9dGO3iLxasFNq/hM7vM/DzaiHi4BowxW 0gIhAMohefNj+QgLfPhvyODHIPE9LMyfp7lJEaCC2K8PCSFD """ +peer_name = 'peer1' peer_cert = """ MIICSTCCAfCgAwIBAgIUPxYleUgCo/glVVePze3QmAFgi6MwCgYIKoZIzj0EAwIw bzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv @@ -140,6 +145,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): # Check for no longer running process self.assertFalse(process_named_running(PROCESS_NAME)) + def setupPKI(self): + self.cli_set(['pki', 'ca', ca_name, 'certificate', ca_pem.replace('\n','')]) + self.cli_set(['pki', 'ca', int_ca_name, 'certificate', int_ca_pem.replace('\n','')]) + self.cli_set(['pki', 'certificate', peer_name, 'certificate', peer_cert.replace('\n','')]) + self.cli_set(['pki', 'certificate', peer_name, 'private', 'key', peer_key.replace('\n','')]) + + def tearDownPKI(self): + self.cli_delete(['pki']) + def test_01_dhcp_fail_handling(self): # Skip process check - connection is not created for this test self.skip_process_check = True @@ -383,13 +397,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): def test_05_x509_site2site(self): # Enable PKI - peer_name = 'peer1' - ca_name = 'MyVyOS-CA' - int_ca_name = 'MyVyOS-IntCA' - self.cli_set(['pki', 'ca', ca_name, 'certificate', ca_pem.replace('\n','')]) - self.cli_set(['pki', 'ca', int_ca_name, 'certificate', int_ca_pem.replace('\n','')]) - self.cli_set(['pki', 'certificate', peer_name, 'certificate', peer_cert.replace('\n','')]) - self.cli_set(['pki', 'certificate', peer_name, 'private', 'key', peer_key.replace('\n','')]) + self.setupPKI() vti = 'vti20' self.cli_set(vti_path + [vti, 'address', '192.168.0.1/31']) @@ -461,6 +469,9 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): # There is only one VTI test so no need to delete this globally in tearDown() self.cli_delete(vti_path) + # Disable PKI + self.tearDownPKI() + def test_06_flex_vpn_vips(self): local_address = '192.0.2.5' @@ -537,5 +548,351 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.assertIn(line, charon_conf) + def test_07_ikev2_road_warrior(self): + # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17 + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + username = 'vyos' + password = 'secret' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-users', 'username', username, 'password', password]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'auth = eap-mschapv2', + f'eap_id = %any', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'rekey_time = {eap_lifetime}s', + f'rand_time = 540s', + f'dpd_action = clear', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_secrets_lines = [ + f'eap-{conn_name}-{username}', + f'secret = "{password}"', + f'id-{conn_name}-{username} = "{username}"', + ] + for line in swanctl_secrets_lines: + self.assertIn(line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {prefix}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}_1.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + self.tearDownPKI() + + def test_08_ikev2_road_warrior_client_auth_eap_tls(self): + # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17 + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + username = 'vyos' + password = 'secret' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + # Use EAP-TLS auth instead of default EAP-MSCHAPv2 + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'eap-tls']) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'auth = eap-tls', + f'eap_id = %any', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'rekey_time = {eap_lifetime}s', + f'rand_time = 540s', + f'dpd_action = clear', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {prefix}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}_1.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + self.tearDownPKI() + + def test_09_ikev2_road_warrior_client_auth_x509(self): + # This is a known to be good configuration for Microsoft Windows 10 and Apple iOS 17 + self.setupPKI() + + ike_group = 'IKE-RW' + esp_group = 'ESP-RW' + + conn_name = 'vyos-rw' + local_address = '192.0.2.1' + ip_pool_name = 'ra-rw-ipv4' + ike_lifetime = '7200' + eap_lifetime = '3600' + local_id = 'ipsec.vyos.net' + + name_servers = ['172.16.254.100', '172.16.254.101'] + prefix = '172.16.250.0/28' + + # IKE + self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'dh-group', '2']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'dh-group', '14']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '10', 'hash', 'sha256']) + + # ESP + self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', eap_lifetime]) + self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'disable']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha512']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '2', 'hash', 'sha384']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'sha256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'encryption', 'aes256']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '4', 'hash', 'sha1']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'encryption', 'aes128gcm128']) + self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '10', 'hash', 'sha256']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'local-id', local_id]) + # Use client-mode x509 instead of default EAP-MSCHAPv2 + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'client-mode', 'x509']) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'server-mode', 'x509']) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'certificate', peer_name]) + # verify() - CA cert required for x509 auth + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'authentication', 'x509', 'ca-certificate', ca_name]) + + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'esp-group', esp_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'ike-group', ike_group]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'local-address', local_address]) + self.cli_set(base_path + ['remote-access', 'connection', conn_name, 'pool', ip_pool_name]) + + for ns in name_servers: + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'name-server', ns]) + self.cli_set(base_path + ['remote-access', 'pool', ip_pool_name, 'prefix', prefix]) + + self.cli_commit() + + # verify applied configuration + swanctl_conf = read_file(swanctl_file) + swanctl_lines = [ + f'{conn_name}', + f'remote_addrs = %any', + f'local_addrs = {local_address}', + f'proposals = aes256-sha512-modp2048,aes256-sha256-modp2048,aes256-sha256-modp1024,aes128gcm128-sha256-modp2048', + f'version = 2', + f'send_certreq = no', + f'rekey_time = {ike_lifetime}s', + f'keyingtries = 0', + f'pools = {ip_pool_name}', + f'id = "{local_id}"', + f'auth = pubkey', + f'certs = peer1.pem', + f'esp_proposals = aes256-sha512,aes256-sha384,aes256-sha256,aes256-sha1,aes128gcm128-sha256', + f'rekey_time = {eap_lifetime}s', + f'rand_time = 540s', + f'dpd_action = clear', + f'inactivity = 28800', + f'local_ts = 0.0.0.0/0,::/0', + ] + for line in swanctl_lines: + self.assertIn(line, swanctl_conf) + + swanctl_unexpected_lines = [ + f'auth = eap-', + f'eap_id' + ] + for unexpected_line in swanctl_unexpected_lines: + self.assertNotIn(unexpected_line, swanctl_conf) + + swanctl_pool_lines = [ + f'{ip_pool_name}', + f'addrs = {prefix}', + f'dns = {",".join(name_servers)}', + ] + for line in swanctl_pool_lines: + self.assertIn(line, swanctl_conf) + + # Check Root CA, Intermediate CA and Peer cert/key pair is present + self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{ca_name}_1.pem'))) + self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) + + self.tearDownPKI() + if __name__ == '__main__': unittest.main(verbosity=2) |