summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsarthurdev <965089+sarthurdev@users.noreply.github.com>2021-07-21 14:36:48 +0200
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2021-07-21 22:48:18 +0200
commita9e9c4acfa90fc15a8a4b6b5ea6e1c2814ce940e (patch)
treef42f7d9f65cdbf0b832373e68fd71e253a69f452
parent936b36fdf180fce830dbc388ec5e8fc35feb9474 (diff)
downloadvyos-1x-a9e9c4acfa90fc15a8a4b6b5ea6e1c2814ce940e.tar.gz
vyos-1x-a9e9c4acfa90fc15a8a4b6b5ea6e1c2814ce940e.zip
pki: openvpn: T3642: Migrate OpenVPN to PKI and refactor
-rw-r--r--data/templates/openvpn/server.conf.tmpl83
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in73
-rw-r--r--op-mode-definitions/openvpn.xml.in44
-rw-r--r--python/vyos/pki.py5
-rw-r--r--python/vyos/template.py29
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py151
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py326
-rwxr-xr-xsrc/migration-scripts/interfaces/22-to-23219
8 files changed, 574 insertions, 356 deletions
diff --git a/data/templates/openvpn/server.conf.tmpl b/data/templates/openvpn/server.conf.tmpl
index c5d665c0b..d9f01310e 100644
--- a/data/templates/openvpn/server.conf.tmpl
+++ b/data/templates/openvpn/server.conf.tmpl
@@ -36,8 +36,8 @@ rport {{ remote_port }}
remote {{ remote }}
{% endfor %}
{% endif %}
-{% if shared_secret_key_file is defined and shared_secret_key_file is not none %}
-secret {{ shared_secret_key_file }}
+{% if shared_secret_key is defined and shared_secret_key is not none %}
+secret /run/openvpn/{{ ifname }}_shared.key
{% endif %}
{% if persistent_tunnel is defined %}
persist-tun
@@ -157,32 +157,32 @@ ifconfig-ipv6 {{ laddr }} {{ raddr }}
{% if tls is defined and tls is not none %}
# TLS options
-{% if tls.ca_cert_file is defined and tls.ca_cert_file is not none %}
-ca {{ tls.ca_cert_file }}
+{% if tls.ca_certificate is defined and tls.ca_certificate is not none %}
+ca /run/openvpn/{{ ifname }}_ca.pem
{% endif %}
-{% if tls.cert_file is defined and tls.cert_file is not none %}
-cert {{ tls.cert_file }}
+{% if tls.certificate is defined and tls.certificate is not none %}
+cert /run/openvpn/{{ ifname }}_cert.pem
{% endif %}
-{% if tls.key_file is defined and tls.key_file is not none %}
-key {{ tls.key_file }}
+{% if tls.private_key is defined %}
+key /run/openvpn/{{ ifname }}_cert.key
{% endif %}
-{% if tls.crypt_file is defined and tls.crypt_file is not none %}
-tls-crypt {{ tls.crypt_file }}
+{% if tls.crypt_key is defined and tls.crypt_key is not none %}
+tls-crypt /run/openvpn/{{ ifname }}_crypt.key
{% endif %}
-{% if tls.crl_file is defined and tls.crl_file is not none %}
-crl-verify {{ tls.crl_file }}
+{% if tls.crl is defined %}
+crl-verify /run/openvpn/{{ ifname }}_crl.pem
{% endif %}
{% if tls.tls_version_min is defined and tls.tls_version_min is not none %}
tls-version-min {{ tls.tls_version_min }}
{% endif %}
-{% if tls.dh_file is defined and tls.dh_file is not none %}
-dh {{ tls.dh_file }}
+{% if tls.dh_params is defined and tls.dh_params is not none %}
+dh /run/openvpn/{{ ifname }}_dh.pem
{% endif %}
-{% if tls.auth_file is defined and tls.auth_file is not none %}
+{% if tls.auth_key is defined and tls.auth_key is not none %}
{% if mode == 'client' %}
-tls-auth {{ tls.auth_file }} 1
+tls-auth /run/openvpn/{{ ifname }}_auth.key 1
{% elif mode == 'server' %}
-tls-auth {{ tls.auth_file }} 0
+tls-auth /run/openvpn/{{ ifname }}_auth.key 0
{% endif %}
{% endif %}
{% if tls.role is defined and tls.role is not none %}
@@ -197,56 +197,15 @@ tls-server
# Encryption options
{% if encryption is defined and encryption is not none %}
{% if encryption.cipher is defined and encryption.cipher is not none %}
-{% if encryption.cipher == 'none' %}
-cipher none
-{% elif encryption.cipher == 'des' %}
-cipher des-cbc
-{% elif encryption.cipher == '3des' %}
-cipher des-ede3-cbc
-{% elif encryption.cipher == 'bf128' %}
-cipher bf-cbc
+cipher {{ encryption.cipher | openvpn_cipher }}
+{% if encryption.cipher == 'bf128' %}
keysize 128
{% elif encryption.cipher == 'bf256' %}
-cipher bf-cbc
-keysize 25
-{% elif encryption.cipher == 'aes128gcm' %}
-cipher aes-128-gcm
-{% elif encryption.cipher == 'aes128' %}
-cipher aes-128-cbc
-{% elif encryption.cipher == 'aes192gcm' %}
-cipher aes-192-gcm
-{% elif encryption.cipher == 'aes192' %}
-cipher aes-192-cbc
-{% elif encryption.cipher == 'aes256gcm' %}
-cipher aes-256-gcm
-{% elif encryption.cipher == 'aes256' %}
-cipher aes-256-cbc
+keysize 256
{% endif %}
{% endif %}
{% if encryption.ncp_ciphers is defined and encryption.ncp_ciphers is not none %}
-{% set cipher_list = [] %}
-{% for cipher in encryption.ncp_ciphers %}
-{% if cipher == 'none' %}
-{% set cipher_list = cipher_list.append('none') %}
-{% elif cipher == 'des' %}
-{% set cipher_list = cipher_list.append('des-cbc') %}
-{% elif cipher == '3des' %}
-{% set cipher_list = cipher_list.append('des-ede3-cbc') %}
-{% elif cipher == 'aes128' %}
-{% set cipher_list = cipher_list.append('aes-128-cbc') %}
-{% elif cipher == 'aes128gcm' %}
-{% set cipher_list = cipher_list.append('aes-128-gcm') %}
-{% elif cipher == 'aes192' %}
-{% set cipher_list = cipher_list.append('aes-192-cbc') %}
-{% elif cipher == 'aes192gcm' %}
-{% set cipher_list = cipher_list.append('aes-192-gcm') %}
-{% elif cipher == 'aes256' %}
-{% set cipher_list = cipher_list.append('aes-256-cbc') %}
-{% elif cipher == 'aes256gcm' %}
-{% set cipher_list = cipher_list.append('aes-256-gcm') %}
-{% endif %}
-{% endfor %}
-ncp-ciphers {{ cipher_list | join(':') }}:{{ cipher_list | join(':') | upper }}
+data-ciphers {{ encryption.ncp_ciphers | openvpn_ncp_ciphers }}
{% endif %}
{% endif %}
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index 681290570..7ff08ac86 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -637,16 +637,12 @@
</leafNode>
</children>
</node>
- <leafNode name="shared-secret-key-file">
+ <leafNode name="shared-secret-key">
<properties>
- <help>File containing the secret key shared with remote end of tunnel</help>
- <valueHelp>
- <format>filename</format>
- <description>File in /config/auth directory</description>
- </valueHelp>
- <constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
- </constraint>
+ <help>Secret key shared with remote end of tunnel</help>
+ <completionHelp>
+ <path>pki openvpn shared-secret</path>
+ </completionHelp>
</properties>
</leafNode>
<node name="tls">
@@ -654,55 +650,30 @@
<help>Transport Layer Security (TLS) options</help>
</properties>
<children>
- <leafNode name="auth-file">
- <properties>
- <help>File containing tls static key for tls-auth</help>
- <valueHelp>
- <format>filename</format>
- <description>File in /config/auth directory</description>
- </valueHelp>
- <constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/certificate.xml.i>
- #include <include/certificate-ca.xml.i>
- <leafNode name="crl-file">
+ <leafNode name="auth-key">
<properties>
- <help>File containing certificate revocation list (CRL) for this host</help>
- <valueHelp>
- <format>filename</format>
- <description>File in /config/auth directory</description>
- </valueHelp>
- <constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
- </constraint>
+ <help>TLS shared secret key for tls-auth</help>
+ <completionHelp>
+ <path>pki openvpn shared-secret</path>
+ </completionHelp>
</properties>
</leafNode>
- <leafNode name="dh-file">
+ #include <include/pki/certificate.xml.i>
+ #include <include/pki/ca-certificate.xml.i>
+ <leafNode name="dh-params">
<properties>
- <help>File containing Diffie Hellman parameters (server only)</help>
- <valueHelp>
- <format>filename</format>
- <description>File in /config/auth directory</description>
- </valueHelp>
- <constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
- </constraint>
+ <help>Diffie Hellman parameters (server only)</help>
+ <completionHelp>
+ <path>pki dh</path>
+ </completionHelp>
</properties>
</leafNode>
- #include <include/certificate-key.xml.i>
- <leafNode name="crypt-file">
+ <leafNode name="crypt-key">
<properties>
- <help>File containing encryption key to authenticate control channel</help>
- <valueHelp>
- <format>filename</format>
- <description>File in /config/auth directory</description>
- </valueHelp>
- <constraint>
- <validator name="file-exists" argument="--directory /config/auth"/>
- </constraint>
+ <help>Static key to use to authenticate control channel</help>
+ <completionHelp>
+ <path>pki openvpn shared-secret</path>
+ </completionHelp>
</properties>
</leafNode>
<leafNode name="tls-version-min">
diff --git a/op-mode-definitions/openvpn.xml.in b/op-mode-definitions/openvpn.xml.in
index f8dc0cff0..781fbdc9d 100644
--- a/op-mode-definitions/openvpn.xml.in
+++ b/op-mode-definitions/openvpn.xml.in
@@ -1,49 +1,5 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="generate">
- <children>
- <node name="openvpn">
- <properties>
- <help>OpenVPN key generation tool</help>
- </properties>
- <children>
- <tagNode name="key">
- <properties>
- <help>Generate shared-secret key with specified file name</help>
- <completionHelp>
- <list>&lt;filename&gt;</list>
- </completionHelp>
- </properties>
- <command>
- result=1;
- key_path=$4
- full_path=
-
- if echo $key_path | egrep -ve '^/.*' &gt; /dev/null; then
- full_path=/config/auth/$key_path
- else
- full_path=$key_path
- fi
-
- key_dir=`dirname $full_path`
- if [ ! -d $key_dir ]; then
- echo "Directory $key_dir does not exist!"
- exit 1
- fi
-
- echo "Generating OpenVPN key to $full_path"
- sudo /usr/sbin/openvpn --genkey secret "$full_path"
- result=$?
- if [ $result = 0 ]; then
- echo "Your new local OpenVPN key has been generated"
- fi
- /usr/libexec/vyos/validators/file-exists --directory /config/auth "$full_path"
- </command>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
<node name="reset">
<properties>
<help>Reset a service</help>
diff --git a/python/vyos/pki.py b/python/vyos/pki.py
index 1c6282d84..68ad73bf2 100644
--- a/python/vyos/pki.py
+++ b/python/vyos/pki.py
@@ -43,6 +43,8 @@ CSR_BEGIN='-----BEGIN CERTIFICATE REQUEST-----\n'
CSR_END='\n-----END CERTIFICATE REQUEST-----'
DH_BEGIN='-----BEGIN DH PARAMETERS-----\n'
DH_END='\n-----END DH PARAMETERS-----'
+OVPN_BEGIN = '-----BEGIN OpenVPN Static key V{0}-----\n'
+OVPN_END = '\n-----END OpenVPN Static key V{0}-----'
# Print functions
@@ -227,6 +229,9 @@ def wrap_crl(raw_data):
def wrap_dh_parameters(raw_data):
return DH_BEGIN + raw_data + DH_END
+def wrap_openvpn_key(raw_data, version='1'):
+ return OVPN_BEGIN.format(version) + raw_data + OVPN_END.format(version)
+
# Load functions
def load_public_key(raw_data, wrap_tags=True):
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 0d2bd39e7..6902d3720 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -439,3 +439,32 @@ def get_uuid(interface):
""" Get interface IP addresses"""
from uuid import uuid1
return uuid1()
+
+openvpn_translate = {
+ 'des': 'des-cbc',
+ '3des': 'des-ede3-cbc',
+ 'bf128': 'bf-cbc',
+ 'bf256': 'bf-cbc',
+ 'aes128gcm': 'aes-128-gcm',
+ 'aes128': 'aes-128-cbc',
+ 'aes192gcm': 'aes-192-gcm',
+ 'aes192': 'aes-192-cbc',
+ 'aes256gcm': 'aes-256-gcm',
+ 'aes256': 'aes-256-cbc'
+}
+
+@register_filter('openvpn_cipher')
+def get_openvpn_cipher(cipher):
+ if cipher in openvpn_translate:
+ return openvpn_translate[cipher].upper()
+ return cipher.upper()
+
+@register_filter('openvpn_ncp_ciphers')
+def get_openvpn_ncp_ciphers(ciphers):
+ out = []
+ for cipher in ciphers:
+ if cipher in openvpn_translate:
+ out.append(openvpn_translate[cipher])
+ else:
+ out.append(cipher)
+ return ':'.join(out).upper()
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 68c61b98c..7ce1b9872 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -37,12 +37,11 @@ from vyos.template import netmask_from_cidr
PROCESS_NAME = 'openvpn'
base_path = ['interfaces', 'openvpn']
-ca_cert = '/config/auth/ovpn_test_ca.pem'
-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'
+
+cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0='
+key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww'
+dh_data = 'MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFOFxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZUHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS67q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg=='
+ovpn_key_data = '443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa431d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e7042a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e65210b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a'
remote_port = '1194'
protocol = 'udp'
@@ -65,6 +64,12 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_set(['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32'])
self.cli_set(['vrf', 'name', vrf_name, 'table', '12345'])
+ self.cli_set(['pki', 'ca', 'ovpn_test', 'certificate', cert_data])
+ self.cli_set(['pki', 'certificate', 'ovpn_test', 'certificate', cert_data])
+ self.cli_set(['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data])
+ self.cli_set(['pki', 'dh', 'ovpn_test', 'parameters', dh_data])
+ self.cli_set(['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data])
+
def tearDown(self):
self.cli_delete(base_path)
self.cli_delete(['interfaces', 'dummy', dummy_if])
@@ -101,25 +106,24 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_set(path + ['remote-host', '192.0.9.9'])
- # check validate() - cannot specify "tls dh-file" in client mode
- self.cli_set(path + ['tls', 'dh-file', dh_pem])
+ # check validate() - cannot specify "tls dh-params" in client mode
+ self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
self.cli_delete(path + ['tls'])
- # check validate() - must specify one of "shared-secret-key-file" and "tls"
+ # check validate() - must specify one of "shared-secret-key" and "tls"
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(path + ['shared-secret-key-file', s2s_key])
+ self.cli_set(path + ['shared-secret-key', 'ovpn_test'])
- # check validate() - must specify one of "shared-secret-key-file" and "tls"
+ # check validate() - must specify one of "shared-secret-key" and "tls"
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_delete(path + ['shared-secret-key-file', s2s_key])
+ self.cli_delete(path + ['shared-secret-key', 'ovpn_test'])
- self.cli_set(path + ['tls', 'ca-cert-file', ca_cert])
- self.cli_set(path + ['tls', 'cert-file', ssl_cert])
- self.cli_set(path + ['tls', 'key-file', ssl_key])
+ self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
# check validate() - can not have auth username without a password
self.cli_set(path + ['authentication', 'username', 'vyos'])
@@ -152,9 +156,8 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['protocol', protocol])
self.cli_set(path + ['remote-host', remote_host])
self.cli_set(path + ['remote-port', remote_port])
- self.cli_set(path + ['tls', 'ca-cert-file', ca_cert])
- self.cli_set(path + ['tls', 'cert-file', ssl_cert])
- self.cli_set(path + ['tls', 'key-file', ssl_key])
+ self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
self.cli_set(path + ['vrf', vrf_name])
self.cli_set(path + ['authentication', 'username', interface+'user'])
self.cli_set(path + ['authentication', 'password', interface+'secretpw'])
@@ -176,12 +179,12 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'remote {remote_host}', config)
self.assertIn(f'persist-tun', config)
self.assertIn(f'auth {auth_hash}', config)
- self.assertIn(f'cipher aes-256-cbc', config)
+ self.assertIn(f'cipher AES-256-CBC', config)
# TLS options
- self.assertIn(f'ca {ca_cert}', config)
- self.assertIn(f'cert {ssl_cert}', config)
- self.assertIn(f'key {ssl_key}', config)
+ self.assertIn(f'ca /run/openvpn/{interface}_ca.pem', config)
+ self.assertIn(f'cert /run/openvpn/{interface}_cert.pem', config)
+ self.assertIn(f'key /run/openvpn/{interface}_cert.key', config)
self.assertTrue(process_named_running(PROCESS_NAME))
self.assertEqual(get_vrf(interface), vrf_name)
@@ -228,11 +231,11 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(path + ['remote-host'])
- # check validate() - must specify "tls dh-file" when not using EC keys
+ # check validate() - must specify "tls dh-params" when not using EC keys
# in server mode
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(path + ['tls', 'dh-file', dh_pem])
+ self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
# check validate() - must specify "server subnet" or add interface to
# bridge in server mode
@@ -251,20 +254,15 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(path + ['server', 'subnet', '100.64.0.0/10'])
- # check validate() - must specify "tls ca-cert-file"
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- self.cli_set(path + ['tls', 'ca-cert-file', ca_cert])
-
- # check validate() - must specify "tls cert-file"
+ # check validate() - must specify "tls ca-certificate"
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(path + ['tls', 'cert-file', ssl_cert])
+ self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
- # check validate() - must specify "tls key-file"
+ # check validate() - must specify "tls certificate"
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(path + ['tls', 'key-file', ssl_key])
+ self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
# check validate() - cannot specify "tls role" in client-server mode'
self.cli_set(path + ['tls', 'role', 'active'])
@@ -272,7 +270,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# check validate() - cannot specify "tls role" in client-server mode'
- self.cli_set(path + ['tls', 'auth-file', auth_key])
+ self.cli_set(path + ['tls', 'auth-key', 'ovpn_test'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
@@ -282,11 +280,11 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(path + ['protocol'])
- # check validate() - cannot specify "tls dh-file" when "tls role" is "active"
- self.cli_set(path + ['tls', 'dh-file', dh_pem])
+ # check validate() - cannot specify "tls dh-params" when "tls role" is "active"
+ self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_delete(path + ['tls', 'dh-file'])
+ self.cli_delete(path + ['tls', 'dh-params'])
# Now test the other path with tls role passive
self.cli_set(path + ['tls', 'role', 'passive'])
@@ -297,10 +295,10 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_delete(path + ['protocol'])
- # check validate() - must specify "tls dh-file" when "tls role" is "passive"
+ # check validate() - must specify "tls dh-params" when "tls role" is "passive"
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(path + ['tls', 'dh-file', dh_pem])
+ self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
self.cli_commit()
@@ -338,10 +336,9 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['server', 'client', 'client1', 'subnet', route])
self.cli_set(path + ['replace-default-route'])
- self.cli_set(path + ['tls', 'ca-cert-file', ca_cert])
- self.cli_set(path + ['tls', 'cert-file', ssl_cert])
- self.cli_set(path + ['tls', 'key-file', ssl_key])
- self.cli_set(path + ['tls', 'dh-file', dh_pem])
+ self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
self.cli_set(path + ['vrf', vrf_name])
self.cli_commit()
@@ -367,17 +364,17 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'persist-key', config)
self.assertIn(f'proto udp', config) # default protocol
self.assertIn(f'auth {auth_hash}', config)
- self.assertIn(f'cipher aes-192-cbc', config)
+ self.assertIn(f'cipher AES-192-CBC', config)
self.assertIn(f'topology subnet', config)
self.assertIn(f'lport {port}', config)
self.assertIn(f'push "redirect-gateway def1"', config)
self.assertIn(f'keepalive 5 25', config)
# TLS options
- self.assertIn(f'ca {ca_cert}', config)
- self.assertIn(f'cert {ssl_cert}', config)
- self.assertIn(f'key {ssl_key}', config)
- self.assertIn(f'dh {dh_pem}', config)
+ self.assertIn(f'ca /run/openvpn/{interface}_ca.pem', config)
+ self.assertIn(f'cert /run/openvpn/{interface}_cert.pem', config)
+ self.assertIn(f'key /run/openvpn/{interface}_cert.key', config)
+ self.assertIn(f'dh /run/openvpn/{interface}_dh.pem', config)
# IP pool configuration
netmask = IPv4Network(subnet).netmask
@@ -425,10 +422,9 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['replace-default-route'])
self.cli_set(path + ['keep-alive', 'failure-count', '10'])
self.cli_set(path + ['keep-alive', 'interval', '5'])
- self.cli_set(path + ['tls', 'ca-cert-file', ca_cert])
- self.cli_set(path + ['tls', 'cert-file', ssl_cert])
- self.cli_set(path + ['tls', 'key-file', ssl_key])
- self.cli_set(path + ['tls', 'dh-file', dh_pem])
+ self.cli_set(path + ['tls', 'ca-certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
+ self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
self.cli_set(path + ['vrf', vrf_name])
self.cli_commit()
@@ -448,17 +444,17 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'persist-key', config)
self.assertIn(f'proto udp', config) # default protocol
self.assertIn(f'auth {auth_hash}', config)
- self.assertIn(f'cipher aes-192-cbc', config)
+ self.assertIn(f'cipher AES-192-CBC', config)
self.assertIn(f'topology net30', config)
self.assertIn(f'lport {port}', config)
self.assertIn(f'push "redirect-gateway def1"', config)
self.assertIn(f'keepalive 5 50', config)
# TLS options
- self.assertIn(f'ca {ca_cert}', config)
- self.assertIn(f'cert {ssl_cert}', config)
- self.assertIn(f'key {ssl_key}', config)
- self.assertIn(f'dh {dh_pem}', config)
+ self.assertIn(f'ca /run/openvpn/{interface}_ca.pem', config)
+ self.assertIn(f'cert /run/openvpn/{interface}_cert.pem', config)
+ self.assertIn(f'key /run/openvpn/{interface}_cert.key', config)
+ self.assertIn(f'dh /run/openvpn/{interface}_dh.pem', config)
# IP pool configuration
netmask = IPv4Network(subnet).netmask
@@ -530,10 +526,10 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_delete(path + ['remote-address', '2001:db8:ffff::2'])
- # check validate() - Must specify one of "shared-secret-key-file" and "tls"
+ # check validate() - Must specify one of "shared-secret-key" and "tls"
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_set(path + ['shared-secret-key-file', s2s_key])
+ self.cli_set(path + ['shared-secret-key', 'ovpn_test'])
self.cli_commit()
@@ -565,7 +561,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['mode', 'site-to-site'])
self.cli_set(path + ['local-port', port])
self.cli_set(path + ['remote-port', port])
- self.cli_set(path + ['shared-secret-key-file', s2s_key])
+ self.cli_set(path + ['shared-secret-key', 'ovpn_test'])
self.cli_set(path + ['remote-address', remote_address])
self.cli_set(path + ['vrf', vrf_name])
@@ -589,7 +585,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'ifconfig {local_address} {local_address_subnet}', config)
self.assertIn(f'dev {interface}', config)
- self.assertIn(f'secret {s2s_key}', config)
+ self.assertIn(f'secret /run/openvpn/{interface}_shared.key', config)
self.assertIn(f'lport {port}', config)
self.assertIn(f'rport {port}', config)
@@ -609,37 +605,4 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
if __name__ == '__main__':
- # Our SSL certificates need a subject ...
- subject = '/C=DE/ST=BY/O=VyOS/localityName=Cloud/commonName=vyos/' \
- 'organizationalUnitName=VyOS/emailAddress=maintainers@vyos.io/'
-
- if not (os.path.isfile(ssl_key) and os.path.isfile(ssl_cert)):
- # Generate mandatory SSL certificate
- tmp = f'openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 '\
- f'-keyout {ssl_key} -out {ssl_cert} -subj {subject}'
- cmd(tmp)
-
- if not os.path.isfile(ca_cert):
- # Generate "CA"
- tmp = f'openssl req -new -x509 -key {ssl_key} -out {ca_cert} -subj {subject}'
- cmd(tmp)
-
- if not os.path.isfile(dh_pem):
- # Generate "DH" key
- tmp = f'openssl dhparam -out {dh_pem} 2048'
- cmd(tmp)
-
- if not os.path.isfile(s2s_key):
- # Generate site-2-site key
- tmp = f'openvpn --genkey --secret {s2s_key}'
- cmd(tmp)
-
- if not os.path.isfile(auth_key):
- # Generate TLS auth key
- tmp = f'openvpn --genkey --secret {auth_key}'
- cmd(tmp)
-
- for file in [ca_cert, ssl_cert, ssl_key, dh_pem, s2s_key, auth_key]:
- cmd(f'sudo chown openvpn:openvpn {file}')
-
unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 0256ad62a..74e29ed82 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -17,6 +17,7 @@
import os
import re
+from cryptography.hazmat.primitives.asymmetric import ec
from glob import glob
from sys import exit
from ipaddress import IPv4Address
@@ -31,8 +32,14 @@ from vyos.config import Config
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bridge_delete
-from vyos.configverify import verify_diffie_hellman_length
from vyos.ifconfig import VTunIf
+from vyos.pki import load_dh_parameters
+from vyos.pki import load_private_key
+from vyos.pki import wrap_certificate
+from vyos.pki import wrap_crl
+from vyos.pki import wrap_dh_parameters
+from vyos.pki import wrap_openvpn_key
+from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.template import is_ipv4
from vyos.template import is_ipv6
@@ -40,6 +47,7 @@ from vyos.util import call
from vyos.util import chown
from vyos.util import chmod_600
from vyos.util import dict_search
+from vyos.util import dict_search_args
from vyos.validate import is_addr_assigned
from vyos import ConfigError
@@ -49,23 +57,9 @@ airbag.enable()
user = 'openvpn'
group = 'openvpn'
+cfg_dir = '/run/openvpn'
cfg_file = '/run/openvpn/{ifname}.conf'
-def checkCertHeader(header, filename):
- """
- Verify if filename contains specified header.
- Returns True if match is found, False if no match or file is not found
- """
- if not os.path.isfile(filename):
- return False
-
- with open(filename, 'r') as f:
- for line in f:
- if re.match(header, line):
- return True
-
- return False
-
def get_config(config=None):
"""
Retrive CLI config as dictionary. Dictionary can never be empty, as at least the
@@ -76,14 +70,105 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'openvpn']
+
+ tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
openvpn = get_interface_dict(conf, base)
+ if 'deleted' not in openvpn:
+ openvpn['pki'] = tmp_pki
+
openvpn['auth_user_pass_file'] = '/run/openvpn/{ifname}.pw'.format(**openvpn)
openvpn['daemon_user'] = user
openvpn['daemon_group'] = group
return openvpn
+def is_ec_private_key(pki, cert_name):
+ if not pki or 'certificate' not in pki:
+ return False
+ if cert_name not in pki['certificate']:
+ return False
+
+ pki_cert = pki['certificate'][cert_name]
+ if 'private' not in pki_cert or 'key' not in pki_cert['private']:
+ return False
+
+ key = load_private_key(pki_cert['private']['key'])
+ return isinstance(key, ec.EllipticCurvePrivateKey)
+
+def verify_pki(openvpn):
+ pki = openvpn['pki']
+ interface = openvpn['ifname']
+ mode = openvpn['mode']
+ shared_secret_key = dict_search_args(openvpn, 'shared_secret_key')
+ tls = dict_search_args(openvpn, 'tls')
+
+ if not bool(shared_secret_key) ^ bool(tls): # xor check if only one is set
+ raise ConfigError('Must specify only one of "shared-secret-key" and "tls"')
+
+ if mode in ['server', 'client'] and not tls:
+ raise ConfigError('Must specify "tls" for server and client modes')
+
+ if not pki:
+ raise ConfigError('PKI is not configured')
+
+ if shared_secret_key:
+ if not dict_search_args(pki, 'openvpn', 'shared_secret'):
+ raise ConfigError('There are no openvpn shared-secrets in PKI configuration')
+
+ if shared_secret_key not in pki['openvpn']['shared_secret']:
+ raise ConfigError(f'Invalid shared-secret on openvpn interface {interface}')
+
+ if tls:
+ 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}')
+
+ if not (mode == 'client' and 'auth_key' in tls):
+ if 'certificate' not in tls:
+ raise ConfigError(f'Missing "tls certificate" on openvpn interface {interface}')
+
+ if 'certificate' in tls:
+ if tls['certificate'] not in pki['certificate']:
+ raise ConfigError(f'Invalid certificate on openvpn interface {interface}')
+
+ if dict_search_args(pki, 'certificate', tls['certificate'], 'private', 'password_protected'):
+ raise ConfigError(f'Cannot use encrypted private key on openvpn interface {interface}')
+
+ if mode == 'server' and 'dh_params' not in tls and not is_ec_private_key(pki, tls['certificate']):
+ raise ConfigError('Must specify "tls dh-params" when not using EC keys in server mode')
+
+ if 'dh_params' in tls:
+ if 'dh' not in pki:
+ raise ConfigError('There are no DH parameters in PKI configuration')
+
+ if tls['dh_params'] not in pki['dh']:
+ raise ConfigError(f'Invalid dh-params on openvpn interface {interface}')
+
+ pki_dh = pki['dh'][tls['dh_params']]
+ dh_params = load_dh_parameters(pki_dh['parameters'])
+ dh_numbers = dh_params.parameter_numbers()
+ dh_bits = dh_numbers.p.bit_length()
+
+ if dh_bits < 2048:
+ raise ConfigError(f'Minimum DH key-size is 2048 bits')
+
+ if 'auth_key' in tls or 'crypt_key' in tls:
+ if not dict_search_args(pki, 'openvpn', 'shared_secret'):
+ raise ConfigError('There are no openvpn shared-secrets in PKI configuration')
+
+ if 'auth_key' in tls:
+ if tls['auth_key'] not in pki['openvpn']['shared_secret']:
+ raise ConfigError(f'Invalid auth-key on openvpn interface {interface}')
+
+ if 'crypt_key' in tls:
+ if tls['crypt_key'] not in pki['openvpn']['shared_secret']:
+ raise ConfigError(f'Invalid crypt-key on openvpn interface {interface}')
+
def verify(openvpn):
if 'deleted' in openvpn:
verify_bridge_delete(openvpn)
@@ -108,8 +193,8 @@ def verify(openvpn):
if openvpn['protocol'] == 'tcp-passive':
raise ConfigError('Protocol "tcp-passive" is not valid in client mode')
- if dict_search('tls.dh_file', openvpn):
- raise ConfigError('Cannot specify "tls dh-file" in client mode')
+ if dict_search('tls.dh_params', openvpn):
+ raise ConfigError('Cannot specify "tls dh-params" in client mode')
#
# OpenVPN site-to-site - VERIFY
@@ -194,11 +279,6 @@ def verify(openvpn):
if 'remote_host' in openvpn:
raise ConfigError('Cannot specify "remote-host" in server mode')
- if 'tls' in openvpn:
- if 'dh_file' not in openvpn['tls']:
- if 'key_file' in openvpn['tls'] and not checkCertHeader('-----BEGIN EC PRIVATE KEY-----', openvpn['tls']['key_file']):
- raise ConfigError('Must specify "tls dh-file" when not using EC keys in server mode')
-
tmp = dict_search('server.subnet', openvpn)
if tmp:
v4_subnets = len([subnet for subnet in tmp if is_ipv4(subnet)])
@@ -306,97 +386,40 @@ def verify(openvpn):
if 'remote_host' not in openvpn:
raise ConfigError('Must specify "remote-host" with "tcp-active"')
- # shared secret and TLS
- if not ('shared_secret_key_file' in openvpn or 'tls' in openvpn):
- raise ConfigError('Must specify one of "shared-secret-key-file" and "tls"')
-
- if {'shared_secret_key_file', 'tls'} <= set(openvpn):
- raise ConfigError('Can only specify one of "shared-secret-key-file" and "tls"')
-
- if openvpn['mode'] in ['client', 'server']:
- if 'tls' not in openvpn:
- raise ConfigError('Must specify "tls" for server and client mode')
-
#
# TLS/encryption
#
- if 'shared_secret_key_file' in openvpn:
+ if 'shared_secret_key' in openvpn:
if dict_search('encryption.cipher', openvpn) in ['aes128gcm', 'aes192gcm', 'aes256gcm']:
- raise ConfigError('GCM encryption with shared-secret-key-file not supported')
-
- file = dict_search('shared_secret_key_file', openvpn)
- if file and not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', file):
- raise ConfigError(f'Specified shared-secret-key-file "{file}" is not valid')
+ raise ConfigError('GCM encryption with shared-secret-key not supported')
if 'tls' in openvpn:
- if 'ca_cert_file' not in openvpn['tls']:
- raise ConfigError('Must specify "tls ca-cert-file"')
-
- if not (openvpn['mode'] == 'client' and 'auth_file' in openvpn['tls']):
- if 'cert_file' not in openvpn['tls']:
- raise ConfigError('Missing "tls cert-file"')
-
- if 'key_file' not in openvpn['tls']:
- raise ConfigError('Missing "tls key-file"')
-
- if {'auth_file', 'crypt_file'} <= set(openvpn['tls']):
- raise ConfigError('TLS auth and crypt are mutually exclusive')
-
- file = dict_search('tls.ca_cert_file', openvpn)
- if file and not checkCertHeader('-----BEGIN CERTIFICATE-----', file):
- raise ConfigError(f'Specified ca-cert-file "{file}" is invalid')
-
- file = dict_search('tls.auth_file', openvpn)
- if file and not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', file):
- raise ConfigError(f'Specified auth-file "{file}" is invalid')
-
- file = dict_search('tls.cert_file', openvpn)
- if file and not checkCertHeader('-----BEGIN CERTIFICATE-----', file):
- raise ConfigError(f'Specified cert-file "{file}" is invalid')
-
- file = dict_search('tls.key_file', openvpn)
- if file and not checkCertHeader('-----BEGIN (?:RSA |EC )?PRIVATE KEY-----', file):
- raise ConfigError(f'Specified key-file "{file}" is not valid')
-
- file = dict_search('tls.crypt_file', openvpn)
- if file and not checkCertHeader('-----BEGIN OpenVPN Static key V1-----', file):
- raise ConfigError(f'Specified TLS crypt-file "{file}" is invalid')
-
- file = dict_search('tls.crl_file', openvpn)
- if file and not checkCertHeader('-----BEGIN X509 CRL-----', file):
- raise ConfigError(f'Specified crl-file "{file} not valid')
-
- file = dict_search('tls.dh_file', openvpn)
- if file and not checkCertHeader('-----BEGIN DH PARAMETERS-----', file):
- raise ConfigError(f'Specified dh-file "{file}" is not valid')
-
- if file and not verify_diffie_hellman_length(file, 2048):
- raise ConfigError(f'Minimum DH key-size is 2048 bits')
+ if {'auth_key', 'crypt_key'} <= set(openvpn['tls']):
+ raise ConfigError('TLS auth and crypt keys are mutually exclusive')
tmp = dict_search('tls.role', openvpn)
if tmp:
if openvpn['mode'] in ['client', 'server']:
- if not dict_search('tls.auth_file', openvpn):
+ if not dict_search('tls.auth_key', openvpn):
raise ConfigError('Cannot specify "tls role" in client-server mode')
if tmp == 'active':
if openvpn['protocol'] == 'tcp-passive':
raise ConfigError('Cannot specify "tcp-passive" when "tls role" is "active"')
- if dict_search('tls.dh_file', openvpn):
- raise ConfigError('Cannot specify "tls dh-file" when "tls role" is "active"')
+ if dict_search('tls.dh_params', openvpn):
+ raise ConfigError('Cannot specify "tls dh-params" when "tls role" is "active"')
elif tmp == 'passive':
if openvpn['protocol'] == 'tcp-active':
raise ConfigError('Cannot specify "tcp-active" when "tls role" is "passive"')
- if not dict_search('tls.dh_file', openvpn):
- raise ConfigError('Must specify "tls dh-file" when "tls role" is "passive"')
+ if not dict_search('tls.dh_params', openvpn):
+ raise ConfigError('Must specify "tls dh-params" when "tls role" is "passive"')
- file = dict_search('tls.key_file', openvpn)
- if file and checkCertHeader('-----BEGIN EC PRIVATE KEY-----', file):
- if dict_search('tls.dh_file', openvpn):
- print('Warning: using dh-file and EC keys simultaneously will ' \
+ if 'certificate' in openvpn['tls'] and is_ec_private_key(openvpn['pki'], openvpn['tls']['certificate']):
+ if 'dh_params' in openvpn['tls']:
+ print('Warning: using dh-params and EC keys simultaneously will ' \
'lead to DH ciphers being used instead of ECDH')
if dict_search('encryption.cipher', openvpn) == 'none':
@@ -404,6 +427,8 @@ def verify(openvpn):
print('No encryption will be performed and data is transmitted in ' \
'plain text over the network!')
+ verify_pki(openvpn)
+
#
# Auth user/pass
#
@@ -419,6 +444,110 @@ def verify(openvpn):
return None
+def generate_pki_files(openvpn):
+ pki = openvpn['pki']
+
+ if not pki:
+ return None
+
+ interface = openvpn['ifname']
+ shared_secret_key = dict_search_args(openvpn, 'shared_secret_key')
+ tls = dict_search_args(openvpn, 'tls')
+
+ files = []
+
+ if shared_secret_key:
+ pki_key = pki['openvpn']['shared_secret'][shared_secret_key]
+ key_path = os.path.join(cfg_dir, f'{interface}_shared.key')
+
+ with open(key_path, 'w') as f:
+ f.write(wrap_openvpn_key(pki_key['key']))
+
+ files.append(key_path)
+
+ if tls:
+ if 'ca_certificate' in tls:
+ cert_name = tls['ca_certificate']
+ pki_ca = pki['ca'][cert_name]
+
+ if 'certificate' in pki_ca:
+ cert_path = os.path.join(cfg_dir, f'{interface}_ca.pem')
+
+ with open(cert_path, 'w') as f:
+ f.write(wrap_certificate(pki_ca['certificate']))
+
+ files.append(cert_path)
+
+ if 'crl' in pki_ca:
+ for crl in pki_ca['crl']:
+ crl_path = os.path.join(cfg_dir, f'{interface}_crl.pem')
+
+ with open(crl_path, 'w') as f:
+ f.write(wrap_crl(crl))
+
+ files.append(crl_path)
+ openvpn['tls']['crl'] = True
+
+ if 'certificate' in tls:
+ cert_name = tls['certificate']
+ pki_cert = pki['certificate'][cert_name]
+
+ if 'certificate' in pki_cert:
+ cert_path = os.path.join(cfg_dir, f'{interface}_cert.pem')
+
+ with open(cert_path, 'w') as f:
+ f.write(wrap_certificate(pki_cert['certificate']))
+
+ files.append(cert_path)
+
+ if 'private' in pki_cert and 'key' in pki_cert['private']:
+ key_path = os.path.join(cfg_dir, f'{interface}_cert.key')
+
+ with open(key_path, 'w') as f:
+ f.write(wrap_private_key(pki_cert['private']['key']))
+
+ files.append(key_path)
+ openvpn['tls']['private_key'] = True
+
+ if 'dh_params' in tls:
+ dh_name = tls['dh_params']
+ pki_dh = pki['dh'][dh_name]
+
+ if 'parameters' in pki_dh:
+ dh_path = os.path.join(cfg_dir, f'{interface}_dh.pem')
+
+ with open(dh_path, 'w') as f:
+ f.write(wrap_dh_parameters(pki_dh['parameters']))
+
+ files.append(dh_path)
+
+ if 'auth_key' in tls:
+ key_name = tls['auth_key']
+ pki_key = pki['openvpn']['shared_secret'][key_name]
+
+ if 'key' in pki_key:
+ key_path = os.path.join(cfg_dir, f'{interface}_auth.key')
+
+ with open(key_path, 'w') as f:
+ f.write(wrap_openvpn_key(pki_key['key']))
+
+ files.append(key_path)
+
+ if 'crypt_key' in tls:
+ key_name = tls['crypt_key']
+ pki_key = pki['openvpn']['shared_secret'][key_name]
+
+ if 'key' in pki_key:
+ key_path = os.path.join(cfg_dir, f'{interface}_crypt.key')
+
+ with open(key_path, 'w') as f:
+ f.write(wrap_openvpn_key(pki_key['key']))
+
+ files.append(key_path)
+
+ return files
+
+
def generate(openvpn):
interface = openvpn['ifname']
directory = os.path.dirname(cfg_file.format(**openvpn))
@@ -438,13 +567,7 @@ def generate(openvpn):
chown(ccd_dir, user, group)
# Fix file permissons for keys
- fix_permissions = []
-
- tmp = dict_search('shared_secret_key_file', openvpn)
- if tmp: fix_permissions.append(openvpn['shared_secret_key_file'])
-
- tmp = dict_search('tls.key_file', openvpn)
- if tmp: fix_permissions.append(tmp)
+ fix_permissions = generate_pki_files(openvpn)
# Generate User/Password authentication file
if 'authentication' in openvpn:
@@ -456,8 +579,9 @@ def generate(openvpn):
os.remove(openvpn['auth_user_pass_file'])
# Generate client specific configuration
- if dict_search('server.client', openvpn):
- for client, client_config in dict_search('server.client', openvpn).items():
+ server_client = dict_search_args(openvpn, 'server', 'client')
+ if server_client:
+ for client, client_config in server_client.items():
client_file = os.path.join(ccd_dir, client)
# Our client need's to know its subnet mask ...
diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23
index 3fd5998a0..93ce9215f 100755
--- a/src/migration-scripts/interfaces/22-to-23
+++ b/src/migration-scripts/interfaces/22-to-23
@@ -21,12 +21,34 @@ import os
import sys
from vyos.configtree import ConfigTree
from vyos.pki import load_certificate
+from vyos.pki import load_crl
+from vyos.pki import load_dh_parameters
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.util import run
def wrapped_pem_to_config_value(pem):
- return "".join(pem.strip().split("\n")[1:-1])
+ out = []
+ for line in pem.strip().split("\n"):
+ if not line or line.startswith("-----") or line[0] == '#':
+ continue
+ out.append(line)
+ return "".join(out)
+
+def read_file_for_pki(config_auth_path):
+ full_path = os.path.join(AUTH_DIR, config_auth_path)
+ output = None
+
+ if os.path.isfile(full_path):
+ if not os.access(full_path, os.R_OK):
+ run(f'sudo chmod 644 {full_path}')
+
+ with open(full_path, 'r') as f:
+ output = f.read()
+
+ return output
if (len(sys.argv) < 1):
print("Must specify file name!")
@@ -39,6 +61,198 @@ with open(file_name, 'r') as f:
config = ConfigTree(config_file)
+AUTH_DIR = '/config/auth'
+pki_base = ['pki']
+
+# OpenVPN
+base = ['interfaces', 'openvpn']
+
+if config.exists(base):
+ for interface in config.list_nodes(base):
+ x509_base = base + [interface, 'tls']
+ pki_name = f'openvpn_{interface}'
+
+ if config.exists(base + [interface, 'shared-secret-key-file']):
+ if not config.exists(pki_base + ['openvpn', 'shared-secret']):
+ config.set(pki_base + ['openvpn', 'shared-secret'])
+ config.set_tag(pki_base + ['openvpn', 'shared-secret'])
+
+ key_file = config.return_value(base + [interface, 'shared-secret-key-file'])
+ key = read_file_for_pki(key_file)
+ key_pki_name = f'{pki_name}_shared'
+
+ if key:
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
+ config.set(base + [interface, 'shared-secret-key'], value=key_pki_name)
+ else:
+ print(f'Failed to migrate shared-secret-key on openvpn interface {interface}')
+
+ config.delete(base + [interface, 'shared-secret-key-file'])
+
+ if not config.exists(base + [interface, 'tls']):
+ continue
+
+ if config.exists(base + [interface, 'tls', 'auth-file']):
+ if not config.exists(pki_base + ['openvpn', 'shared-secret']):
+ config.set(pki_base + ['openvpn', 'shared-secret'])
+ config.set_tag(pki_base + ['openvpn', 'shared-secret'])
+
+ key_file = config.return_value(base + [interface, 'tls', 'auth-file'])
+ key = read_file_for_pki(key_file)
+ key_pki_name = f'{pki_name}_auth'
+
+ if key:
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
+ config.set(base + [interface, 'tls', 'auth-key'], value=key_pki_name)
+ else:
+ print(f'Failed to migrate auth-key on openvpn interface {interface}')
+
+ config.delete(base + [interface, 'tls', 'auth-file'])
+
+ if config.exists(base + [interface, 'tls', 'crypt-file']):
+ if not config.exists(pki_base + ['openvpn', 'shared-secret']):
+ config.set(pki_base + ['openvpn', 'shared-secret'])
+ config.set_tag(pki_base + ['openvpn', 'shared-secret'])
+
+ key_file = config.return_value(base + [interface, 'tls', 'crypt-file'])
+ key = read_file_for_pki(key_file)
+ key_pki_name = f'{pki_name}_crypt'
+
+ if key:
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'key'], value=wrapped_pem_to_config_value(key))
+ config.set(pki_base + ['openvpn', 'shared-secret', key_pki_name, 'version'], value='1')
+ config.set(base + [interface, 'tls', 'crypt-key'], value=key_pki_name)
+ else:
+ print(f'Failed to migrate crypt-key on openvpn interface {interface}')
+
+ config.delete(base + [interface, 'tls', 'crypt-file'])
+
+ if config.exists(x509_base + ['ca-cert-file']):
+ if not config.exists(pki_base + ['ca']):
+ config.set(pki_base + ['ca'])
+ config.set_tag(pki_base + ['ca'])
+
+ 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)
+ else:
+ print(f'Failed to migrate CA certificate on openvpn interface {interface}')
+
+ config.delete(x509_base + ['ca-cert-file'])
+
+ if config.exists(x509_base + ['crl-file']):
+ if not config.exists(pki_base + ['ca']):
+ config.set(pki_base + ['ca'])
+ config.set_tag(pki_base + ['ca'])
+
+ crl_file = config.return_value(x509_base + ['crl-file'])
+ crl_path = os.path.join(AUTH_DIR, crl_file)
+ crl = None
+
+ if os.path.isfile(crl_path):
+ if not os.access(crl_path, os.R_OK):
+ run(f'sudo chmod 644 {crl_path}')
+
+ with open(crl_path, 'r') as f:
+ crl_data = f.read()
+ crl = load_crl(crl_data, wrap_tags=False)
+
+ if crl:
+ crl_pem = encode_certificate(crl)
+ config.set(pki_base + ['ca', pki_name, 'crl'], value=wrapped_pem_to_config_value(crl_pem))
+ else:
+ print(f'Failed to migrate CRL on openvpn interface {interface}')
+
+ config.delete(x509_base + ['crl-file'])
+
+ if config.exists(x509_base + ['cert-file']):
+ if not config.exists(pki_base + ['certificate']):
+ config.set(pki_base + ['certificate'])
+ config.set_tag(pki_base + ['certificate'])
+
+ cert_file = config.return_value(x509_base + ['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 + ['certificate', pki_name, 'certificate'], value=wrapped_pem_to_config_value(cert_pem))
+ config.set(x509_base + ['certificate'], value=pki_name)
+ else:
+ print(f'Failed to migrate certificate on openvpn interface {interface}')
+
+ config.delete(x509_base + ['cert-file'])
+
+ if config.exists(x509_base + ['key-file']):
+ key_file = config.return_value(x509_base + ['key-file'])
+ key_path = os.path.join(AUTH_DIR, key_file)
+ key = None
+
+ if os.path.isfile(key_path):
+ if not os.access(key_path, os.R_OK):
+ run(f'sudo chmod 644 {key_path}')
+
+ with open(key_path, 'r') as f:
+ key_data = f.read()
+ key = load_private_key(key_data, passphrase=None, wrap_tags=False)
+
+ if key:
+ key_pem = encode_private_key(key, passphrase=None)
+ config.set(pki_base + ['certificate', pki_name, 'private', 'key'], value=wrapped_pem_to_config_value(key_pem))
+ else:
+ print(f'Failed to migrate private key on openvpn interface {interface}')
+
+ config.delete(x509_base + ['key-file'])
+
+ if config.exists(x509_base + ['dh-file']):
+ if not config.exists(pki_base + ['dh']):
+ config.set(pki_base + ['dh'])
+ config.set_tag(pki_base + ['dh'])
+
+ dh_file = config.return_value(x509_base + ['dh-file'])
+ dh_path = os.path.join(AUTH_DIR, dh_file)
+ dh = None
+
+ if os.path.isfile(dh_path):
+ if not os.access(dh_path, os.R_OK):
+ run(f'sudo chmod 644 {dh_path}')
+
+ with open(dh_path, 'r') as f:
+ dh_data = f.read()
+ dh = load_dh_parameters(dh_data, wrap_tags=False)
+
+ if dh:
+ dh_pem = encode_dh_parameters(dh)
+ config.set(pki_base + ['dh', pki_name, 'parameters'], value=wrapped_pem_to_config_value(dh_pem))
+ config.set(x509_base + ['dh-params'], value=pki_name)
+ else:
+ print(f'Failed to migrate DH parameters on openvpn interface {interface}')
+
+ config.delete(x509_base + ['dh-file'])
+
# Wireguard
base = ['interfaces', 'wireguard']
@@ -67,9 +281,6 @@ if config.exists(base):
base = ['interfaces', 'ethernet']
if config.exists(base):
- AUTH_DIR = '/config/auth'
- pki_base = ['pki']
-
for interface in config.list_nodes(base):
if not config.exists(base + [interface, 'eapol']):
continue