From 1fc69810450a306310fc6bf82cd0b2406d029fb2 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Mon, 6 Sep 2021 12:12:40 +0200
Subject: pki: T3642: verify() that we can not delete certificates still
 referenced in CLI

---
 src/conf_mode/pki.py | 46 +++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 41 insertions(+), 5 deletions(-)

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__':
-- 
cgit v1.2.3