#!/usr/bin/env python3 # # 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 # 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 . 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 ethernet_path = ['interfaces', 'ethernet'] tunnel_path = ['interfaces', 'tunnel'] vti_path = ['interfaces', 'vti'] nhrp_path = ['protocols', 'nhrp'] base_path = ['vpn', 'ipsec'] charon_file = '/etc/strongswan.d/charon.conf' dhcp_waiting_file = '/tmp/ipsec_dhcp_waiting' swanctl_file = '/etc/swanctl/swanctl.conf' peer_ip = '203.0.113.45' connection_name = 'main-branch' local_id = 'left' remote_id = 'right' interface = 'eth1' vif = '100' esp_group = 'MyESPGroup' ike_group = 'MyIKEGroup' 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 bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEgMB4GA1UEAwwXSVBTZWMgU21va2V0ZXN0 IFJvb3QgQ0EwHhcNMjMwOTI0MTIwMzQxWhcNMzMwOTIxMTIwMzQxWjBnMQswCQYD VQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UEBwwJU29tZS1DaXR5 MQ0wCwYDVQQKDARWeU9TMSAwHgYDVQQDDBdJUFNlYyBTbW9rZXRlc3QgUm9vdCBD QTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEh8/yU572B3zmFxrGgHk+H7grYt EHUJodY3gXNWMHz0gySrbGhsGtECDfP/G+T4Suk7cuVzB1wnLocSafD8TcqjYTBf MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsG AQUFBwMCBggrBgEFBQcDATAdBgNVHQ4EFgQUTYoQJNlk7X87/gRegHnCnPef39Aw CgYIKoZIzj0EAwIDRwAwRAIgX1spXjrUc10r3g/Zm4O31LU5O08J2vVqFo94zHE5 0VgCIG4JK9Zg5O/yn4mYksZux7efiHRUzL2y2TXQ9IqrqM8W """ int_ca_name = 'MyVyOS-IntCA' int_ca_pem = """ MIICYDCCAgWgAwIBAgIUcFx2BVYErHI+SneyPYHijxXt1cgwCgYIKoZIzj0EAwIw ZzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEgMB4GA1UEAwwXSVBTZWMgU21va2V0ZXN0 IFJvb3QgQ0EwHhcNMjMwOTI0MTIwNTE5WhcNMzMwOTIwMTIwNTE5WjBvMQswCQYD VQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTESMBAGA1UEBwwJU29tZS1DaXR5 MQ0wCwYDVQQKDARWeU9TMSgwJgYDVQQDDB9JUFNlYyBTbW9rZXRlc3QgSW50ZXJt ZWRpYXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIHw2G5dq3c715AcA tzR++dYu1fLRFmHzRGTZOT7hLrh2Fg4hnKFPLOeUA5Qi50xCvjJ9JnonTyy2RfRH axYizKOBhjCBgzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAd BgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHQYDVR0OBBYEFC9KrFYtA+hO l7vdMbWxTMAyLB7BMB8GA1UdIwQYMBaAFE2KECTZZO1/O/4EXoB5wpz3n9/QMAoG CCqGSM49BAMCA0kAMEYCIQCnqWbElgOL9dGO3iLxasFNq/hM7vM/DzaiHi4BowxW 0gIhAMohefNj+QgLfPhvyODHIPE9LMyfp7lJEaCC2K8PCSFD """ peer_name = 'peer1' peer_cert = """ MIICSTCCAfCgAwIBAgIUPxYleUgCo/glVVePze3QmAFgi6MwCgYIKoZIzj0EAwIw bzELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv bWUtQ2l0eTENMAsGA1UECgwEVnlPUzEoMCYGA1UEAwwfSVBTZWMgU21va2V0ZXN0 IEludGVybWVkaWF0ZSBDQTAeFw0yMzA5MjQxMjA2NDJaFw0yODA5MjIxMjA2NDJa MGQxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlT b21lLUNpdHkxDTALBgNVBAoMBFZ5T1MxHTAbBgNVBAMMFElQU2VjIFNtb2tldGVz dCBQZWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZJtuTDu84uy++GMwRNLl 10JAXZxXQSDl+CdTWwjbQZURcdY+ia7BoaoYX/0VKPel3Se64rIUQQLQoY/9MJb9 UKN1MHMwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI KwYBBQUHAwEwHQYDVR0OBBYEFNJCdnkm3cAmf04UwOKL7IqMJ6OXMB8GA1UdIwQY MBaAFC9KrFYtA+hOl7vdMbWxTMAyLB7BMAoGCCqGSM49BAMCA0cAMEQCIGVnDRUy UJ0U/deDvrBo1+AakZndkNAMN/XNo5a5GzhEAiBCY7E/3b0BIO8FiIbVB3iDcaxg g7ET2RgWxvhEoN3ZRw== """ peer_key = """ MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVDEZDK7q/T+tiJUV WLKS3ZYDfZ4lZv0C1gJpYq0gWP2hRANCAARkm25MO7zi7L74YzBE0uXXQkBdnFdB IOX4J1NbCNtBlRFx1j6JrsGhqhhf/RUo96XdJ7rishRBAtChj/0wlv1Q """ swanctl_dir = '/etc/swanctl' CERT_PATH = f'{swanctl_dir}/x509/' CA_PATH = f'{swanctl_dir}/x509ca/' class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): skip_process_check = False @classmethod def setUpClass(cls): super(TestVPNIPsec, 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) cls.cli_set(cls, base_path + ['interface', f'{interface}.{vif}']) @classmethod def tearDownClass(cls): super(TestVPNIPsec, cls).tearDownClass() cls.cli_delete(cls, base_path + ['interface', f'{interface}.{vif}']) def setUp(self): # Set IKE/ESP Groups self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'encryption', 'aes128']) self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '1', 'hash', 'sha1']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'dh-group', '2']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'encryption', 'aes128']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '1', 'hash', 'sha1']) def tearDown(self): # Check for running process if not self.skip_process_check: self.assertTrue(process_named_running(PROCESS_NAME)) else: self.skip_process_check = False # Reset self.cli_delete(base_path) self.cli_delete(tunnel_path) self.cli_commit() # 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 # Interface for dhcp-interface self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server # vpn ipsec auth psk id self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) # Site to site peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['default-esp-group', esp_group]) self.cli_set(peer_base_path + ['dhcp-interface', f'{interface}.{vif}']) self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre']) self.cli_commit() self.assertTrue(os.path.exists(dhcp_waiting_file)) dhcp_waiting = read_file(dhcp_waiting_file) self.assertIn(f'{interface}.{vif}', dhcp_waiting) # Ensure dhcp-failed interface was added for dhclient hook self.cli_delete(ethernet_path + [interface, 'vif', vif, 'address']) def test_02_site_to_site(self): self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) local_address = '192.0.2.10' priority = '20' life_bytes = '100000' life_packets = '2000000' # vpn ipsec auth psk id self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) # Site to site peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(base_path + ['esp-group', esp_group, 'life-bytes', life_bytes]) self.cli_set(base_path + ['esp-group', esp_group, 'life-packets', life_packets]) self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['default-esp-group', esp_group]) self.cli_set(peer_base_path + ['local-address', local_address]) self.cli_set(peer_base_path + ['remote-address', peer_ip]) self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'tcp']) self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24']) self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24']) self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'port', '443']) self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24']) self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.11.0/24']) self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'port', '443']) self.cli_set(peer_base_path + ['tunnel', '2', 'local', 'prefix', '10.1.0.0/16']) self.cli_set(peer_base_path + ['tunnel', '2', 'remote', 'prefix', '10.2.0.0/16']) self.cli_set(peer_base_path + ['tunnel', '2', 'priority', priority]) self.cli_commit() # Verify strongSwan configuration swanctl_conf = read_file(swanctl_file) swanctl_conf_lines = [ f'version = 2', f'auth = psk', f'life_bytes = {life_bytes}', f'life_packets = {life_packets}', f'rekey_time = 28800s', # default value f'proposals = aes128-sha1-modp1024', f'esp_proposals = aes128-sha1-modp1024', f'life_time = 3600s', # default value f'local_addrs = {local_address} # dhcp:no', f'remote_addrs = {peer_ip}', f'mode = tunnel', f'{connection_name}-tunnel-1', f'local_ts = 172.16.10.0/24[tcp/443],172.16.11.0/24[tcp/443]', f'remote_ts = 172.17.10.0/24[tcp/443],172.17.11.0/24[tcp/443]', f'mode = tunnel', f'{connection_name}-tunnel-2', f'local_ts = 10.1.0.0/16', f'remote_ts = 10.2.0.0/16', f'priority = {priority}', f'mode = tunnel', ] for line in swanctl_conf_lines: self.assertIn(line, swanctl_conf) swanctl_secrets_lines = [ f'id-{regex_uuid4} = "{local_id}"', f'id-{regex_uuid4} = "{remote_id}"', f'id-{regex_uuid4} = "{local_address}"', f'id-{regex_uuid4} = "{peer_ip}"', f'secret = "{secret}"' ] for line in swanctl_secrets_lines: self.assertRegex(swanctl_conf, fr'{line}') def test_03_site_to_site_vti(self): local_address = '192.0.2.10' vti = 'vti10' # IKE self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) self.cli_set(base_path + ['ike-group', ike_group, 'disable-mobike']) # ESP self.cli_set(base_path + ['esp-group', esp_group, 'compression']) # VTI interface self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24']) # vpn ipsec auth psk id self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) # Site to site peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) self.cli_set(peer_base_path + ['connection-type', 'none']) self.cli_set(peer_base_path + ['force-udp-encapsulation']) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['default-esp-group', esp_group]) self.cli_set(peer_base_path + ['local-address', local_address]) self.cli_set(peer_base_path + ['remote-address', peer_ip]) self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.10.0/24']) self.cli_set(peer_base_path + ['tunnel', '1', 'local', 'prefix', '172.16.11.0/24']) self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.10.0/24']) self.cli_set(peer_base_path + ['tunnel', '1', 'remote', 'prefix', '172.17.11.0/24']) self.cli_set(peer_base_path + ['vti', 'bind', vti]) self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group]) self.cli_commit() swanctl_conf = read_file(swanctl_file) if_id = vti.lstrip('vti') # The key defaults to 0 and will match any policies which similarly do # not have a lookup key configuration - thus we shift the key by one # to also support a vti0 interface if_id = str(int(if_id) +1) swanctl_conf_lines = [ f'version = 2', f'auth = psk', f'proposals = aes128-sha1-modp1024', f'esp_proposals = aes128-sha1-modp1024', f'local_addrs = {local_address} # dhcp:no', f'mobike = no', f'remote_addrs = {peer_ip}', f'mode = tunnel', f'local_ts = 172.16.10.0/24,172.16.11.0/24', f'remote_ts = 172.17.10.0/24,172.17.11.0/24', f'ipcomp = yes', f'start_action = none', f'if_id_in = {if_id}', # will be 11 for vti10 - shifted by one f'if_id_out = {if_id}', f'updown = "/etc/ipsec.d/vti-up-down {vti}"' ] for line in swanctl_conf_lines: self.assertIn(line, swanctl_conf) swanctl_secrets_lines = [ f'id-{regex_uuid4} = "{local_id}"', f'id-{regex_uuid4} = "{remote_id}"', f'secret = "{secret}"' ] for line in swanctl_secrets_lines: self.assertRegex(swanctl_conf, fr'{line}') def test_04_dmvpn(self): tunnel_if = 'tun100' nhrp_secret = 'secret' ike_lifetime = '3600' esp_lifetime = '1800' # Tunnel self.cli_set(tunnel_path + [tunnel_if, 'address', '172.16.253.134/29']) self.cli_set(tunnel_path + [tunnel_if, 'encapsulation', 'gre']) self.cli_set(tunnel_path + [tunnel_if, 'source-address', '192.0.2.1']) self.cli_set(tunnel_path + [tunnel_if, 'enable-multicast']) self.cli_set(tunnel_path + [tunnel_if, 'parameters', 'ip', 'key', '1']) # NHRP self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'cisco-authentication', nhrp_secret]) self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'holding-time', '300']) self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'multicast', 'dynamic']) self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'redirect']) self.cli_set(nhrp_path + ['tunnel', tunnel_if, 'shortcut']) # IKE/ESP Groups self.cli_set(base_path + ['esp-group', esp_group, 'lifetime', esp_lifetime]) self.cli_set(base_path + ['esp-group', esp_group, 'mode', 'transport']) self.cli_set(base_path + ['esp-group', esp_group, 'pfs', 'dh-group2']) 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', 'sha1']) self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'encryption', '3des']) self.cli_set(base_path + ['esp-group', esp_group, 'proposal', '3', 'hash', 'md5']) self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev1']) self.cli_set(base_path + ['ike-group', ike_group, 'lifetime', ike_lifetime]) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '2']) 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', 'sha1']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'prf', 'prfsha1']) # Profile self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'mode', 'pre-shared-secret']) self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'pre-shared-secret', nhrp_secret]) self.cli_set(base_path + ['profile', 'NHRPVPN', 'bind', 'tunnel', tunnel_if]) self.cli_set(base_path + ['profile', 'NHRPVPN', 'esp-group', esp_group]) self.cli_set(base_path + ['profile', 'NHRPVPN', 'ike-group', ike_group]) self.cli_commit() swanctl_conf = read_file(swanctl_file) swanctl_lines = [ f'proposals = aes128-sha1-modp1024,aes256-sha1-prfsha1-modp1024', f'version = 1', f'rekey_time = {ike_lifetime}s', f'rekey_time = {esp_lifetime}s', f'esp_proposals = aes128-sha1-modp1024,aes256-sha1-modp1024,3des-md5-modp1024', f'local_ts = dynamic[gre]', f'remote_ts = dynamic[gre]', f'mode = transport', f'secret = {nhrp_secret}' ] for line in swanctl_lines: self.assertIn(line, swanctl_conf) # There is only one NHRP test so no need to delete this globally in tearDown() self.cli_delete(nhrp_path) def test_05_x509_site2site(self): # Enable PKI self.setupPKI() vti = 'vti20' self.cli_set(vti_path + [vti, 'address', '192.168.0.1/31']) peer_ip = '172.18.254.202' connection_name = 'office' local_address = '172.18.254.201' peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(peer_base_path + ['authentication', 'local-id', peer_name]) self.cli_set(peer_base_path + ['authentication', 'mode', 'x509']) self.cli_set(peer_base_path + ['authentication', 'remote-id', 'peer2']) self.cli_set(peer_base_path + ['authentication', 'x509', 'ca-certificate', int_ca_name]) self.cli_set(peer_base_path + ['authentication', 'x509', 'certificate', peer_name]) self.cli_set(peer_base_path + ['connection-type', 'initiate']) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['ikev2-reauth', 'inherit']) self.cli_set(peer_base_path + ['local-address', local_address]) self.cli_set(peer_base_path + ['remote-address', peer_ip]) self.cli_set(peer_base_path + ['vti', 'bind', vti]) self.cli_set(peer_base_path + ['vti', 'esp-group', esp_group]) self.cli_commit() swanctl_conf = read_file(swanctl_file) tmp = peer_ip.replace('.', '-') if_id = vti.lstrip('vti') # The key defaults to 0 and will match any policies which similarly do # not have a lookup key configuration - thus we shift the key by one # to also support a vti0 interface if_id = str(int(if_id) +1) swanctl_lines = [ f'{connection_name}', f'version = 0', # key-exchange not set - defaulting to 0 for ikev1 and ikev2 f'send_cert = always', f'mobike = yes', f'keyingtries = 0', f'id = "{peer_name}"', f'auth = pubkey', f'certs = {peer_name}.pem', f'proposals = aes128-sha1-modp1024', f'esp_proposals = aes128-sha1-modp1024', f'local_addrs = {local_address} # dhcp:no', f'remote_addrs = {peer_ip}', f'local_ts = 0.0.0.0/0,::/0', f'remote_ts = 0.0.0.0/0,::/0', f'updown = "/etc/ipsec.d/vti-up-down {vti}"', f'if_id_in = {if_id}', # will be 11 for vti10 f'if_id_out = {if_id}', f'ipcomp = no', f'mode = tunnel', f'start_action = start', ] for line in swanctl_lines: self.assertIn(line, swanctl_conf) swanctl_secrets_lines = [ f'{connection_name}', f'file = {peer_name}.pem', ] for line in swanctl_secrets_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'{int_ca_name}_1.pem'))) self.assertTrue(os.path.exists(os.path.join(CA_PATH, f'{int_ca_name}_2.pem'))) self.assertTrue(os.path.exists(os.path.join(CERT_PATH, f'{peer_name}.pem'))) # 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' local_id = 'vyos-r1' remote_id = 'vyos-r2' peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(tunnel_path + ['tun1', 'encapsulation', 'gre']) self.cli_set(tunnel_path + ['tun1', 'source-address', local_address]) self.cli_set(base_path + ['interface', interface]) self.cli_set(base_path + ['options', 'flexvpn']) self.cli_set(base_path + ['options', 'interface', 'tun1']) self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) # vpn ipsec auth psk id self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) self.cli_set(peer_base_path + ['authentication', 'local-id', local_id]) self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) self.cli_set(peer_base_path + ['authentication', 'remote-id', remote_id]) self.cli_set(peer_base_path + ['connection-type', 'initiate']) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['default-esp-group', esp_group]) self.cli_set(peer_base_path + ['local-address', local_address]) self.cli_set(peer_base_path + ['remote-address', peer_ip]) self.cli_set(peer_base_path + ['tunnel', '1', 'protocol', 'gre']) self.cli_set(peer_base_path + ['virtual-address', '203.0.113.55']) self.cli_set(peer_base_path + ['virtual-address', '203.0.113.56']) self.cli_commit() # Verify strongSwan configuration swanctl_conf = read_file(swanctl_file) swanctl_conf_lines = [ f'version = 2', f'vips = 203.0.113.55, 203.0.113.56', f'life_time = 3600s', # default value f'local_addrs = {local_address} # dhcp:no', f'remote_addrs = {peer_ip}', f'{connection_name}-tunnel-1', f'mode = tunnel', ] for line in swanctl_conf_lines: self.assertIn(line, swanctl_conf) swanctl_secrets_lines = [ f'id-{regex_uuid4} = "{local_id}"', f'id-{regex_uuid4} = "{remote_id}"', f'id-{regex_uuid4} = "{peer_ip}"', f'id-{regex_uuid4} = "{local_address}"', f'secret = "{secret}"', ] for line in swanctl_secrets_lines: self.assertRegex(swanctl_conf, fr'{line}') # Verify charon configuration charon_conf = read_file(charon_file) charon_conf_lines = [ f'# Cisco FlexVPN', f'cisco_flexvpn = yes', f'install_virtual_ip = yes', f'install_virtual_ip_on = tun1', ] for line in charon_conf_lines: 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)