Hello ML,

This is a first patch attempt to add the SSL/TLS support to peers.

Everything is detailed in the commit log. This patch is not supposed to be integrated right now because the documentation is missing. Furthermore there are remaining SSL/TLS keywords to be supported which must be identified. Any advice would be appreciated.

Fred
>From 0bc8780e90140b638644c3912ba22f964143c130 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <flecai...@haproxy.com>
Date: Wed, 11 Apr 2018 14:04:26 +0200
Subject: [PATCH] MINOR: peers: Add SSL/TLS support.

As all peers belonging to the same "peers" section share the
same SSL/TLS settings, these latter are provided on lines which
may potentially be the last ones of a "peers" section. Such lines
are not identified by their first words. Perhaps this should be the case.

So, this patch extracts the current code used to setup the connection binding
of each frontend for each "peers" section so that it can be used after
having completely parsed "peers" sections. A "bind_conf" structure field
has been added to "peers" structures to do so. On the counterpart, a "server"
structure field has been also added to "peers" structure to store at parsing
time the SSL/TLS settings used to connect to remote peers.

Both these two new "peers" fields are used after having parsed the configuration
files to setup the SSL/TLS settings of all peers in the section  (local or remote).
This is the reason why these two new structure fields have been added to "peer"
structure.

This patch also adds "ssl" and "cert" two new keywords to basically enable
SSL/TLS usage to a "peers" section: "ssl" and "cert". Their syntaxes
are identical to the ones for "bind" or "server" lines.

Ex:
   # Enable SSL/TLS support for "my_peers" section peers
   peers my_peers
      peer foo1 ...
      peer foo2 ...
      ssl cert my/cert.pem
---
 include/proto/peers.h  |  67 ++++++++++++++++++++++
 include/proto/server.h |   1 +
 include/types/peers.h  |  24 ++++++++
 src/cfgparse.c         | 150 +++++++++++++++++++++++++++++++++++++++----------
 src/peers.c            |  38 ++++++++++++-
 src/server.c           |   2 +-
 src/ssl_sock.c         |  44 +++++++++++++++
 7 files changed, 293 insertions(+), 33 deletions(-)

diff --git a/include/proto/peers.h b/include/proto/peers.h
index 782b66e..c5f70c1 100644
--- a/include/proto/peers.h
+++ b/include/proto/peers.h
@@ -28,9 +28,76 @@
 #include <types/stream.h>
 #include <types/peers.h>
 
+#include <proto/server.h>
+
+struct peers_kw *peers_find_kw(const char *kw);
+void peers_register_keywords(struct peers_kw_list *kwl);
+
 void peers_init_sync(struct peers *peers);
 void peers_register_table(struct peers *, struct stktable *table);
 void peers_setup_frontend(struct proxy *fe);
 
+#if defined(USE_OPENSSL)
+static inline enum obj_type *peer_session_target(struct peer *p, struct stream *s)
+{
+	if (p->srv.use_ssl)
+		return &p->srv.obj_type;
+	else
+		return &s->be->obj_type;
+}
+
+static inline struct xprt_ops *peers_fe_xprt(struct peers *peers)
+{
+	return peers->bind_conf.is_ssl ? xprt_get(XPRT_SSL) : xprt_get(XPRT_RAW);
+}
+
+static inline int peers_prepare_srvs(struct peers *peers)
+{
+	int ret;
+	struct peer *p;
+
+	if (!peers->srv.use_ssl)
+		return 0;
+
+	ret = 0;
+	for (p = peers->remote; p; p = p->next) {
+		struct xprt_ops *xprt_ops;
+
+		if (p->local)
+			continue;
+
+		p->srv.use_ssl = 1;
+		srv_ssl_settings_cpy(&p->srv, &peers->srv);
+		xprt_ops = xprt_get(XPRT_SSL);
+		if (!xprt_ops || !xprt_ops->prepare_srv)
+			continue;
+
+		p->srv.obj_type = OBJ_TYPE_SERVER;
+		/* These two following fields are required by ssl_sock API
+		 * error handling functions.
+		 */
+		p->srv.proxy = peers->peers_fe;
+		p->srv.id = p->id;
+		ret += xprt_ops->prepare_srv(&p->srv);
+	}
+	return ret;
+}
+#else
+static inline enum obj_type *peer_session_target(struct peer *p, struct stream *s)
+{
+	return &s->be->obj_type;
+}
+
+static inline struct xprt_ops *peers_fe_xprt(struct peers *p)
+{
+	return xprt_get(XPRT_RAW);
+}
+
+static inline int peers_prepare_srvs(struct peers *p)
+{
+	return 0;
+}
+#endif
+
 #endif /* _PROTO_PEERS_H */
 
diff --git a/include/proto/server.h b/include/proto/server.h
index 14f4926..0a4a035 100644
--- a/include/proto/server.h
+++ b/include/proto/server.h
@@ -49,6 +49,7 @@ void apply_server_state(void);
 void srv_compute_all_admin_states(struct proxy *px);
 int srv_set_addr_via_libc(struct server *srv, int *err_code);
 int srv_init_addr(void);
+void srv_ssl_settings_cpy(struct server *srv, struct server *src);
 struct server *cli_find_server(struct appctx *appctx, char *arg);
 void servers_update_status(void);
 
diff --git a/include/types/peers.h b/include/types/peers.h
index 58c8c4e..097223d 100644
--- a/include/types/peers.h
+++ b/include/types/peers.h
@@ -33,6 +33,19 @@
 #include <common/tools.h>
 #include <eb32tree.h>
 
+struct peers_kw {
+	const char *kw;
+	int (*parse)(char **args, int *cur_arg, struct proxy *px,
+	             struct peers *peers, char **err);
+	int skip;
+};
+
+struct peers_kw_list {
+	const char *scope;
+	struct list list;
+	struct peers_kw kw[VAR_ARRAY];
+};
+
 struct shared_table {
 	struct stktable *table;		    /* stick table to sync */
 	int local_id;
@@ -69,6 +82,10 @@ struct peer {
 	struct shared_table *tables;
 	__decl_hathreads(HA_SPINLOCK_T lock); /* lock used to handle this peer section */
 	struct peer *next;	  /* next peer in the list */
+#if defined(USE_OPENSSL)
+	struct server srv;
+	struct bind_conf *bind_conf;  /* Used only if this peer is local */
+#endif
 };
 
 
@@ -83,12 +100,19 @@ struct peers {
 	struct {
 		const char *file;	 /* file where the section appears */
 		int line;		 /* line where the section appears */
+		char *arg0;          /* Used only for error handling */
+		char *arg1;          /* Used only for error handling */
+		char *bind_conf_arg; /* argument used by bind_conf_alloc() */
 	} conf;				 /* config information */
 	time_t last_change;
 	struct peers *next;		 /* next peer section */
 	unsigned int flags;		 /* current peers section resync state */
 	unsigned int resync_timeout;	 /* resync timeout timer */
 	int count;			 /* total of peers */
+#if defined(USE_OPENSSL)
+	struct server srv;            /* Used only during parsing */
+	struct bind_conf bind_conf;   /* Used only during parsing */
+#endif
 };
 
 
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 37bbf45..5af8338 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1937,6 +1937,86 @@ static int create_cond_regex_rule(const char *file, int line,
 	return ret_code;
 }
 
+static int peers_fe_setup_listener(struct peers *peers, char **err)
+{
+#if defined(USE_OPENSSL)
+	struct bind_conf *fe_bind_conf;
+#endif
+	struct bind_conf *bind_conf;
+	struct xprt_ops *xprt_ops;
+	struct listener *l;
+	const char *file, *arg0, *arg1;
+	char *bind_conf_arg;
+	int line;
+
+	xprt_ops = peers_fe_xprt(peers);
+	file = peers->peers_fe->conf.file;
+	line = peers->peers_fe->conf.line;
+	arg0 = peers->conf.arg0;
+	arg1 = peers->conf.arg1;
+	bind_conf_arg = peers->conf.bind_conf_arg;
+
+	bind_conf = bind_conf_alloc(peers->peers_fe, file, line,
+	                            peers->conf.bind_conf_arg, xprt_ops);
+	if (!str2listener(bind_conf_arg, peers->peers_fe, bind_conf, file, line, err)) {
+		if (err && *err) {
+			indent_msg(err, 2);
+			ha_alert("parsing [%s:%d] : '%s %s' : %s\n",
+			         file, line, arg0, arg1, *err);
+		}
+		else {
+			ha_alert("parsing [%s:%d] : '%s %s' : error encountered "
+			         "while parsing listening address %s.\n",
+			         file, line, arg0, arg1, bind_conf_arg);
+		}
+		return ERR_FATAL;
+	}
+
+	free(peers->conf.arg0);
+	peers->conf.arg0 = NULL;
+	free(peers->conf.arg1);
+	peers->conf.arg1 = NULL;
+	free(peers->conf.bind_conf_arg);
+	peers->conf.bind_conf_arg = NULL;
+
+#if defined(USE_OPENSSL)
+	fe_bind_conf = &peers->bind_conf;
+	if (fe_bind_conf->is_ssl) {
+		bind_conf->is_ssl = fe_bind_conf->is_ssl;
+		bind_conf->ca_ignerr        = fe_bind_conf->ca_ignerr;
+		bind_conf->crt_ignerr       = fe_bind_conf->crt_ignerr;
+		bind_conf->initial_ctx      = fe_bind_conf->initial_ctx;
+		bind_conf->default_ctx      = fe_bind_conf->default_ctx;
+		bind_conf->default_ssl_conf = fe_bind_conf->default_ssl_conf;
+		bind_conf->strict_sni       = fe_bind_conf->strict_sni;
+		bind_conf->ssl_options      = fe_bind_conf->ssl_options;
+		bind_conf->keys_ref         = fe_bind_conf->keys_ref;
+		bind_conf->ca_sign_file     = fe_bind_conf->ca_sign_file;
+		bind_conf->ca_sign_pass     = fe_bind_conf->ca_sign_pass;
+		bind_conf->ca_sign_cert     = fe_bind_conf->ca_sign_cert;
+		bind_conf->ca_sign_pkey     = fe_bind_conf->ca_sign_pkey;
+		memcpy(&bind_conf->ssl_conf,  &fe_bind_conf->ssl_conf,  sizeof bind_conf->ssl_conf);
+		memcpy(&bind_conf->sni_ctx,   &fe_bind_conf->sni_ctx,   sizeof bind_conf->sni_ctx);
+		memcpy(&bind_conf->sni_w_ctx, &fe_bind_conf->sni_w_ctx, sizeof bind_conf->sni_w_ctx);
+		if (bind_conf->xprt->prepare_bind_conf &&
+		    bind_conf->xprt->prepare_bind_conf(bind_conf) < 0)
+			return ERR_FATAL;
+	}
+#endif
+	list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+		l->maxaccept = 1;
+		l->maxconn = peers->peers_fe->maxconn;
+		l->backlog = peers->peers_fe->backlog;
+		l->accept = session_accept_fd;
+		l->analysers |=  peers->peers_fe->fe_req_ana;
+		l->default_target = peers->peers_fe->default_target;
+		l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */
+		global.maxsock += l->maxconn;
+	}
+
+	return 0;
+}
+
 /*
  * Parse a line in a <listen>, <frontend> or <backend> section.
  * Returns the error code, 0 if OK, or any combination of :
@@ -1952,10 +2032,9 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
 	static struct peers *curpeers = NULL;
 	struct peer *newpeer = NULL;
 	const char *err;
-	struct bind_conf *bind_conf;
-	struct listener *l;
 	int err_code = 0;
 	char *errmsg = NULL;
+	struct peers_kw *kw;
 
 	if (strcmp(args[0], "peers") == 0) { /* new peers section */
 		if (!*args[1]) {
@@ -2033,7 +2112,6 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
 		curpeers->remote = newpeer;
 		newpeer->conf.file = strdup(file);
 		newpeer->conf.line = linenum;
-
 		newpeer->last_change = now.tv_sec;
 		newpeer->id = strdup(args[1]);
 
@@ -2089,32 +2167,10 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
 				curpeers->peers_fe->id = strdup(args[1]);
 				curpeers->peers_fe->conf.args.file = curpeers->peers_fe->conf.file = strdup(file);
 				curpeers->peers_fe->conf.args.line = curpeers->peers_fe->conf.line = linenum;
+				curpeers->conf.arg0 = strdup(args[0]);
+				curpeers->conf.arg1 = strdup(args[1]);
+				curpeers->conf.bind_conf_arg = strdup(args[2]);
 				peers_setup_frontend(curpeers->peers_fe);
-
-				bind_conf = bind_conf_alloc(curpeers->peers_fe, file, linenum, args[2], xprt_get(XPRT_RAW));
-
-				if (!str2listener(args[2], curpeers->peers_fe, bind_conf, file, linenum, &errmsg)) {
-					if (errmsg && *errmsg) {
-						indent_msg(&errmsg, 2);
-						ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg);
-					}
-					else
-						ha_alert("parsing [%s:%d] : '%s %s' : error encountered while parsing listening address %s.\n",
-							 file, linenum, args[0], args[1], args[2]);
-					err_code |= ERR_FATAL;
-					goto out;
-				}
-
-				list_for_each_entry(l, &bind_conf->listeners, by_bind) {
-					l->maxaccept = 1;
-					l->maxconn = curpeers->peers_fe->maxconn;
-					l->backlog = curpeers->peers_fe->backlog;
-					l->accept = session_accept_fd;
-					l->analysers |=  curpeers->peers_fe->fe_req_ana;
-					l->default_target = curpeers->peers_fe->default_target;
-					l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */
-					global.maxsock += l->maxconn;
-				}
 			}
 			else {
 				ha_alert("parsing [%s:%d] : '%s %s' : local peer name already referenced at %s:%d.\n",
@@ -2132,9 +2188,33 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
 		curpeers->state = PR_STNEW;
 	}
 	else if (*args[0] != 0) {
-		ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection);
-		err_code |= ERR_ALERT | ERR_FATAL;
-		goto out;
+		int cur_arg = 0;
+
+		while ((kw = peers_find_kw(args[cur_arg]))) {
+			int ret;
+
+			ret = kw->parse(args, &cur_arg, curpeers->peers_fe, curpeers, &errmsg);
+			err_code |= ret;
+			if (ret) {
+				if (errmsg && *errmsg) {
+					indent_msg(&errmsg, 2);
+					ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg);
+				}
+				else
+					ha_alert("parging [%s:%d] : error encountered while processing '%s'\n",
+					         file, linenum, args[cur_arg]);
+				if (ret & ERR_FATAL)
+					goto out;
+			}
+			cur_arg += 1 + kw->skip;
+		}
+		args += cur_arg;
+		if (*args[0]) {
+			ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n",
+			         file, linenum, args[0], cursection);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
 	}
 
 out:
@@ -8890,6 +8970,14 @@ out_uri_auth_compat:
 				curpeers->peers_fe = NULL;
 			}
 			else {
+				char *err;
+
+				err = NULL;
+				err_code |= peers_fe_setup_listener(curpeers, &err);
+				free(err);
+
+				cfgerr += peers_prepare_srvs(curpeers);
+
 				peers_init_sync(curpeers);
 				last = &curpeers->next;
 				continue;
diff --git a/src/peers.c b/src/peers.c
index c56ed3a..cf019c5 100644
--- a/src/peers.c
+++ b/src/peers.c
@@ -39,6 +39,7 @@
 #include <proto/log.h>
 #include <proto/hdr_idx.h>
 #include <proto/mux_pt.h>
+#include <proto/peers.h>
 #include <proto/proto_tcp.h>
 #include <proto/proto_http.h>
 #include <proto/proxy.h>
@@ -176,6 +177,40 @@ enum {
 struct peers *cfg_peers = NULL;
 static void peer_session_forceshutdown(struct appctx *appctx);
 
+static struct peers_kw_list peers_keywords = {
+	.list = LIST_HEAD_INIT(peers_keywords.list)
+};
+
+struct peers_kw *peers_find_kw(const char *kw)
+{
+	int index;
+	const char *kwend;
+	struct peers_kw_list *kwl;
+	struct peers_kw *ret = NULL;
+
+	kwend = strchr(kw, '(');
+	if (!kwend)
+		kwend = kw + strlen(kw);
+
+	list_for_each_entry(kwl, &peers_keywords.list, list) {
+		for (index = 0; kwl->kw[index].kw != NULL; index++) {
+			if ((strncmp(kwl->kw[index].kw, kw, kwend - kw) == 0) &&
+			    kwl->kw[index].kw[kwend-kw] == 0) {
+				if (kwl->kw[index].parse)
+					return &kwl->kw[index]; /* found it !*/
+				else
+					ret = &kwl->kw[index];  /* may be OK */
+			}
+		}
+	}
+	return ret;
+}
+
+void peers_register_keywords(struct peers_kw_list *kwl)
+{
+	LIST_ADDQ(&peers_keywords.list, &kwl->list);
+}
+
 /* This function encode an uint64 to 'dynamic' length format.
    The encoded value is written at address *str, and the
    caller must assure that size after *str is large enought.
@@ -1929,7 +1964,8 @@ static struct appctx *peer_session_create(struct peers *peers, struct peer *peer
 	conn_install_mux(conn, &mux_pt_ops, cs);
 	si_attach_cs(&s->si[1], cs);
 
-	conn->target = s->target = &s->be->obj_type;
+	conn->target = s->target = peer_session_target(peer, s);
+
 	memcpy(&conn->addr.to, &peer->addr, sizeof(conn->addr.to));
 	s->do_log = NULL;
 	s->uniq_id = 0;
diff --git a/src/server.c b/src/server.c
index 28cc741..bc9f178 100644
--- a/src/server.c
+++ b/src/server.c
@@ -1403,7 +1403,7 @@ static void srv_conn_src_cpy(struct server *srv, struct server *src)
  * everything needed.
  */
 #if defined(USE_OPENSSL)
-static void srv_ssl_settings_cpy(struct server *srv, struct server *src)
+void srv_ssl_settings_cpy(struct server *srv, struct server *src)
 {
 	if (src->ssl_ctx.ca_file != NULL)
 		srv->ssl_ctx.ca_file = strdup(src->ssl_ctx.ca_file);
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 8151cb3..11dd8a5 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -96,6 +96,7 @@
 #include <proto/proto_tcp.h>
 #include <proto/proto_http.h>
 #include <proto/server.h>
+#include <proto/peers.h>
 #include <proto/stream_interface.h>
 #include <proto/log.h>
 #include <proto/proxy.h>
@@ -8072,6 +8073,36 @@ static int ssl_parse_default_server_options(char **args, int section_type, struc
 	return 0;
 }
 
+/* parse "crt" keyword in "peers" section.
+ * Returns <0 on alert, >0 on warning, 0 on success.
+ */
+static int peers_parse_crt(char **args, int *cur_arg, struct proxy *px,
+                           struct peers *peers, char **err)
+{
+	int ret;
+
+	if ((ret = srv_parse_crt(args, cur_arg, px, &peers->srv, err)) != 0 ||
+	    (ret = bind_parse_crt(args, *cur_arg, px, &peers->bind_conf, err)) != 0)
+		return ret;
+
+	return 0;
+}
+
+/* parse "ssl" keyword in "peers" section.
+ * Returns <0 on alert, >0 on warning, 0 on success.
+ */
+static int peers_parse_ssl(char **args, int *cur_arg, struct proxy *px,
+                           struct peers *peers, char **err)
+{
+	int ret;
+
+	if ((ret = srv_parse_ssl(args, cur_arg, px, &peers->srv, err)) != 0 ||
+	    (ret = bind_parse_ssl(args, *cur_arg, px, &peers->bind_conf, err)) != 0)
+		return -1;
+
+	return 0;
+}
+
 /* parse the "ca-base" / "crt-base" keywords in global section.
  * Returns <0 on alert, >0 on warning, 0 on success.
  */
@@ -8790,6 +8821,18 @@ static struct srv_kw_list srv_kws = { "SSL", { }, {
 	{ NULL, NULL, 0, 0 },
 }};
 
+/* Note: must not be declared <const> as its list will be overwritten.
+ * Please take care of keeping this list alphabetically sorted, doing so helps
+ * all code contributors.
+ * Optional keywords are also declared with a NULL ->parse() function so that
+ * the config parser can report an appropriate error when a known keyword was
+ * not enabled.
+ */
+static struct peers_kw_list peers_kws = { "SSL", { }, {
+	{ "crt", peers_parse_crt, 1, },
+	{ "ssl", peers_parse_ssl, 0, },
+}};
+
 static struct cfg_kw_list cfg_kws = {ILH, {
 	{ CFG_GLOBAL, "ca-base",  ssl_parse_global_ca_crt_base },
 	{ CFG_GLOBAL, "crt-base", ssl_parse_global_ca_crt_base },
@@ -8911,6 +8954,7 @@ static void __ssl_sock_init(void)
 	acl_register_keywords(&acl_kws);
 	bind_register_keywords(&bind_kws);
 	srv_register_keywords(&srv_kws);
+	peers_register_keywords(&peers_kws);
 	cfg_register_keywords(&cfg_kws);
 	cli_register_kw(&cli_kws);
 #ifndef OPENSSL_NO_ENGINE
-- 
2.1.4

Reply via email to