diff options
-rw-r--r-- | interface-definitions/include/pki/ca-certificate.xml.i | 8 | ||||
-rw-r--r-- | interface-definitions/include/pki/certificate.xml.i | 8 | ||||
-rw-r--r-- | interface-definitions/include/pki/private-key.xml.i | 8 | ||||
-rw-r--r-- | interface-definitions/include/pki/public-key.xml.i | 8 | ||||
-rw-r--r-- | python/vyos/util.py | 31 | ||||
-rwxr-xr-x | src/conf_mode/pki.py | 46 | ||||
-rw-r--r-- | src/tests/test_dict_search.py | 21 |
7 files changed, 101 insertions, 29 deletions
diff --git a/interface-definitions/include/pki/ca-certificate.xml.i b/interface-definitions/include/pki/ca-certificate.xml.i index 14295a281..b32bb676a 100644 --- a/interface-definitions/include/pki/ca-certificate.xml.i +++ b/interface-definitions/include/pki/ca-certificate.xml.i @@ -2,13 +2,13 @@ <leafNode name="ca-certificate"> <properties> <help>Certificate Authority in PKI configuration</help> - <valueHelp> - <format>CA name</format> - <description>Name of CA in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki ca</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of CA in PKI configuration</description> + </valueHelp> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/pki/certificate.xml.i b/interface-definitions/include/pki/certificate.xml.i index 436aa90ba..1ba70e058 100644 --- a/interface-definitions/include/pki/certificate.xml.i +++ b/interface-definitions/include/pki/certificate.xml.i @@ -2,13 +2,13 @@ <leafNode name="certificate"> <properties> <help>Certificate in PKI configuration</help> - <valueHelp> - <format>cert name</format> - <description>Name of certificate in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki certificate</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of certificate in PKI configuration</description> + </valueHelp> </properties> </leafNode> <!-- include end --> diff --git a/interface-definitions/include/pki/private-key.xml.i b/interface-definitions/include/pki/private-key.xml.i index 6099daa89..ae4e9103e 100644 --- a/interface-definitions/include/pki/private-key.xml.i +++ b/interface-definitions/include/pki/private-key.xml.i @@ -7,13 +7,13 @@ <leafNode name="key"> <properties> <help>Private key in PKI configuration</help> - <valueHelp> - <format>key name</format> - <description>Name of private key in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki key-pair</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of private key in PKI configuration</description> + </valueHelp> </properties> </leafNode> <leafNode name="passphrase"> diff --git a/interface-definitions/include/pki/public-key.xml.i b/interface-definitions/include/pki/public-key.xml.i index dfc6979fd..3067bff74 100644 --- a/interface-definitions/include/pki/public-key.xml.i +++ b/interface-definitions/include/pki/public-key.xml.i @@ -2,13 +2,13 @@ <leafNode name="public-key"> <properties> <help>Public key in PKI configuration</help> - <valueHelp> - <format>key name</format> - <description>Name of public key in PKI configuration</description> - </valueHelp> <completionHelp> <path>pki key-pair</path> </completionHelp> + <valueHelp> + <format>txt</format> + <description>Name of public key in PKI configuration</description> + </valueHelp> </properties> </leafNode> <!-- include end --> diff --git a/python/vyos/util.py b/python/vyos/util.py index 93a2f6640..b41c5b346 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -692,21 +692,21 @@ def find_device_file(device): return None -def dict_search(path, my_dict): - """ Traverse Python dictionary (my_dict) delimited by dot (.). +def dict_search(path, dict_object): + """ Traverse Python dictionary (dict_object) delimited by dot (.). Return value of key if found, None otherwise. - This is faster implementation then jmespath.search('foo.bar', my_dict)""" - if not isinstance(my_dict, dict) or not path: + This is faster implementation then jmespath.search('foo.bar', dict_object)""" + if not isinstance(dict_object, dict) or not path: return None parts = path.split('.') inside = parts[:-1] if not inside: - if path not in my_dict: + if path not in dict_object: return None - return my_dict[path] - c = my_dict + return dict_object[path] + c = dict_object for p in parts[:-1]: c = c.get(p, {}) return c.get(parts[-1], None) @@ -724,6 +724,23 @@ def dict_search_args(dict_object, *path): dict_object = dict_object[item] return dict_object +def dict_search_recursive(dict_object, key): + """ Traverse a dictionary recurisvely and return the value of the key + we are looking for. + + Thankfully copied from https://stackoverflow.com/a/19871956 + """ + if isinstance(dict_object, list): + for i in dict_object: + for x in dict_search_recursive(i, key): + yield x + elif isinstance(dict_object, dict): + if key in dict_object: + yield dict_object[key] + for j in dict_object.values(): + for x in dict_search_recursive(j, key): + yield x + def get_interface_config(interface): """ Returns the used encapsulation protocol for given interface. If interface does not exist, None is returned. diff --git a/src/conf_mode/pki.py b/src/conf_mode/pki.py index ef1b57650..efa3578b4 100755 --- a/src/conf_mode/pki.py +++ b/src/conf_mode/pki.py @@ -16,8 +16,11 @@ from sys import exit +import jmespath + from vyos.config import Config from vyos.configdict import dict_merge +from vyos.configdict import node_changed from vyos.pki import is_ca_certificate from vyos.pki import load_certificate from vyos.pki import load_certificate_request @@ -26,6 +29,7 @@ from vyos.pki import load_private_key from vyos.pki import load_crl from vyos.pki import load_dh_parameters from vyos.util import ask_input +from vyos.util import dict_search_recursive from vyos.xml import defaults from vyos import ConfigError from vyos import airbag @@ -37,14 +41,29 @@ def get_config(config=None): else: conf = Config() base = ['pki'] - if not conf.exists(base): - return None pki = conf.get_config_dict(base, key_mangling=('-', '_'), - get_first_key=True, no_tag_node_value_mangle=True) + get_first_key=True, + no_tag_node_value_mangle=True) + + pki['changed'] = {} + tmp = node_changed(conf, base + ['ca'], key_mangling=('-', '_')) + if tmp: pki['changed'].update({'ca' : tmp}) + + tmp = node_changed(conf, base + ['certificate'], key_mangling=('-', '_')) + if tmp: pki['changed'].update({'certificate' : tmp}) + + # We only merge on the defaults of there is a configuration at all + if conf.exists(base): + default_values = defaults(base) + pki = dict_merge(default_values, pki) + + # We need to get the entire system configuration to verify that we are not + # deleting a certificate that is still referenced somewhere! + pki['system'] = conf.get_config_dict([], key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) - default_values = defaults(base) - pki = dict_merge(default_values, pki) return pki def is_valid_certificate(raw_data): @@ -142,6 +161,21 @@ def verify(pki): if len(country) != 2 or not country.isalpha(): raise ConfigError(f'Invalid default country value. Value must be 2 alpha characters.') + if 'changed' in pki: + # if the list is getting longer, we can move to a dict() and also embed the + # search key as value from line 173 or 176 + for cert_type in ['ca', 'certificate']: + if not cert_type in pki['changed']: + continue + for certificate in pki['changed'][cert_type]: + if cert_type not in pki or certificate not in pki['changed'][cert_type]: + if cert_type == 'ca': + if certificate in dict_search_recursive(pki['system'], 'ca_certificate'): + raise ConfigError(f'CA certificate "{certificate}" is still in use!') + elif cert_type == 'certificate': + if certificate in dict_search_recursive(pki['system'], 'certificate'): + raise ConfigError(f'Certificate "{certificate}" is still in use!') + return None def generate(pki): @@ -154,6 +188,8 @@ def apply(pki): if not pki: return None + # XXX: restart services if the content of a certificate changes + return None if __name__ == '__main__': diff --git a/src/tests/test_dict_search.py b/src/tests/test_dict_search.py index 991722f0f..1028437b2 100644 --- a/src/tests/test_dict_search.py +++ b/src/tests/test_dict_search.py @@ -16,13 +16,25 @@ from unittest import TestCase from vyos.util import dict_search +from vyos.util import dict_search_recursive data = { 'string': 'fooo', 'nested': {'string': 'bar', 'empty': '', 'list': ['foo', 'bar']}, 'non': {}, 'list': ['bar', 'baz'], - 'dict': {'key_1': {}, 'key_2': 'vyos'} + 'dict': {'key_1': {}, 'key_2': 'vyos'}, + 'interfaces': {'dummy': {'dum0': {'address': ['192.0.2.17/29']}}, + 'ethernet': {'eth0': {'address': ['2001:db8::1/64', '192.0.2.1/29'], + 'description': 'Test123', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:01', + 'speed': 'auto'}, + 'eth1': {'address': ['192.0.2.9/29'], + 'description': 'Test456', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:02', + 'speed': 'auto'}}} } class TestDictSearch(TestCase): @@ -63,3 +75,10 @@ class TestDictSearch(TestCase): # TestDictSearch: Return list items when querying nested list self.assertEqual(dict_search('nested.list', None), None) self.assertEqual(dict_search(None, data), None) + + def test_dict_search_recursive(self): + # Test nested search in dictionary + tmp = list(dict_search_recursive(data, 'hw_id')) + self.assertEqual(len(tmp), 2) + tmp = list(dict_search_recursive(data, 'address')) + self.assertEqual(len(tmp), 3) |