Re: Sharing OpenSSL CTX between multiple sockets

2018-11-22 Thread Julian Wiesener
Hi Lukas,

On Thu, 22 Nov 2018 19:39:11 +0100
Lukas Tribus  wrote:
> Trying to understand the use-case better here, binding to any IP is
> not acceptable? Your client *needs* to bind to specific IPs?

one bind for multiple IPs would reduce the flexibility of the config, you could 
not longer set different Backends on different IPs that share one certificate 
(directory) for example. There are clealy ways to reduce the reload times by 
changing the configuration, however there is a strong preference to keep the 
current configuration layout and solve the problem in code, if there are no 
strong reasons against it.


> I'd assume such a configuration would only create a single CTX, do you
> know whether that is in fact the case (as it's just an assumption)?

I did not verify, but based on my understanding of the code, i would assume the 
same.


Regards,
Julian



Re: Sharing OpenSSL CTX between multiple sockets

2018-11-22 Thread Julian Wiesener
Hi again,

of course i forgot to attach the patch...


Kind regards,
Julian
diff --git a/include/types/ssl_sock.h b/include/types/ssl_sock.h
index 2e02631c..76073f37 100644
--- a/include/types/ssl_sock.h
+++ b/include/types/ssl_sock.h
@@ -71,4 +71,14 @@ struct sh_ssl_sess_hdr {
 	unsigned char key_data[SSL_MAX_SSL_SESSION_ID_LENGTH];
 };
 
+/* shared ssl ctx */
+struct shared_ssl_ctx {
+	struct list list; /* Used to chain refs. */
+	char *path;
+SSL_CTX *ctx;
+	int refcount;  /* number of users of this shared_ssl_ctx. */
+	__decl_hathreads(HA_RWLOCK_T lock); /* lock used to protect the ref */
+enum { loaded, prepared } state;
+};
+
 #endif /* _TYPES_SSL_SOCK_H */
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index d26ee789..823eda69 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -330,6 +330,7 @@ const char *SSL_SOCK_KEYTYPE_NAMES[] = {
 
 static struct shared_context *ssl_shctx = NULL; /* ssl shared session cache */
 static struct eb_root *sh_ssl_sess_tree; /* ssl shared session tree */
+static struct list shared_ssl_ctx_list = LIST_HEAD_INIT(shared_ssl_ctx_list); /* list of shared ssl ctxs */
 
 #define sh_ssl_sess_tree_delete(s)	ebmb_delete(&(s)->key);
 
@@ -910,6 +911,27 @@ int ssl_sock_update_tlskey(char *filename, struct buffer *tlskey, char **err)
 	return 0;
 }
 
+
+struct shared_ssl_ctx *shared_ssl_ctx_lookup_name(const char* path)
+{
+struct shared_ssl_ctx *ss_ctx;
+
+list_for_each_entry(ss_ctx, _ssl_ctx_list, list)
+if (ss_ctx->path && strcmp(path, ss_ctx->path) == 0)
+return ss_ctx;
+return NULL;
+}
+
+struct shared_ssl_ctx *shared_ssl_ctx_lookup_ctx(SSL_CTX *ctx)
+{
+struct shared_ssl_ctx *ss_ctx;
+
+list_for_each_entry(ss_ctx, _ssl_ctx_list, list)
+if (ss_ctx->ctx && ctx == ss_ctx->ctx)
+return ss_ctx;
+return NULL;
+}
+
 /* This function finalize the configuration parsing. Its set all the
  * automatic ids. It's called just after the basic checks. It returns
  * 0 on success otherwise ERR_*.
@@ -3322,20 +3344,30 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf
 {
 	int ret;
 	SSL_CTX *ctx;
-
-	ctx = SSL_CTX_new(SSLv23_server_method());
-	if (!ctx) {
-		memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n",
-		  err && *err ? *err : "", path);
-		return 1;
-	}
-
-	if (SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) {
-		memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
-		  err && *err ? *err : "", path);
-		SSL_CTX_free(ctx);
-		return 1;
-	}
+struct shared_ssl_ctx *ss_ctx;
+
+	ss_ctx = shared_ssl_ctx_lookup_name(path);
+if(ss_ctx /* FIXME cfg */ ) {
+		ss_ctx->refcount++; /* FIXME lock needed? */
+ss_ctx->state = loaded;
+ctx = ss_ctx->ctx;
+}
+
+if(!ss_ctx) {
+ctx = SSL_CTX_new(SSLv23_server_method());
+if (!ctx) {
+memprintf(err, "%sunable to allocate SSL context for cert '%s'.\n",
+  err && *err ? *err : "", path);
+return 1;
+}
+
+if (SSL_CTX_use_PrivateKey_file(ctx, path, SSL_FILETYPE_PEM) <= 0) {
+memprintf(err, "%sunable to load SSL private key from PEM file '%s'.\n",
+  err && *err ? *err : "", path);
+SSL_CTX_free(ctx);
+return 1;
+}
+}
 
 	ret = ssl_sock_load_cert_chain_file(ctx, path, bind_conf, ssl_conf, sni_filter, fcount);
 	if (ret <= 0) {
@@ -3346,54 +3378,73 @@ static int ssl_sock_load_cert_file(const char *path, struct bind_conf *bind_conf
 		return 1;
 	}
 
-	if (SSL_CTX_check_private_key(ctx) <= 0) {
+	if (!ss_ctx && SSL_CTX_check_private_key(ctx) <= 0) {
 		memprintf(err, "%sinconsistencies between private key and certificate loaded from PEM file '%s'.\n",
 		  err && *err ? *err : "", path);
 		return 1;
 	}
 
-	/* we must not free the SSL_CTX anymore below, since it's already in
-	 * the tree, so it will be discovered and cleaned in time.
-	 */
+if(!ss_ctx) {
+/* we must not free the SSL_CTX anymore below, since it's already in
+ * the tree, so it will be discovered and cleaned in time.
+ */
 #ifndef OPENSSL_NO_DH
-	/* store a NULL pointer to indicate we have not yet loaded
-	   a custom DH param file */
-	if (ssl_dh_ptr_index >= 0) {
-		SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL);
-	}
+/* store a NULL pointer to indicate we have not yet loaded
+   a custom DH param file */
+if (ssl_dh_ptr_index >= 0) {
+SSL_CTX_set_ex_data(ctx, ssl_dh_ptr_index, NULL);
+}
 
-	ret = ssl_sock_load_dh_params(ctx, path);
-	if (ret < 0) {
-		if (err)
-			memprintf(err, "%sunable to load DH parameters from file '%s'.\n",
-  *err ? *err : "", path);
-		return 1;
-	}
+ret = ssl_sock_load_dh_params(ctx, path);
+if (ret < 0) {
+if (err)
+memprintf(err, 

Sharing OpenSSL CTX between multiple sockets

2018-11-22 Thread Julian Wiesener
Hello,

one of our clients runs a haproxy setup with a 2000+ SSL-Certificates on 
multiple IPs. As an OpenSSL CTX needs to be created for each certificate for 
each sockets, restarting or reloading the config takes several minutes. 
Therfore i like to propose to share the CTX for on multiple sockets, which 
reduces the reload-times to acceptable values (~9 secs+0.5 per IP instead of 8 
oer IP on our testsetup).

The attached patch is a POC based on 73373ab43a4599e8bcb209687e9ec1c7be37779a, 
i'm aware that this is at this state not commitable, but i like to discuss the 
further directions.

First issue is, it needs to be configurable and should be disabled by default, 
as it disables the ability to set different SSL options on different IPs. One 
way would be a global option, good enough for me, but you wouldn't be able to 
run a client-certificate setup only one IP and share the server certificates 
with others. Enable by bind configuration is an onther option that should not 
make to mouch trouble, however we still would need to decide on how to name 
them, i would propose share_ssl_ctx.

Also there is some name confusion in the code. atm there is struct 
shared_context, which is not for sharing CTX, it is a shared session cache. 
Therfore i introduced shared_ssl_ctx_list which does share the context. I could 
live with it, but i think that might lead to confusion in the future.

I did not yet care for reloading the config. If someone would disable the 
proposed sharing option and reloading, it might end in an disaster, i'll need 
to look into the code, removing or changing of certificates without restart is 
also tbd.

It also was not clear to me yet, if i require a lock, a ctx at any time, if 
only one thread is running whenever the config is reloaded, i think there is no 
need to, but i might have missed cases which could lead to deinitialization of 
an CTX.


So please let me know what you think and what needs to be done to get it 
upstream.


Kind regards,
Julian