summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsarthurdev <965089+sarthurdev@users.noreply.github.com>2021-07-20 17:33:00 +0200
committersarthurdev <965089+sarthurdev@users.noreply.github.com>2021-07-20 20:14:12 +0200
commitbfadd6dfb5969f231097353a76ada3b839964a19 (patch)
tree5c9cae9c04121dd082c0a7a3e6d262df27c86489
parent1554d3316eb74971d2ac7e3608173f6f113684e0 (diff)
downloadvyos-1x-bfadd6dfb5969f231097353a76ada3b839964a19.tar.gz
vyos-1x-bfadd6dfb5969f231097353a76ada3b839964a19.zip
pki: eapol: T3642: Migrate EAPoL to use PKI configuration
-rw-r--r--data/templates/ethernet/wpa_supplicant.conf.tmpl8
-rw-r--r--interface-definitions/include/interface/interface-eapol.xml.i5
-rw-r--r--python/vyos/configverify.py35
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py41
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py31
-rwxr-xr-xsrc/migration-scripts/interfaces/22-to-23130
6 files changed, 195 insertions, 55 deletions
diff --git a/data/templates/ethernet/wpa_supplicant.conf.tmpl b/data/templates/ethernet/wpa_supplicant.conf.tmpl
index fe518ad45..308d777f1 100644
--- a/data/templates/ethernet/wpa_supplicant.conf.tmpl
+++ b/data/templates/ethernet/wpa_supplicant.conf.tmpl
@@ -32,11 +32,11 @@ fast_reauth=1
network={
{% if eapol is defined and eapol is not none %}
-{% if eapol.ca_cert_file is defined and eapol.ca_cert_file is not none %}
- ca_cert="{{ eapol.ca_cert_file }}"
+{% if eapol.ca_certificate is defined and eapol.ca_certificate is not none %}
+ ca_cert="/run/wpa_supplicant/{{ ifname }}_ca.pem"
{% endif %}
- client_cert="{{ eapol.cert_file }}"
- private_key="{{ eapol.key_file }}"
+ client_cert="/run/wpa_supplicant/{{ ifname }}_cert.pem"
+ private_key="/run/wpa_supplicant/{{ ifname }}_cert.key"
{% endif %}
# list of accepted authenticated key management protocols
diff --git a/interface-definitions/include/interface/interface-eapol.xml.i b/interface-definitions/include/interface/interface-eapol.xml.i
index 92b7a3f35..270ec5b13 100644
--- a/interface-definitions/include/interface/interface-eapol.xml.i
+++ b/interface-definitions/include/interface/interface-eapol.xml.i
@@ -4,9 +4,8 @@
<help>Extensible Authentication Protocol over Local Area Network</help>
</properties>
<children>
- #include <include/certificate.xml.i>
- #include <include/certificate-ca.xml.i>
- #include <include/certificate-key.xml.i>
+ #include <include/pki/ca-certificate.xml.i>
+ #include <include/pki/certificate-key.xml.i>
</children>
</node>
<!-- include end -->
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 979e28b11..58028b604 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -149,9 +149,38 @@ def verify_eapol(config):
recurring validation of EAPoL configuration.
"""
if 'eapol' in config:
- if not {'cert_file', 'key_file'} <= set(config['eapol']):
- raise ConfigError('Both cert and key-file must be specified '\
- 'when using EAPoL!')
+ if 'certificate' not in config['eapol']:
+ raise ConfigError('Certificate must be specified when using EAPoL!')
+
+ if 'certificate' not in config['pki']:
+ raise ConfigError('Invalid certificate specified for EAPoL')
+
+ cert_name = config['eapol']['certificate']
+
+ if cert_name not in config['pki']['certificate']:
+ raise ConfigError('Invalid certificate specified for EAPoL')
+
+ cert = config['pki']['certificate'][cert_name]
+
+ if 'certificate' not in cert or 'private' not in cert or 'key' not in cert['private']:
+ raise ConfigError('Invalid certificate/private key specified for EAPoL')
+
+ if 'password_protected' in cert['private']:
+ raise ConfigError('Encrypted private key cannot be used for EAPoL')
+
+ if 'ca_certificate' in config['eapol']:
+ if 'ca' not in config['pki']:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
+
+ ca_cert_name = config['eapol']['ca_certificate']
+
+ if ca_cert_name not in config['pki']['ca']:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
+
+ ca_cert = config['pki']['ca'][cert_name]
+
+ if 'certificate' not in ca_cert:
+ raise ConfigError('Invalid CA certificate specified for EAPoL')
def verify_mirror(config):
"""
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index a31d75423..a9cdab16a 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -25,9 +25,9 @@ from vyos.util import cmd
from vyos.util import process_named_running
from vyos.util import read_file
-ca_cert = '/config/auth/eapol_test_ca.pem'
-ssl_cert = '/config/auth/eapol_test_server.pem'
-ssl_key = '/config/auth/eapol_test_server.key'
+pki_path = ['pki']
+cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0='
+key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww'
def get_wpa_supplicant_value(interface, key):
tmp = read_file(f'/run/wpa_supplicant/{interface}.conf')
@@ -66,6 +66,8 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
def tearDown(self):
+ self.cli_delete(pki_path)
+
for interface in self._interfaces:
# when using a dedicated interface to test via TEST_ETH environment
# variable only this one will be cleared in the end - usable to test
@@ -149,11 +151,14 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_commit()
def test_eapol_support(self):
+ self.cli_set(pki_path + ['ca', 'eapol', 'certificate', cert_data])
+ self.cli_set(pki_path + ['certificate', 'eapol', 'certificate', cert_data])
+ self.cli_set(pki_path + ['certificate', 'eapol', 'private', 'key', key_data])
+
for interface in self._interfaces:
# Enable EAPoL
- self.cli_set(self._base_path + [interface, 'eapol', 'ca-cert-file', ca_cert])
- self.cli_set(self._base_path + [interface, 'eapol', 'cert-file', ssl_cert])
- self.cli_set(self._base_path + [interface, 'eapol', 'key-file', ssl_key])
+ self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol'])
+ self.cli_set(self._base_path + [interface, 'eapol', 'certificate', 'eapol'])
self.cli_commit()
@@ -172,35 +177,17 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.assertEqual('0', tmp)
tmp = get_wpa_supplicant_value(interface, 'ca_cert')
- self.assertEqual(f'"{ca_cert}"', tmp)
+ self.assertEqual(f'"/run/wpa_supplicant/{interface}_ca.pem"', tmp)
tmp = get_wpa_supplicant_value(interface, 'client_cert')
- self.assertEqual(f'"{ssl_cert}"', tmp)
+ self.assertEqual(f'"/run/wpa_supplicant/{interface}_cert.pem"', tmp)
tmp = get_wpa_supplicant_value(interface, 'private_key')
- self.assertEqual(f'"{ssl_key}"', tmp)
+ self.assertEqual(f'"/run/wpa_supplicant/{interface}_cert.key"', tmp)
mac = read_file(f'/sys/class/net/{interface}/address')
tmp = get_wpa_supplicant_value(interface, 'identity')
self.assertEqual(f'"{mac}"', tmp)
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)
-
- for file in [ca_cert, ssl_cert, ssl_key]:
- cmd(f'sudo chown radius_priv_user:vyattacfg {file}')
-
unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index 378f400b8..78c24952b 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -32,6 +32,8 @@ from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
from vyos.ethtool import Ethtool
from vyos.ifconfig import EthernetIf
+from vyos.pki import wrap_certificate
+from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.util import call
from vyos.util import dict_search
@@ -40,6 +42,7 @@ from vyos import airbag
airbag.enable()
# XXX: wpa_supplicant works on the source interface
+cfg_dir = '/run/wpa_supplicant'
wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf'
def get_config(config=None):
@@ -52,8 +55,15 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'ethernet']
+
+ tmp_pki = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
ethernet = get_interface_dict(conf, base)
+ if 'deleted' not in ethernet:
+ ethernet['pki'] = tmp_pki
+
return ethernet
def verify(ethernet):
@@ -126,6 +136,27 @@ def generate(ethernet):
if 'eapol' in ethernet:
render(wpa_suppl_conf.format(**ethernet),
'ethernet/wpa_supplicant.conf.tmpl', ethernet)
+
+ ifname = ethernet['ifname']
+ cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem')
+ cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key')
+
+ cert_name = ethernet['eapol']['certificate']
+ pki_cert = ethernet['pki']['certificate'][cert_name]
+
+ with open(cert_file_path, 'w') as f:
+ f.write(wrap_certificate(pki_cert['certificate']))
+
+ with open(cert_key_path, 'w') as f:
+ f.write(wrap_private_key(pki_cert['private']['key']))
+
+ if 'ca_certificate' in ethernet['eapol']:
+ ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem')
+ ca_cert_name = ethernet['eapol']['ca_certificate']
+ pki_ca_cert = ethernet['pki']['ca'][cert_name]
+
+ with open(ca_cert_file_path, 'w') as f:
+ f.write(wrap_certificate(pki_ca_cert['certificate']))
else:
# delete configuration on interface removal
if os.path.isfile(wpa_suppl_conf.format(**ethernet)):
diff --git a/src/migration-scripts/interfaces/22-to-23 b/src/migration-scripts/interfaces/22-to-23
index c6dc32baf..3fd5998a0 100755
--- a/src/migration-scripts/interfaces/22-to-23
+++ b/src/migration-scripts/interfaces/22-to-23
@@ -15,27 +15,34 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Migrate Wireguard to store keys in CLI
+# Migrate EAPoL to PKI configuration
import os
import sys
from vyos.configtree import ConfigTree
+from vyos.pki import load_certificate
+from vyos.pki import load_private_key
+from vyos.pki import encode_certificate
+from vyos.pki import encode_private_key
-if __name__ == '__main__':
- if (len(sys.argv) < 1):
- print("Must specify file name!")
- sys.exit(1)
+def wrapped_pem_to_config_value(pem):
+ return "".join(pem.strip().split("\n")[1:-1])
- file_name = sys.argv[1]
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
- with open(file_name, 'r') as f:
- config_file = f.read()
+file_name = sys.argv[1]
- config = ConfigTree(config_file)
- base = ['interfaces', 'wireguard']
- if not config.exists(base):
- # Nothing to do
- sys.exit(0)
+with open(file_name, 'r') as f:
+ config_file = f.read()
+config = ConfigTree(config_file)
+
+# Wireguard
+base = ['interfaces', 'wireguard']
+
+if config.exists(base):
for interface in config.list_nodes(base):
private_key_path = base + [interface, 'private-key']
@@ -56,9 +63,96 @@ if __name__ == '__main__':
for peer in config.list_nodes(base + [interface, 'peer']):
config.rename(base + [interface, 'peer', peer, 'pubkey'], 'public-key')
- try:
- with open(file_name, 'w') as f:
- f.write(config.to_string())
- except OSError as e:
- print("Failed to save the modified config: {}".format(e))
- sys.exit(1)
+# Ethernet EAPoL
+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
+
+ x509_base = base + [interface, 'eapol']
+ pki_name = f'eapol_{interface}'
+
+ 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 eapol config for interface {interface}')
+
+ config.delete(x509_base + ['ca-cert-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 eapol config for 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 eapol config for interface {interface}')
+
+ config.delete(x509_base + ['key-file'])
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)