On 04/11/2018 03:10 PM, Frederic Lecaille wrote:
Hello ML,

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


So, after having discussed about this feature in private here is a more complete patch to support SSL/TLS over peer protocol.

Regards,
Fred.

>From ecf7ddcc431bfc5ebaf151af2027a17bea123f97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20L=C3=A9caille?= <flecai...@haproxy.com>
Date: Tue, 17 Apr 2018 08:42:57 +0200
Subject: [PATCH] MINOR: peers: Add SSL/TLS support.

As all peers belonging to a "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.

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. This is the reason
why these two new structure fields have been also added also to "peer" structure.

The line to setup the peer frontend SSL/TLS binding of a "peers" section are
identified by "ssl-accept" keyword. The remote peers SSL/TLS settings are setup
on "ssl-connect" lines. "ssl-accept" lines support all "bind" SSL/TLS settings
even when irrelevant (but without any impact). "ssl-connect" lines also support
all "server" SSL/TLS settings even if some of them are irrelevant.

Ex:
  # Enable SSL/TLS support for "my_peers" section peers
  peers my_peers
    ssl-accept crt my_certs/pem
    ssl-connect verify none
    peer peerA...
    peer peerB...

or
  global
    ssl-server-verify none

  peers my_peers
    ssl-accept crt my_certs/pem
    peer peerA...
    peer peerB...
---
 doc/configuration.txt  |  40 +++++++++++
 include/proto/peers.h  |  64 +++++++++++++++++
 include/proto/server.h |   1 +
 include/types/peers.h  |  11 +++
 src/cfgparse.c         | 192 +++++++++++++++++++++++++++++++++++++++++--------
 src/peers.c            |   3 +-
 src/server.c           |   2 +-
 7 files changed, 283 insertions(+), 30 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index c687722..755a823 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1851,6 +1851,28 @@ peer <peername> <ip>:<port>
   You may want to reference some environment variables in the address
   parameter, see section 2.3 about environment variables.
 
+ssl-connect [params*]
+  This setting is only available when support for OpenSSL was built in. It
+  enables SSL on connections instantiated from the listener or local peer of
+  this "peers" section to connect to remote peers. All "server" SSL/TLS
+  parameters are supported on this line even if some of them are really not
+  relevant for peer protocol.
+
+  It is not mandatory to set "ssl" as parameter because it is enabled by default.
+
+ssl-accept [params*]
+  This setting is only available when support for OpenSSL was built in. It
+  enables SSL on connections instantiated from the listener or local peer of
+  this "peers" section. A certificate is necessary (see "crt" "bind" option).
+  All "bind" SSL/TLS parameters are supported on this line even if some of
+  them are really not relevant for peer protocol.
+
+  It is not mandatory to set "ssl" as parameter because it is enabled by default.
+
+  When "ssl-accept" is set, it is also not mandatory to set "ssl-connect" to
+  support the basic following SSL/TLS configuration without CA file to verify
+  client certificates (see SSL/TLS example below).
+
   Example:
     peers mypeers
         peer haproxy1 192.168.0.1:1024
@@ -1866,6 +1888,24 @@ peer <peername> <ip>:<port>
         server srv1 192.168.0.30:80
         server srv2 192.168.0.31:80
 
+   SSL/TLS example:
+     peers mypeers
+        ssl-accept crt mycerts/pem
+        ssl-connect verify none
+        peer haproxy1 192.168.0.1:1024
+        peer haproxy2 192.168.0.2:1024
+        peer haproxy3 10.2.0.1:1024
+
+   is equivalent to:
+
+     global
+        ssl-server-verify none
+
+     peers mypeers
+        ssl-accept crt mycerts/pem
+        peer haproxy1 192.168.0.1:1024
+        peer haproxy2 192.168.0.2:1024
+        peer haproxy3 10.2.0.1:1024
 
 3.6. Mailers
 ------------
diff --git a/include/proto/peers.h b/include/proto/peers.h
index 782b66e..32ce3c2 100644
--- a/include/proto/peers.h
+++ b/include/proto/peers.h
@@ -28,6 +28,70 @@
 #include <types/stream.h>
 #include <types/peers.h>
 
+#if defined(USE_OPENSSL)
+#include <proto/server.h>
+
+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
+
 void peers_init_sync(struct peers *peers);
 void peers_register_table(struct peers *, struct stktable *table);
 void peers_setup_frontend(struct proxy *fe);
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..f35c9a4 100644
--- a/include/types/peers.h
+++ b/include/types/peers.h
@@ -68,6 +68,10 @@ struct peer {
 	struct shared_table *last_local_table;
 	struct shared_table *tables;
 	__decl_hathreads(HA_SPINLOCK_T lock); /* lock used to handle this peer section */
+#if defined(USE_OPENSSL)
+	struct server srv;
+	struct bind_conf *bind_conf;  /* Used only if this peer is local */
+#endif
 	struct peer *next;	  /* next peer in the list */
 };
 
@@ -83,12 +87,19 @@ struct peers {
 	struct {
 		const char *file;	 /* file where the section appears */
 		int line;		 /* line where the section appears */
+		char *arg0;
+		char *arg1;
+		char *bind_conf_arg;
 	} 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;
+	struct bind_conf bind_conf;
+#endif
 };
 
 
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 621af6c..1147ccd 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1937,6 +1937,99 @@ 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;
+}
+
+#if defined(USE_OPENSSL)
+static void peers_enable_ssl(struct peers *peers)
+{
+	struct srv_kw *srv_kw;
+	struct bind_kw *bind_kw;
+
+	srv_kw = srv_find_kw("ssl");
+	srv_kw->parse(NULL, NULL, peers->peers_fe, &peers->srv, NULL);
+	bind_kw = bind_find_kw("ssl");
+	bind_kw->parse(NULL, 0, peers->peers_fe, &peers->bind_conf, NULL);
+}
+#endif
+
 /*
  * Parse a line in a <listen>, <frontend> or <backend> section.
  * Returns the error code, 0 if OK, or any combination of :
@@ -1952,8 +2045,6 @@ 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;
 
@@ -2089,32 +2180,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",
@@ -2124,7 +2193,67 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
 				goto out;
 			}
 		}
-	} /* neither "peer" nor "peers" */
+	}
+#if defined(USE_OPENSSL)
+	else if (!strcmp(args[0], "ssl-accept")) {
+		int ret, cur_arg;
+		struct bind_kw *kw;
+
+		cur_arg = 1;
+		peers_enable_ssl(curpeers);
+		while ((kw = bind_find_kw(args[cur_arg]))) {
+			ret = kw->parse(args, cur_arg, curpeers->peers_fe, &curpeers->bind_conf, &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("parsing [%s:%d] : error encountered while processing '%s'\n",
+					         file, linenum, args[cur_arg]);
+				if (ret & ERR_FATAL)
+					goto out;
+			}
+			cur_arg += 1 + kw->skip;
+		}
+		if (*args[cur_arg] != 0) {
+			ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n",
+			         file, linenum, args[cur_arg], cursection);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
+	else if (!strcmp(args[0], "ssl-connect")) {
+		int ret, cur_arg;
+		struct srv_kw *kw;
+
+		cur_arg = 1;
+		peers_enable_ssl(curpeers);
+		while ((kw = srv_find_kw(args[cur_arg]))) {
+			ret = kw->parse(args, &cur_arg, curpeers->peers_fe, &curpeers->srv, &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("parsing [%s:%d] : error encountered while processing '%s'\n",
+					         file, linenum, args[cur_arg]);
+				if (ret & ERR_FATAL)
+					goto out;
+			}
+			cur_arg += 1 + kw->skip;
+		}
+		if (*args[cur_arg] != 0) {
+			ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n",
+			         file, linenum, args[cur_arg], cursection);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
+#endif
 	else if (!strcmp(args[0], "disabled")) {  /* disables this peers section */
 		curpeers->state = PR_STSTOPPED;
 	}
@@ -8891,6 +9020,13 @@ 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..082f865 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>
@@ -1929,7 +1930,7 @@ 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);
-- 
2.1.4

Reply via email to