Explorar o código

qnetd: Support multiple server certificates

This is mainly useful for hybrid PKI configurations. Usage is pretty
simple, just use `-S cert_nickname=` multiple times.

Signed-off-by: Jan Friesse <jfriesse@redhat.com>
Reviewed-by: Fabio M. Di Nitto <fdinitto@redhat.com>
Jan Friesse hai 1 día
pai
achega
fea5154bf7

+ 5 - 2
man/corosync-qnetd.8

@@ -31,7 +31,7 @@
 .\" * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 .\" * THE POSSIBILITY OF SUCH DAMAGE.
 .\" */
-.TH COROSYNC-QNETD 8 2025-10-16
+.TH COROSYNC-QNETD 8 2025-11-10
 .SH NAME
 corosync-qnetd \- QNet daemon
 .SH SYNOPSIS
@@ -205,7 +205,10 @@ allowed message size received by client). (32768)
 NSS database directory. (/etc/corosync/qnetd/nssdb)
 .TP
 .B cert_nickname
-NSS nickname of qnetd server certificate. (QNetd Cert)
+NSS database nickname of the QNetd server certificate.
+This option can be specified multiple times to configure multiple certificates.
+Order matters; preferred certificates should be specified first.
+If this option is specified, the default value is ignored.  (QNetd Cert)
 .TP
 .B heartbeat_interval_min
 Minimum heartbeat timeout accepted by server in ms. (1000)

+ 9 - 2
qdevices/corosync-qnetd.c

@@ -345,6 +345,7 @@ main(int argc, char * const argv[])
 	enum tlv_tls_supported tls_supported;
 	int client_cert_required;
 	size_t max_clients;
+	size_t zi;
 	PRIntn address_family;
 	int lock_file;
 	int another_instance_running;
@@ -358,6 +359,10 @@ main(int argc, char * const argv[])
 	cli_parse(argc, argv, &host_addr, &host_port, &foreground, &debug_log, &bump_log_priority,
 	    &tls_supported, &client_cert_required, &max_clients, &address_family, &advanced_settings);
 
+	if (qnetd_advanced_settings_post_parse(&advanced_settings) != 0) {
+		errx(EXIT_FAILURE, "Can't alloc memory for advanced settings");
+	}
+
 	log_target = LOG_TARGET_SYSLOG;
 	if (foreground) {
 		log_target |= LOG_TARGET_STDERR;
@@ -480,8 +485,10 @@ main(int argc, char * const argv[])
 		qnetd_warn_nss();
 	}
 
-	CERT_DestroyCertificate(instance.server.cert);
-	SECKEY_DestroyPrivateKey(instance.server.private_key);
+	for (zi = 0; zi < instance.server.no_certs; zi++) {
+		CERT_DestroyCertificate(instance.server.certs[zi]);
+		SECKEY_DestroyPrivateKey(instance.server.private_keys[zi]);
+	}
 
 	SSL_ClearSessionCache();
 

+ 8 - 4
qdevices/nss-sock.c

@@ -461,11 +461,13 @@ nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const char *ssl_url,
 }
 
 PRFileDesc *
-nss_sock_start_ssl_as_server(PRFileDesc *input_sock, CERTCertificate *server_cert,
-    SECKEYPrivateKey *server_key, int require_client_cert, int force_handshake,
+nss_sock_start_ssl_as_server(PRFileDesc *input_sock,
+    size_t no_certs, CERTCertificate **server_certs, SECKEYPrivateKey **server_keys,
+    int require_client_cert, int force_handshake,
     int *reset_would_block)
 {
 	PRFileDesc *ssl_sock;
+	size_t zi;
 
 	if (force_handshake) {
 		*reset_would_block = 0;
@@ -476,8 +478,10 @@ nss_sock_start_ssl_as_server(PRFileDesc *input_sock, CERTCertificate *server_cer
 		return (NULL);
 	}
 
-	if (SSL_ConfigServerCert(ssl_sock, server_cert, server_key, NULL, 0) != SECSuccess) {
-		return (NULL);
+	for (zi = 0; zi < no_certs; zi++) {
+		if (SSL_ConfigServerCert(ssl_sock, server_certs[zi], server_keys[zi], NULL, 0) != SECSuccess) {
+			return (NULL);
+		}
 	}
 
 	if ((SSL_OptionSet(ssl_sock, SSL_SECURITY, PR_TRUE) != SECSuccess) ||

+ 2 - 2
qdevices/nss-sock.h

@@ -73,8 +73,8 @@ extern PRFileDesc	*nss_sock_start_ssl_as_client(PRFileDesc *input_sock, const ch
     int force_handshake, int *reset_would_block);
 
 extern PRFileDesc	*nss_sock_start_ssl_as_server(PRFileDesc *input_sock,
-    CERTCertificate *server_cert, SECKEYPrivateKey *server_key, int require_client_cert,
-    int force_handshake, int *reset_would_block);
+    size_t no_certs, CERTCertificate **server_certs, SECKEYPrivateKey **server_keys,
+    int require_client_cert, int force_handshake, int *reset_would_block);
 
 extern int		 nss_sock_non_blocking_client_init(const char *host_name,
     uint16_t port, PRIntn af, struct nss_sock_non_blocking_client *client);

+ 2 - 0
qdevices/qnet-config.h

@@ -88,6 +88,8 @@ extern "C" {
 
 #define QNETD_DEFAULT_KEEP_ACTIVE_PARTITION_TB		TLV_KEEP_ACTIVE_PARTITION_TIE_BREAKER_DISABLED
 
+#define QNETD_MAX_CERTIFICATES				16
+
 #define QNETD_TOOL_PROGRAM_NAME				"corosync-qnetd-tool"
 
 #define QDEVICE_NET_DEFAULT_NSS_DB_DIR			COROSYSCONFDIR "/qdevice/net/nssdb"

+ 30 - 6
qdevices/qnetd-advanced-settings.c

@@ -60,13 +60,13 @@ qnetd_advanced_settings_init(struct qnetd_advanced_settings *settings)
 	if ((settings->nss_db_dir = strdup(QNETD_DEFAULT_NSS_DB_DIR)) == NULL) {
 		return (-1);
 	}
-	if ((settings->cert_nickname = strdup(QNETD_DEFAULT_CERT_NICKNAME)) == NULL) {
-		return (-1);
-	}
 	settings->heartbeat_interval_min = QNETD_DEFAULT_HEARTBEAT_INTERVAL_MIN;
 	settings->heartbeat_interval_max = QNETD_DEFAULT_HEARTBEAT_INTERVAL_MAX;
 	settings->dpd_enabled = QNETD_DEFAULT_DPD_ENABLED;
 	settings->dpd_interval_coefficient = QNETD_DEFAULT_DPD_INTERVAL_COEFFICIENT;
+	if ((settings->cert_nicknames = calloc(QNETD_MAX_CERTIFICATES, sizeof(char *))) == NULL) {
+		return (-1);
+	}
 	if ((settings->lock_file = strdup(QNETD_DEFAULT_LOCK_FILE)) == NULL) {
 		return (-1);
 	}
@@ -85,12 +85,32 @@ qnetd_advanced_settings_init(struct qnetd_advanced_settings *settings)
 	return (0);
 }
 
+int
+qnetd_advanced_settings_post_parse(struct qnetd_advanced_settings *settings)
+{
+
+	if (settings->no_cert_nicknames == 0) {
+		if ((settings->cert_nicknames[settings->no_cert_nicknames] =
+		    strdup(QNETD_DEFAULT_CERT_NICKNAME)) == NULL) {
+			return (-1);
+		}
+
+		settings->no_cert_nicknames++;
+	}
+
+	return (0);
+}
+
 void
 qnetd_advanced_settings_destroy(struct qnetd_advanced_settings *settings)
 {
+	size_t zi;
 
 	free(settings->nss_db_dir);
-	free(settings->cert_nickname);
+	for (zi = 0; zi < settings->no_cert_nicknames; zi++) {
+		free(settings->cert_nicknames[zi]);
+	}
+	free(settings->cert_nicknames);
 	free(settings->lock_file);
 	free(settings->local_socket_file);
 }
@@ -139,11 +159,15 @@ qnetd_advanced_settings_set(struct qnetd_advanced_settings *settings,
 			return (-1);
 		}
 	} else if (strcasecmp(option, "cert_nickname") == 0) {
-		free(settings->cert_nickname);
+		if (settings->no_cert_nicknames + 1 > QNETD_MAX_CERTIFICATES) {
+			return (-2);
+		}
 
-		if ((settings->cert_nickname = strdup(value)) == NULL) {
+		if ((settings->cert_nicknames[settings->no_cert_nicknames] = strdup(value)) == NULL) {
 			return (-1);
 		}
+
+		settings->no_cert_nicknames++;
 	} else if (strcasecmp(option, "heartbeat_interval_min") == 0) {
 		if (utils_strtonum(value, QNETD_MIN_HEARTBEAT_INTERVAL, UINT32_MAX, &tmpll) == -1) {
 			return (-2);

+ 4 - 1
qdevices/qnetd-advanced-settings.h

@@ -47,7 +47,8 @@ struct qnetd_advanced_settings {
 	size_t max_client_send_size;
 	size_t max_client_receive_size;
 	char *nss_db_dir;
-	char *cert_nickname;
+	size_t no_cert_nicknames;
+	char **cert_nicknames;
 	uint32_t heartbeat_interval_min;
 	uint32_t heartbeat_interval_max;
 	uint8_t dpd_enabled;
@@ -66,6 +67,8 @@ struct qnetd_advanced_settings {
 
 extern int		qnetd_advanced_settings_init(struct qnetd_advanced_settings *settings);
 
+extern int		qnetd_advanced_settings_post_parse(struct qnetd_advanced_settings *settings);
+
 extern int		qnetd_advanced_settings_set(struct qnetd_advanced_settings *settings,
     const char *option, const char *value);
 

+ 3 - 2
qdevices/qnetd-client-msg-received.c

@@ -237,8 +237,9 @@ qnetd_client_msg_received_starttls(struct qnetd_instance *instance, struct qnetd
 		return (0);
 	}
 
-	if ((new_pr_fd = nss_sock_start_ssl_as_server(client->socket, instance->server.cert,
-	    instance->server.private_key, instance->tls_client_cert_required, 0, NULL)) == NULL) {
+	if ((new_pr_fd = nss_sock_start_ssl_as_server(client->socket,
+	    instance->server.no_certs, instance->server.certs, instance->server.private_keys,
+	    instance->tls_client_cert_required, 0, NULL)) == NULL) {
 		log_nss(LOG_ERR, "Can't start TLS. Disconnecting client.");
 
 		return (-1);

+ 43 - 9
qdevices/qnetd-instance.c

@@ -34,6 +34,8 @@
 
 #include <sys/types.h>
 
+#include <assert.h>
+
 #include <pk11func.h>
 #include "log.h"
 #include "qnetd-instance.h"
@@ -92,6 +94,20 @@ qnetd_instance_init(struct qnetd_instance *instance,
 
 	instance->advanced_settings = advanced_settings;
 
+	instance->server.no_certs = instance->advanced_settings->no_cert_nicknames;
+
+	instance->server.certs = calloc(instance->server.no_certs,
+	    sizeof(CERTCertificate *));
+	if (instance->server.certs == NULL) {
+		goto error_exit;
+	}
+
+	instance->server.private_keys = calloc(instance->server.no_certs,
+	    sizeof(SECKEYPrivateKey *));
+	if (instance->server.private_keys == NULL) {
+		goto error_free_certs;
+	}
+
 	qnetd_client_list_init(&instance->clients);
 	qnetd_cluster_list_init(&instance->clusters);
 
@@ -107,10 +123,19 @@ qnetd_instance_init(struct qnetd_instance *instance,
 	    instance, NULL) == -1) {
 		log(LOG_ERR, "Can't add instance pre poll loop cb");
 
-		return (-1);
+		goto error_free_private_keys;
 	}
 
 	return (0);
+
+error_free_private_keys:
+	free(instance->server.private_keys);
+
+error_free_certs:
+	free(instance->server.certs);
+
+error_exit:
+	return (-1);
 }
 
 int
@@ -138,6 +163,9 @@ qnetd_instance_destroy(struct qnetd_instance *instance)
 
 	pr_poll_loop_destroy(&instance->main_poll_loop);
 
+	free(instance->server.private_keys);
+	free(instance->server.certs);
+
 	return (0);
 }
 
@@ -165,16 +193,22 @@ qnetd_instance_client_disconnect(struct qnetd_instance *instance, struct qnetd_c
 int
 qnetd_instance_init_certs(struct qnetd_instance *instance)
 {
+	size_t zi;
 
-	instance->server.cert = PK11_FindCertFromNickname(
-	    instance->advanced_settings->cert_nickname, NULL);
-	if (instance->server.cert == NULL) {
-		return (-1);
-	}
+	assert(instance->advanced_settings->no_cert_nicknames == instance->server.no_certs);
 
-	instance->server.private_key = PK11_FindKeyByAnyCert(instance->server.cert, NULL);
-	if (instance->server.private_key == NULL) {
-		return (-1);
+	for (zi = 0; zi < instance->advanced_settings->no_cert_nicknames; zi++) {
+		instance->server.certs[zi] = PK11_FindCertFromNickname(
+		    instance->advanced_settings->cert_nicknames[zi], NULL);
+		if (instance->server.certs[zi] == NULL) {
+			return (-1);
+		}
+
+		instance->server.private_keys[zi] =
+		    PK11_FindKeyByAnyCert(instance->server.certs[zi], NULL);
+		if (instance->server.private_keys[zi] == NULL) {
+			return (-1);
+		}
 	}
 
 	return (0);

+ 3 - 2
qdevices/qnetd-instance.h

@@ -57,8 +57,9 @@ extern "C" {
 struct qnetd_instance {
 	struct {
 		PRFileDesc *socket;
-		CERTCertificate *cert;
-		SECKEYPrivateKey *private_key;
+		size_t no_certs;
+		CERTCertificate **certs;
+		SECKEYPrivateKey **private_keys;
 	} server;
 	size_t max_clients;
 	struct qnetd_client_list clients;