[PATCH 05/10] remote: support specifying multiple keys/certs in libvirtd.conf

Daniel P. Berrangé via Devel posted 10 patches 2 weeks ago
[PATCH 05/10] remote: support specifying multiple keys/certs in libvirtd.conf
Posted by Daniel P. Berrangé via Devel 2 weeks ago
From: Daniel P. Berrangé <berrange@redhat.com>

The 'cert_file' and 'key_file' parameters in libvirtd.conf only
permit a single cert/key. To support hybrid deployments for PQC,
we need to be able to request multiple certs/keys. This involves
new 'cert_files' and 'key_files' config parameters that accept a
list of filenames. The new parameters are mutually exclusive with
the old parameters.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 src/remote/libvirtd.aug.in        |  2 ++
 src/remote/libvirtd.conf.in       | 16 ++++++++++++
 src/remote/remote_daemon.c        | 26 +++++++++----------
 src/remote/remote_daemon_config.c | 43 +++++++++++++++++++++++++++----
 src/remote/remote_daemon_config.h |  4 +--
 src/remote/test_libvirtd.aug.in   |  8 ++++++
 6 files changed, 79 insertions(+), 20 deletions(-)

diff --git a/src/remote/libvirtd.aug.in b/src/remote/libvirtd.aug.in
index d744548f41..1f3bb5d0e2 100644
--- a/src/remote/libvirtd.aug.in
+++ b/src/remote/libvirtd.aug.in
@@ -47,6 +47,8 @@ module @DAEMON_NAME_UC@ =
 
    let certificate_entry = str_entry "key_file"
                          | str_entry "cert_file"
+                         | str_array_entry "key_files"
+                         | str_array_entry "cert_files"
                          | str_entry "ca_file"
                          | str_entry "crl_file"
 
diff --git a/src/remote/libvirtd.conf.in b/src/remote/libvirtd.conf.in
index 32a680317a..e4460e61ef 100644
--- a/src/remote/libvirtd.conf.in
+++ b/src/remote/libvirtd.conf.in
@@ -244,12 +244,28 @@
 
 # Override the default server key file path
 #
+# This parameter is mutually exclusive with 'key_files'
+#
 #key_file = "@sysconfdir@/pki/libvirt/private/serverkey.pem"
 
+# Override the default server key file path(s)
+#
+# This parameter is mutually exclusive with 'key_file'
+#
+#key_files = ["@sysconfdir@/pki/libvirt/private/serverkey-0.pem", "@sysconfdir@/pki/libvirt/private/serverkey-1.pem"]
+
 # Override the default server certificate file path
 #
+# This parameter is mutually exclusive with 'cert_files'
+#
 #cert_file = "@sysconfdir@/pki/libvirt/servercert.pem"
 
+# Override the default server certificate file path(s)
+#
+# This parameter is mutually exclusive with 'cert_file'
+#
+#cert_files = ["@sysconfdir@/pki/libvirt/servercert-0.pem", "@sysconfdir@/pki/libvirt/servercert-1.pem"]
+
 # Override the default CA certificate path
 #
 #ca_file = "@sysconfdir@/pki/CA/cacert.pem"
diff --git a/src/remote/remote_daemon.c b/src/remote/remote_daemon.c
index e7c8f587c4..ee3d10bc23 100644
--- a/src/remote/remote_daemon.c
+++ b/src/remote/remote_daemon.c
@@ -325,31 +325,31 @@ daemonSetupNetworking(virNetServer *srv,
         virNetTLSContext *ctxt = NULL;
 
         if (config->ca_file ||
-            config->cert_file ||
-            config->key_file) {
-            const char *certs[] = { config->cert_file, NULL };
-            const char *keys[] = { config->key_file, NULL };
-
+            config->cert_files ||
+            config->key_files) {
+            g_autofree char *certs = g_strjoinv(", ", config->cert_files);
+            g_autofree char *keys = g_strjoinv(", ", config->key_files);
             if (!config->ca_file) {
                 virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                               _("No CA certificate path set to match server key/cert"));
+                               _("No CA certificate path set to match server key(s)/cert(s)"));
                 return -1;
             }
-            if (!config->cert_file) {
+            if (!config->cert_files) {
                 virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                               _("No server certificate path set to match server key"));
+                               _("No server certificate path(s) set to match server key(s)"));
                 return -1;
             }
-            if (!config->key_file) {
+            if (!config->key_files) {
                 virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
-                               _("No server key path set to match server cert"));
+                               _("No server key path(s) set to match server cert(s)"));
                 return -1;
             }
-            VIR_DEBUG("Using CA='%s' cert='%s' key='%s'",
-                      config->ca_file, config->cert_file, config->key_file);
+            VIR_DEBUG("Using CA='%s' certs='%s' keys='%s'",
+                      config->ca_file, certs, keys);
             if (!(ctxt = virNetTLSContextNewServer(config->ca_file,
                                                    config->crl_file,
-                                                   certs, keys,
+                                                   (const char *const*)config->cert_files,
+                                                   (const char *const*)config->key_files,
                                                    (const char *const*)config->tls_allowed_dn_list,
                                                    config->tls_priority,
                                                    config->tls_no_sanity_certificate ? false : true,
diff --git a/src/remote/remote_daemon_config.c b/src/remote/remote_daemon_config.c
index c1e75444e1..bb6078967f 100644
--- a/src/remote/remote_daemon_config.c
+++ b/src/remote/remote_daemon_config.c
@@ -192,9 +192,9 @@ daemonConfigFree(struct daemonConfig *data)
 
     g_free(data->tls_priority);
 
-    g_free(data->key_file);
+    g_strfreev(data->key_files);
     g_free(data->ca_file);
-    g_free(data->cert_file);
+    g_strfreev(data->cert_files);
     g_free(data->crl_file);
 #endif /* ! WITH_IP */
 
@@ -212,8 +212,12 @@ daemonConfigLoadOptions(struct daemonConfig *data,
                         virConf *conf)
 {
     int rc G_GNUC_UNUSED;
-
 #ifdef WITH_IP
+    g_autofree char *cert_file = NULL;
+    g_autofree char *key_file = NULL;
+    size_t ncerts;
+    size_t nkeys;
+
     if (virConfGetValueBool(conf, "listen_tcp", &data->listen_tcp) < 0)
         return -1;
     if (virConfGetValueBool(conf, "listen_tls", &data->listen_tls) < 0)
@@ -269,10 +273,39 @@ daemonConfigLoadOptions(struct daemonConfig *data,
     if (virConfGetValueBool(conf, "tls_no_verify_certificate", &data->tls_no_verify_certificate) < 0)
         return -1;
 
-    if (virConfGetValueString(conf, "key_file", &data->key_file) < 0)
+    if (virConfGetValueString(conf, "key_file", &key_file) < 0)
+        return -1;
+    if (virConfGetValueString(conf, "cert_file", &cert_file) < 0)
+        return -1;
+    if (virConfGetValueStringList(conf, "key_files", false, &data->key_files) < 0)
+        return -1;
+    if (virConfGetValueStringList(conf, "cert_files", false, &data->cert_files) < 0)
+        return -1;
+    if ((cert_file && data->cert_files) ||
+         (key_file && data->key_files)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("cert_file/key_file are mutually exclusive with cert_files/key_files"));
         return -1;
-    if (virConfGetValueString(conf, "cert_file", &data->cert_file) < 0)
+    }
+    if (cert_file) {
+        data->cert_files = g_new0(char *, 2);
+        data->cert_files[0] = g_steal_pointer(&cert_file);
+        data->cert_files[1] = NULL;
+    }
+    if (key_file) {
+        data->key_files = g_new0(char *, 2);
+        data->key_files[0] = g_steal_pointer(&key_file);
+        data->key_files[1] = NULL;
+    }
+    ncerts = data->cert_files ? g_strv_length(data->cert_files) : 0;
+    nkeys = data->key_files ? g_strv_length(data->key_files) : 0;
+    if (ncerts != nkeys) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("Number of certificates (%1$zu) must match number of keys (%2$zu)"),
+                       ncerts, nkeys);
         return -1;
+    }
+
     if (virConfGetValueString(conf, "ca_file", &data->ca_file) < 0)
         return -1;
     if (virConfGetValueString(conf, "crl_file", &data->crl_file) < 0)
diff --git a/src/remote/remote_daemon_config.h b/src/remote/remote_daemon_config.h
index 9f9e54e838..e699889191 100644
--- a/src/remote/remote_daemon_config.h
+++ b/src/remote/remote_daemon_config.h
@@ -58,8 +58,8 @@ struct daemonConfig {
     char *tls_priority;
     unsigned int tcp_min_ssf;
 
-    char *key_file;
-    char *cert_file;
+    char **key_files;
+    char **cert_files;
     char *ca_file;
     char *crl_file;
 #endif /* ! WITH_IP */
diff --git a/src/remote/test_libvirtd.aug.in b/src/remote/test_libvirtd.aug.in
index c27680e130..a37b0daa55 100644
--- a/src/remote/test_libvirtd.aug.in
+++ b/src/remote/test_libvirtd.aug.in
@@ -26,7 +26,15 @@ module Test_@DAEMON_NAME@ =
         }
 @CUT_ENABLE_IP@
         { "key_file" = "@sysconfdir@/pki/libvirt/private/serverkey.pem" }
+        { "key_files"
+             { "1" = "@sysconfdir@/pki/libvirt/private/serverkey-0.pem" }
+             { "2" = "@sysconfdir@/pki/libvirt/private/serverkey-1.pem" }
+        }
         { "cert_file" = "@sysconfdir@/pki/libvirt/servercert.pem" }
+        { "cert_files"
+             { "1" = "@sysconfdir@/pki/libvirt/servercert-0.pem" }
+             { "2" = "@sysconfdir@/pki/libvirt/servercert-1.pem" }
+        }
         { "ca_file" = "@sysconfdir@/pki/CA/cacert.pem" }
         { "crl_file" = "@sysconfdir@/pki/CA/crl.pem" }
         { "tls_no_sanity_certificate" = "1" }
-- 
2.51.1