diff options
| author | Oleksandr Kuchmystyi <o.kuchmystyi@vyos.io> | 2025-12-31 10:50:21 +0300 |
|---|---|---|
| committer | Oleksandr Kuchmystyi <o.kuchmystyi@vyos.io> | 2026-01-12 16:44:32 +0300 |
| commit | 61487b13da8dc270907633a3140bd0cc2aa0926a (patch) | |
| tree | 967c8864116299c4a4d3307bc9666f54b2418761 | |
| parent | 7fea6b6dbbd786bf6a32c496fc70ca9e9b5f8b1f (diff) | |
| download | vyos-1x-61487b13da8dc270907633a3140bd0cc2aa0926a.tar.gz vyos-1x-61487b13da8dc270907633a3140bd0cc2aa0926a.zip | |
openvpn: T7633: Add support for 'data-ciphers-fallback' in site-to-site tunnels
- Introduced new CLI option 'data-ciphers-fallback' for OpenVPN interfaces
(used as fallback cipher in site-to-site mode)
- Adjust migration 1‑to‑2 to skip migrating 'cipher' for site‑to‑site interfaces
| -rw-r--r-- | data/templates/openvpn/server.conf.j2 | 3 | ||||
| -rw-r--r-- | interface-definitions/include/version/openvpn-version.xml.i | 2 | ||||
| -rw-r--r-- | interface-definitions/interfaces_openvpn.xml.in | 43 | ||||
| -rwxr-xr-x | python/vyos/template.py | 5 | ||||
| -rw-r--r-- | smoketest/configs/assert/vpn-openvpn | 6 | ||||
| -rw-r--r-- | smoketest/configs/vpn-openvpn | 12 | ||||
| -rwxr-xr-x | smoketest/scripts/cli/test_interfaces_openvpn.py | 24 | ||||
| -rwxr-xr-x | src/conf_mode/interfaces_openvpn.py | 8 | ||||
| -rw-r--r-- | src/migration-scripts/openvpn/1-to-2 | 6 | ||||
| -rw-r--r-- | src/migration-scripts/openvpn/4-to-5 | 38 |
10 files changed, 146 insertions, 1 deletions
diff --git a/data/templates/openvpn/server.conf.j2 b/data/templates/openvpn/server.conf.j2 index be811f45e..19c8e226c 100644 --- a/data/templates/openvpn/server.conf.j2 +++ b/data/templates/openvpn/server.conf.j2 @@ -217,6 +217,9 @@ cipher {{ encryption.cipher | openvpn_cipher }} {% if encryption.data_ciphers is vyos_defined %} data-ciphers {{ encryption.data_ciphers | openvpn_data_ciphers }} {% endif %} +{% if encryption.data_ciphers_fallback is vyos_defined %} +data-ciphers-fallback {{ encryption.data_ciphers_fallback | openvpn_data_ciphers_fallback }} +{% endif %} {% endif %} providers default diff --git a/interface-definitions/include/version/openvpn-version.xml.i b/interface-definitions/include/version/openvpn-version.xml.i index 67ef21983..6bc2b2da6 100644 --- a/interface-definitions/include/version/openvpn-version.xml.i +++ b/interface-definitions/include/version/openvpn-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/openvpn-version.xml.i --> -<syntaxVersion component='openvpn' version='4'></syntaxVersion> +<syntaxVersion component='openvpn' version='5'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in index 6510ed733..9ce05db9c 100644 --- a/interface-definitions/interfaces_openvpn.xml.in +++ b/interface-definitions/interfaces_openvpn.xml.in @@ -131,6 +131,49 @@ <multi/> </properties> </leafNode> + <leafNode name="data-ciphers-fallback"> + <properties> + <help>Fallback cipher to use for site-to-site tunnels</help> + <completionHelp> + <list>none 3des aes128 aes128gcm aes192 aes192gcm aes256 aes256gcm</list> + </completionHelp> + <valueHelp> + <format>none</format> + <description>Disable encryption</description> + </valueHelp> + <valueHelp> + <format>3des</format> + <description>DES algorithm with triple encryption</description> + </valueHelp> + <valueHelp> + <format>aes128</format> + <description>AES algorithm with 128-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes128gcm</format> + <description>AES algorithm with 128-bit key GCM</description> + </valueHelp> + <valueHelp> + <format>aes192</format> + <description>AES algorithm with 192-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes192gcm</format> + <description>AES algorithm with 192-bit key GCM</description> + </valueHelp> + <valueHelp> + <format>aes256</format> + <description>AES algorithm with 256-bit key CBC</description> + </valueHelp> + <valueHelp> + <format>aes256gcm</format> + <description>AES algorithm with 256-bit key GCM</description> + </valueHelp> + <constraint> + <regex>(none|3des|aes128|aes128gcm|aes192|aes192gcm|aes256|aes256gcm)</regex> + </constraint> + </properties> + </leafNode> </children> </node> #include <include/interface/ipv4-options.xml.i> diff --git a/python/vyos/template.py b/python/vyos/template.py index 128471f42..6b3e329a3 100755 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -572,6 +572,11 @@ def get_openvpn_data_ciphers(ciphers): out.append(cipher) return ':'.join(out).upper() + +@register_filter('openvpn_data_ciphers_fallback') +def get_openvpn_data_ciphers_fallback(cipher): + return get_openvpn_cipher(cipher) + @register_filter('snmp_auth_oid') def snmp_auth_oid(type): if type not in ['md5', 'sha', 'aes', 'des', 'none']: diff --git a/smoketest/configs/assert/vpn-openvpn b/smoketest/configs/assert/vpn-openvpn index 55b1aa879..f7b9857de 100644 --- a/smoketest/configs/assert/vpn-openvpn +++ b/smoketest/configs/assert/vpn-openvpn @@ -91,6 +91,12 @@ set interfaces openvpn vtun42 local-port '10042' set interfaces openvpn vtun42 mode 'site-to-site' set interfaces openvpn vtun42 remote-address '192.0.2.5' set interfaces openvpn vtun42 shared-secret-key 'openvpn_vtun40_shared' +set interfaces openvpn vtun43 local-address 100.64.40.6 subnet-mask '255.255.255.254' +set interfaces openvpn vtun43 local-port '10043' +set interfaces openvpn vtun43 mode 'site-to-site' +set interfaces openvpn vtun43 remote-address '192.0.2.6' +set interfaces openvpn vtun43 shared-secret-key 'openvpn_vtun40_shared' +set interfaces openvpn vtun43 encryption data-ciphers-fallback 'aes128' set pki ca openvpn_vtun10_1 certificate 'MIIFFDCCA/ygAwIBAgIULRXlIT+yqqWW/OCBGnOtDtxmeJowDQYJKoZIhvcNAQELBQAwgasxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQ4wDAYDVQQHDAVQb3dheTEWMBQGA1UECgwNVnlPUyBOZXR3b3JrczEgMB4GA1UECwwXVnlPUyBOZXR3b3JrcyBUZXN0IENBIDExFDASBgNVBAMMC0Vhc3ktUlNBIENBMScwJQYJKoZIhvcNAQkBFhh0ZXN0LWNhLm5vcmVwbHlAdnlvcy5kZXYwHhcNMjUxMjAxMDc0NDU4WhcNMzUxMTI5MDc0NDU4WjCBqzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExDjAMBgNVBAcMBVBvd2F5MRYwFAYDVQQKDA1WeU9TIE5ldHdvcmtzMSAwHgYDVQQLDBdWeU9TIE5ldHdvcmtzIFRlc3QgQ0EgMTEUMBIGA1UEAwwLRWFzeS1SU0EgQ0ExJzAlBgkqhkiG9w0BCQEWGHRlc3QtY2Eubm9yZXBseUB2eW9zLmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALQHJWBuKQrFvX6pOhpk3FlJaCiwDxPWib0EfMZWeF+CsvHxYH1W/WZfhVW2PcL8Zti0ppoqRsIQpyUDygtwWLjwMCVnQtD9yaA6rUImisQfNsR/ePrFaFK+8fSrGGS4poUnKFueNKzq4V9K7xMFb6+TrecvoclYDEeBMOpCt3dOSCwNlKa2CboUA0mbATXMiIm9lW/1K8bJ7UwRydiEIU9ewi3ZCFXNw7FWj1h3cR1JwxwMPcL9jzm+jlhBTLCy7yiOkdCw7vVFBRa+OaxQ+tTvS+IpPv5f4/9Hr19IqmsR5HBOHJrYvgD3LDGFZZ1uuKeG4CM8N8G7K9DxqXvEl7ECAwEAAaOCASwwggEoMB0GA1UdDgQWBBTh+x9ektMHGHxAY/+cZMng8fuGpTCB6wYDVR0jBIHjMIHggBTh+x9ektMHGHxAY/+cZMng8fuGpaGBsaSBrjCBqzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExDjAMBgNVBAcMBVBvd2F5MRYwFAYDVQQKDA1WeU9TIE5ldHdvcmtzMSAwHgYDVQQLDBdWeU9TIE5ldHdvcmtzIFRlc3QgQ0EgMTEUMBIGA1UEAwwLRWFzeS1SU0EgQ0ExJzAlBgkqhkiG9w0BCQEWGHRlc3QtY2Eubm9yZXBseUB2eW9zLmRldoIULRXlIT+yqqWW/OCBGnOtDtxmeJowDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBACgqIP4xZo6ioKnlNLhix4WRp0tRkveAKJ144ylCzzzP5+pdPQKS5hlydDcNxrJB6EYlkmaWBsvYt7NO9oII4wwD7uvD7Tnad31ANc9lkD8qEksoVy5Sc9HpYQvo+a8ZH/Co8ciTse/eUAIWzp8GHTH40HeqGtJGvLF4pHXxHYBO8vuZAL0Bs8hl7ihyOrRE5fkwjmWH3RcsavTF5jhAOsRsInUiqLUafFChvte3z739irjCaIK0lt0ZWfKvWVeV+JeNgwl0mqNq78FZRQa7rC72x3UJJwV1xRcPp4Y+WW7Xb8gTZqkuMzBW3xgYuKlOmHOz1Ta2ktEteDdS5PF/c+g=' set pki ca openvpn_vtun10_1 crl 'MIIDDzCCAfcCAQEwDQYJKoZIhvcNAQELBQAwgasxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQ4wDAYDVQQHDAVQb3dheTEWMBQGA1UECgwNVnlPUyBOZXR3b3JrczEgMB4GA1UECwwXVnlPUyBOZXR3b3JrcyBUZXN0IENBIDExFDASBgNVBAMMC0Vhc3ktUlNBIENBMScwJQYJKoZIhvcNAQkBFhh0ZXN0LWNhLm5vcmVwbHlAdnlvcy5kZXYXDTI1MTIwMTA5MjYwOFoXDTI2MDUzMDA5MjYwOFowIzAhAhAQ70nBplg7b4g0yD0jTde6Fw0yNTEyMDEwOTI2MDBaoIHxMIHuMIHrBgNVHSMEgeMwgeCAFOH7H16S0wcYfEBj/5xkyeDx+4aloYGxpIGuMIGrMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEOMAwGA1UEBwwFUG93YXkxFjAUBgNVBAoMDVZ5T1MgTmV0d29ya3MxIDAeBgNVBAsMF1Z5T1MgTmV0d29ya3MgVGVzdCBDQSAxMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTEnMCUGCSqGSIb3DQEJARYYdGVzdC1jYS5ub3JlcGx5QHZ5b3MuZGV2ghQtFeUhP7KqpZb84IEac60O3GZ4mjANBgkqhkiG9w0BAQsFAAOCAQEAFVgGK+EIWsTO1d3PGeh+Sxp9etu60opYYcvtlEqZsyLOp8oJYslo62BNcrxzLIoRTViEVqyFjxyl2+mTk9q/REZqcSSesBpdkzErdVLixCEeIfSgmBd3yoqih2gGR+fEibG6ETbYAc4fzZN761F5Z7Wurm65s5Xgl4X6Vl989kskiw8C0ESeSUKciINPmwWbwTRvJRStJve5NtkqpM6Rdq0SoTA0XWQqRcc7Aicv3/SI5aPpEGTLFKuRoCgGGhnTQywaNACU/BykbjGiiFcwNWpfHrYj3lCFm2Gyw9Sw70PT1RGBDq9yBSa0Px2ri1WjhnYF58PjrW3m1laoI+LkIg==' set pki ca openvpn_vtun20_1 certificate 'MIIFFDCCA/ygAwIBAgIUCKOAES495hrgXHMl0IQb1nkcWsEwDQYJKoZIhvcNAQELBQAwgasxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQ4wDAYDVQQHDAVQb3dheTEWMBQGA1UECgwNVnlPUyBOZXR3b3JrczEgMB4GA1UECwwXVnlPUyBOZXR3b3JrcyBUZXN0IENBIDIxFDASBgNVBAMMC0Vhc3ktUlNBIENBMScwJQYJKoZIhvcNAQkBFhh0ZXN0LWNhLm5vcmVwbHlAdnlvcy5kZXYwHhcNMjUxMjAxMDc1MzE5WhcNMzUxMTI5MDc1MzE5WjCBqzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExDjAMBgNVBAcMBVBvd2F5MRYwFAYDVQQKDA1WeU9TIE5ldHdvcmtzMSAwHgYDVQQLDBdWeU9TIE5ldHdvcmtzIFRlc3QgQ0EgMjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0ExJzAlBgkqhkiG9w0BCQEWGHRlc3QtY2Eubm9yZXBseUB2eW9zLmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMxcxr7KKqKuUOWpyE/GZ2BYvMxXEdDavQS3Ok2n5hFAnCAqtjCx63z26P18xJdqkpWpsyTxN5aEGgXyFUIL6kiceL4D9vIO52swI8QaRi/4V7X86wJQqUaQYg7FMboeyBsLXFMl+TX1vkX/bgFiGSfqS0oTPZt68GO6jHxbE1tqqhS2qQSVt/4WJMyZ3BFjQpz0szwcVh2tHKqSSuhpS0MY8MKpeITo6LhPPphfpdBn6fizydj1IEdXD22YJK+YUYv2AFY5V6GzYYQpO2oGe9CGW68a/Q+OtocZYvjpCIm21UFnfkxz4vdW7vNgBSXvfKOEZ+ZhKzaNL6Ftj0afJdsCAwEAAaOCASwwggEoMB0GA1UdDgQWBBTaP0CGONZGw8I/Bze7xjO1p3dKszCB6wYDVR0jBIHjMIHggBTaP0CGONZGw8I/Bze7xjO1p3dKs6GBsaSBrjCBqzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExDjAMBgNVBAcMBVBvd2F5MRYwFAYDVQQKDA1WeU9TIE5ldHdvcmtzMSAwHgYDVQQLDBdWeU9TIE5ldHdvcmtzIFRlc3QgQ0EgMjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0ExJzAlBgkqhkiG9w0BCQEWGHRlc3QtY2Eubm9yZXBseUB2eW9zLmRldoIUCKOAES495hrgXHMl0IQb1nkcWsEwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBADjhWXjaR5EZoqHBD+H2F+YFg4oxf5vTr37JrsDINZG2cDprGQUl/8MrjziuVobtFzFLMlYwI+s8ASV1eYa+8yGrZFspsaJ3FkvWLx1CRCbP+jH+rJu+PzkcNRf5RinNxRVt/TnZlWKGAACsoGyzQJ3bg0NQKb47W0vD6PZ4nKK3SWvLdLBPW0bYpe62S3fE188fq4gr4Ig4J0Egi2aZhX/MHpT9bYGvQih+FgY33aQiGQphX1FX2n4kC7fwcJlzlp+mMhmnjWwuWSxRMFGAp6aJlPBvWbE3GyRx0b7skH7xmbRpV3DYbzxAxpzXw5o2xUx/tyQKW76Xp8nKZtK7jQM=' diff --git a/smoketest/configs/vpn-openvpn b/smoketest/configs/vpn-openvpn index e50a49aba..95a7e76f9 100644 --- a/smoketest/configs/vpn-openvpn +++ b/smoketest/configs/vpn-openvpn @@ -214,6 +214,18 @@ interfaces { remote-address 192.0.2.5 shared-secret-key-file /config/auth/openvpn/shared-secret-2.key } + openvpn vtun43 { + encryption { + cipher aes128 + } + local-address 100.64.40.6 { + subnet-mask 255.255.255.254 + } + local-port 10043 + mode site-to-site + remote-address 192.0.2.6 + shared-secret-key-file /config/auth/openvpn/shared-secret-2.key + } } protocols { ospf { diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index e646ec6fd..9286a8c0e 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -816,6 +816,30 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() + def test_site2site_data_ciphers_fallback(self): + vtun_if = 'vtun2010' + path = ['interfaces', 'openvpn', vtun_if] + + # Configure a minimal site-to-site tunnel with data-ciphers-fallback + self.cli_set(path + ['mode', 'site-to-site']) + self.cli_set(path + ['encryption', 'data-ciphers-fallback', 'aes192']) + self.cli_set(path + ['local-address', '10.0.1.1']) + self.cli_set(path + ['remote-address', '10.0.1.2']) + self.cli_set(path + ['shared-secret-key', 'ovpn_test']) + + self.cli_commit() + + config_file = f'/run/openvpn/{vtun_if}.conf' + config = read_file(config_file) + + # Validate correct OpenVPN configuration rendering + self.assertIn(f'dev {vtun_if}', config) + self.assertIn('data-ciphers-fallback AES-192-CBC', config) + + # Ensure no other directives are rendered + self.assertNotIn('cipher ', config) + self.assertNotIn('data-ciphers ', config) + def test_openvpn_server_server_bridge(self): # Create OpenVPN server interface using bridge. # Validate configuration afterwards. diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py index 5cc09da89..bb9d06389 100755 --- a/src/conf_mode/interfaces_openvpn.py +++ b/src/conf_mode/interfaces_openvpn.py @@ -168,6 +168,12 @@ def is_ec_private_key(pki, cert_name): key = load_private_key(pki_cert['private']['key']) return isinstance(key, ec.EllipticCurvePrivateKey) + +def verify_data_ciphers_fallback(openvpn): + if openvpn['mode'] != 'site-to-site': + if dict_search('encryption.data_ciphers_fallback', openvpn): + raise ConfigError('Cipher fallback is valid only in site-to-site mode') + def verify_pki(openvpn): pki = openvpn['pki'] interface = openvpn['ifname'] @@ -615,6 +621,8 @@ def verify(openvpn): verify_bond_bridge_member(openvpn) verify_mirror_redirect(openvpn) + verify_data_ciphers_fallback(openvpn) + return None def generate_pki_files(openvpn): diff --git a/src/migration-scripts/openvpn/1-to-2 b/src/migration-scripts/openvpn/1-to-2 index 83d6219d6..75360446c 100644 --- a/src/migration-scripts/openvpn/1-to-2 +++ b/src/migration-scripts/openvpn/1-to-2 @@ -25,11 +25,17 @@ def migrate(config: ConfigTree) -> None: # Remove 'encryption cipher' and add this value to 'encryption ncp-ciphers' # for server and client mode. # Site-to-site mode still can use --cipher option + mode_path = ['interfaces', 'openvpn', i, 'mode'] cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'cipher'] ncp_cipher_path = ['interfaces', 'openvpn', i, 'encryption', 'ncp-ciphers'] + if config.exists(cipher_path): if config.exists(['interfaces', 'openvpn', i, 'shared-secret-key']): continue + # Only migrate if mode is not 'site-to-site' + if config.value_exists(mode_path, 'site-to-site'): + continue + cipher = config.return_value(cipher_path) config.delete(cipher_path) if cipher == 'none': diff --git a/src/migration-scripts/openvpn/4-to-5 b/src/migration-scripts/openvpn/4-to-5 new file mode 100644 index 000000000..5c7ee833c --- /dev/null +++ b/src/migration-scripts/openvpn/4-to-5 @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# Copyright 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 +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. +# +# T7633: This migration converts legacy 'encryption cipher' directives into +# 'encryption data-ciphers-fallback' for site-to-site mode tunnels. +# +# In modern OpenVPN (v2.6+), 'cipher' and 'data-ciphers' are not valid +# in site-to-site mode. +# The appropriate directive is now '--data-ciphers-fallback alg'. + +from vyos.configtree import ConfigTree + +def migrate(config: ConfigTree) -> None: + ovpn_intfs = config.list_nodes(['interfaces', 'openvpn'], path_must_exist=False) + for i in ovpn_intfs: + base_path = ['interfaces', 'openvpn', i] + mode_path = base_path + ['mode'] + cipher_path = base_path + ['encryption', 'cipher'] + + # Only migrate if mode is explicitly 'site-to-site' + if config.value_exists(mode_path, 'site-to-site'): + + # Rename 'encryption cipher' with 'encryption data-ciphers-fallback' + if config.exists(cipher_path): + config.rename(cipher_path, 'data-ciphers-fallback') |
