Hi,

I've slightly updated my patch to improve it and to fix some
inconsistencies.

First of all, now "ssl-upgrade" and "no-ssl-upgrade" actions can be used
on "tcp-request content" rules _AND_ "tcp-request connection" rules, in
a frontend _OR_ a backend definition.

Then, these actions are now custom actions. I think this is cleaner this
way.

And finally, by default, no SSL upgrade is done when "defer-ssl-upgrade"
option is used. So you need to use explicitly a "ssl-upgrade" rule to
perform it. For a lack of finding the right place to do SSL upgrades
when no "tcp-request" rule is defined, I've decided to change the
default behavior. I've kept the "defer-ssl-upgrade" keyword, but now,
"skip-ssl-upgrade" could be more appropriate. If you prefer, i can do
the change.

-- 
Christopher
>From 05c439bfcb39ae297c1d1652f8e4fd72850ff775 Mon Sep 17 00:00:00 2001
From: Christopher Faulet <[email protected]>
Date: Thu, 3 Mar 2016 16:15:28 +0100
Subject: [PATCH] MAJOR: ssl: Allow the postpone of the SSL upgrade and a way
 to enable/disable it

the bind option "defer-ssl-upgrade" has been added. It can be used iff "ssl"
option is set. It postpone the SSL upgrade when evaluation of "tcp-request"
rules.

2 actions have been added in "tcp-request connection/content" rules to
conditionally enable/disable this upgrade:

  * ssl-upgrade
  * no-ssl-upgrade

The "ssl-upgrade" is used to upgrade a TCP connection to a SSL connection and
"no-ssl-upgrade" is used to disable it. This is only available for connections
opened on a SSL listener configured with the "defer-ssl-upgrade" option and
ignored for all others. When a "ssl-upgrade" rule matches, it invalidates
"no-ssl-upgrade" rules that follow, and conversely. if neither the "ssl-upgrade"
rules nor the "no-ssl-upgrade" rules matchn, no SSL upgrade is performed (this
is the default behavior).

Here are two examples:

  # Do the SSL connection upgrade only if the SNI field is set to "example.com"
  frontend l
      mode tcp
      bind *:1234 ssl crt /path/to/srv.pem defer-ssl-upgrade

      tcp-request inspect-delay 5s
      tcp-request content ssl-upgrade if { req.ssl_sni -i example.com }
      # by default no SSL upgrade is performed

      use_backend     offloaded-ssl if ssl_fc
      default_backend raw-ssl

  # Accept SSL and non-SSL connctions on the same listener
  frontend l
      mode tcp
      bind *:1234 ssl crt /path/to/srv.pem defer-ssl-upgrade

      tcp-request inspect-delay 5s
      tcp-request content ssl-upgrade { req.ssl_ver gt 0 }

      use_backend     ssl if ssl_fc
      default_backend tcp

This feature is based on the work of PiBa-NL <[email protected]>
---
 doc/configuration.txt      | 36 ++++++++++++++++-
 include/proto/ssl_sock.h   |  1 +
 include/types/connection.h |  4 +-
 include/types/listener.h   | 25 ++++++------
 src/listener.c             |  7 +++-
 src/proto_tcp.c            | 99 ++++++++++++++++++++++++++++++++++++++++++++--
 src/session.c              |  6 +++
 src/ssl_sock.c             | 71 +++++++++++++++++++++++++++++++++
 8 files changed, 230 insertions(+), 19 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 279d076..d490afa 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -8534,7 +8534,7 @@ tcp-request connection <action> [{if | unless} <condition>]
   accept the incoming connection. There is no specific limit to the number of
   rules which may be inserted.
 
-  Four types of actions are supported :
+  Following actions are supported :
     - accept :
         accepts the connection if the condition is true (when used with "if")
         or false (when used with "unless"). The first such rule executed ends
@@ -8646,6 +8646,19 @@ tcp-request connection <action> [{if | unless} <condition>]
         TCP reset doesn't pass the first router, though it's still delivered to
         local networks. Do not use it unless you fully understand how it works.
 
+    - "ssl-upgrade" :
+        When SSL upgrade of a TCP connection was postponed, this action
+        performs it. This is only available for connections opened on a SSL
+        listener configured with the "defer-ssl-upgrade" option and ignored for
+        all others. The next "ssl-upgrade" and "no-ssl-upgrade" rules will have
+        no effect.  See also the bind option "defer-ssl-upgrade".
+
+    - "no-ssl-upgrade" :
+        This does the opposite of the previous action. It explicitly disables
+        any postponed SSL upgrade. The next "ssl-upgrade" and "no-ssl-upgrade"
+        rules will have no effect. See also the bind option "defer-ssl-upgrade".
+
+
   Note that the "if/unless" condition is optional. If no condition is set on
   the action, it is simply performed unconditionally. That can be useful for
   "track-sc*" actions as well as for changing the default action to a reject.
@@ -8718,6 +8731,8 @@ tcp-request content <action> [{if | unless} <condition>]
     - set-gpt0(<sc-id>) <int>
     - set-var(<var-name>) <expr>
     - silent-drop
+    - ssl-upgrade
+    - no-ssl-upgrade
 
   They have the same meaning as their counter-parts in "tcp-request connection"
   so please refer to that section for a complete description.
@@ -8820,6 +8835,18 @@ tcp-request content <action> [{if | unless} <condition>]
             tcp-request content track-sc1 src
             tcp-request content reject if click_too_fast mark_as_abuser
 
+  Example:
+        # Do the SSL connection upgrade only if the SNI field is set
+        # to "example.com"
+        tcp-request inspect-delay 5s
+        tcp-request content ssl-upgrade if { req.ssl_sni -i example.com }
+        # no SSL upgrade is done if SNI field does not match
+
+  Example:
+        # Handle SSL and non-SSL connctions on the same listener
+        tcp-request inspect-delay 5s
+        tcp-request content ssl-upgrade if { req.ssl_ver gt 0 }
+
   See section 7 about ACL usage.
 
   See also : "tcp-request connection", "tcp-request inspect-delay"
@@ -9797,6 +9824,13 @@ defer-accept
   an established connection while the proxy will only see it in SYN_RECV. This
   option is only supported on TCPv4/TCPv6 sockets and ignored by other ones.
 
+defer-ssl-upgrade
+ This setting is only available when support for OpenSSL was built in. It
+ postpones the SSL upgrade of TCP connexions when evaluation of "tcp-request"
+ rules. SSL upgrade is performed only if a "ssl-upgrade" rule matches. So by
+ default, there is no SSL upgrade.
+ See also "tcp-request connection" and"tcp-request content".
+
 force-sslv3
   This option enforces use of SSLv3 only on SSL connections instantiated from
   this listener. SSLv3 is generally less expensive than the TLS counterparts
diff --git a/include/proto/ssl_sock.h b/include/proto/ssl_sock.h
index cb9a1e9..152aa6e 100644
--- a/include/proto/ssl_sock.h
+++ b/include/proto/ssl_sock.h
@@ -43,6 +43,7 @@ int ssl_sock_is_ssl(struct connection *conn)
 }
 
 int ssl_sock_handshake(struct connection *conn, unsigned int flag);
+int ssl_sock_upgrade(struct connection *conn, struct buffer *buf);
 int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, SSL_CTX *ctx, struct proxy *proxy);
 int ssl_sock_prepare_all_ctx(struct bind_conf *bind_conf, struct proxy *px);
 int ssl_sock_prepare_srv_ctx(struct server *srv, struct proxy *px);
diff --git a/include/types/connection.h b/include/types/connection.h
index dfbff6a..308abb7 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -107,7 +107,8 @@ enum {
 	CO_FL_SEND_PROXY    = 0x01000000,  /* send a valid PROXY protocol header */
 	CO_FL_SSL_WAIT_HS   = 0x02000000,  /* wait for an SSL handshake to complete */
 	CO_FL_ACCEPT_PROXY  = 0x04000000,  /* receive a valid PROXY protocol header */
-	/* unused : 0x08000000 */
+
+	CO_FL_DEF_SSL_UPG   = 0x08000000,  /* postpone the ssl upgrade of the connection */
 
 	/* below we have all handshake flags grouped into one */
 	CO_FL_HANDSHAKE     = CO_FL_SEND_PROXY | CO_FL_SSL_WAIT_HS | CO_FL_ACCEPT_PROXY,
@@ -263,6 +264,7 @@ struct connection {
 			int fd;       /* file descriptor for a stream driver when known */
 		} sock;
 	} t;
+
 	enum obj_type *target;        /* the target to connect to (server, proxy, applet, ...) */
 	struct list list;             /* attach point to various connection lists (idle, ...) */
 	const struct netns_entry *proxy_netns;
diff --git a/include/types/listener.h b/include/types/listener.h
index 4da6cac..5249bd9 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -80,18 +80,19 @@ enum li_state {
  */
 
 /* listener socket options */
-#define LI_O_NONE	0x0000
-#define LI_O_NOLINGER	0x0001	/* disable linger on this socket */
-#define LI_O_FOREIGN	0x0002	/* permit listening on foreing addresses */
-#define LI_O_NOQUICKACK	0x0004	/* disable quick ack of immediate data (linux) */
-#define LI_O_DEF_ACCEPT	0x0008	/* wait up to 1 second for data before accepting */
-#define LI_O_TCP_RULES  0x0010  /* run TCP rules checks on the incoming connection */
-#define LI_O_CHK_MONNET 0x0020  /* check the source against a monitor-net rule */
-#define LI_O_ACC_PROXY  0x0040  /* find the proxied address in the first request line */
-#define LI_O_UNLIMITED  0x0080  /* listener not subject to global limits (peers & stats socket) */
-#define LI_O_TCP_FO     0x0100  /* enable TCP Fast Open (linux >= 3.7) */
-#define LI_O_V6ONLY     0x0200  /* bind to IPv6 only on Linux >= 2.4.21 */
-#define LI_O_V4V6       0x0400  /* bind to IPv4/IPv6 on Linux >= 2.4.21 */
+#define LI_O_NONE        0x0000
+#define LI_O_NOLINGER    0x0001  /* disable linger on this socket */
+#define LI_O_FOREIGN     0x0002  /* permit listening on foreing addresses */
+#define LI_O_NOQUICKACK  0x0004  /* disable quick ack of immediate data (linux) */
+#define LI_O_DEF_ACCEPT  0x0008  /* wait up to 1 second for data before accepting */
+#define LI_O_TCP_RULES   0x0010  /* run TCP rules checks on the incoming connection */
+#define LI_O_CHK_MONNET  0x0020  /* check the source against a monitor-net rule */
+#define LI_O_ACC_PROXY   0x0040  /* find the proxied address in the first request line */
+#define LI_O_UNLIMITED   0x0080  /* listener not subject to global limits (peers & stats socket) */
+#define LI_O_TCP_FO      0x0100  /* enable TCP Fast Open (linux >= 3.7) */
+#define LI_O_V6ONLY      0x0200  /* bind to IPv6 only on Linux >= 2.4.21 */
+#define LI_O_V4V6        0x0400  /* bind to IPv4/IPv6 on Linux >= 2.4.21 */
+#define LI_O_DEF_SSL_UPG 0x0800  /* postpone SSL upgrade of the connection */
 
 /* Note: if a listener uses LI_O_UNLIMITED, it is highly recommended that it adds its own
  * maxconn setting to the global.maxsock value so that its resources are reserved.
diff --git a/src/listener.c b/src/listener.c
index 3759c78..07594a9 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -495,8 +495,11 @@ void listener_accept(int fd)
 				global.sps_max = global.sess_per_sec.curr_ctr;
 		}
 #ifdef USE_OPENSSL
-		if (!(l->options & LI_O_UNLIMITED) && l->bind_conf && l->bind_conf->is_ssl) {
-
+		/* if SSL upgrade is postponed, we do no update ssl/sec
+		 * frequency now */
+		if (!(l->options & LI_O_DEF_SSL_UPG) &&
+		    !(l->options & LI_O_UNLIMITED)   &&
+		    l->bind_conf && l->bind_conf->is_ssl) {
 			update_freq_ctr(&global.ssl_per_sec, 1);
 			if (global.ssl_per_sec.curr_ctr > global.ssl_max)
 				global.ssl_max = global.ssl_per_sec.curr_ctr;
diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index cce0acb..fae5d29 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -61,6 +61,11 @@
 #include <proto/stream_interface.h>
 #include <proto/task.h>
 
+#ifdef USE_OPENSSL
+#include <proto/ssl_sock.h>
+#endif /*USE_OPENSSL */
+
+
 static int tcp_bind_listeners(struct protocol *proto, char *errmsg, int errlen);
 static int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen);
 
@@ -1099,7 +1104,6 @@ int tcp_inspect_request(struct stream *s, struct channel *req, int an_bit)
 			if (rule->cond->pol == ACL_COND_UNLESS)
 				ret = !ret;
 		}
-
 		if (ret) {
 			act_flags |= ACT_FLAG_FIRST;
 resume_execution:
@@ -1491,6 +1495,45 @@ static enum act_return tcp_exec_action_silent_drop(struct act_rule *rule, struct
 	return ACT_RET_STOP;
 }
 
+/* Executes the "ssl-upgrade" action. May be called from tcp-request */
+static enum act_return tcp_exec_action_ssl_upgrade(struct act_rule *rule, struct proxy *px,
+						   struct session *sess, struct stream *strm, int flags)
+{
+	struct connection *conn = objt_conn(sess->origin);
+	enum act_return ret = ACT_RET_CONT;
+
+#ifdef USE_OPENSSL
+	if (!conn)
+		goto out;
+	if (!(conn->flags & CO_FL_DEF_SSL_UPG))
+		goto out;
+
+	ret = ACT_RET_ERR;
+	if (strm) {
+		if (ssl_sock_upgrade(conn, strm->req.buf))
+			ret = ACT_RET_YIELD;
+	}
+	else {
+		if (ssl_sock_upgrade(conn, NULL))
+			ret = ACT_RET_CONT;
+	}
+ out:
+#endif /* USE_OPENSSL */
+	return ret;
+}
+
+/* Executes the "no-ssl-upgrade" action. May be called from tcp-request */
+static enum act_return tcp_exec_action_no_ssl_upgrade(struct act_rule *rule, struct proxy *px,
+						      struct session *sess, struct stream *strm, int flags)
+{
+	struct connection *conn = objt_conn(sess->origin);
+
+	if (conn)
+		conn->flags &= ~CO_FL_DEF_SSL_UPG;
+	return ACT_RET_CONT;
+}
+
+
 /* Parse a tcp-response rule. Return a negative value in case of failure */
 static int tcp_parse_response_rule(char **args, int arg, int section_type,
                                    struct proxy *curpx, struct proxy *defpx,
@@ -2044,6 +2087,52 @@ static enum act_parse_ret tcp_parse_silent_drop(const char **args, int *orig_arg
 	return ACT_RET_PRS_OK;
 }
 
+/* Parse a "ssl-upgrade" action. It takes no argument. It returns ACT_RET_PRS_OK
+ * on success, ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret tcp_parse_ssl_upgrade(const char **args, int *orig_arg, struct proxy *px,
+                                                struct act_rule *rule, char **err)
+{
+#ifdef USE_OPENSSL
+	if (px->mode == PR_MODE_HTTP) {
+		memprintf(err,
+			  "'%s %s %s' : invalid rule as it requires TCP mode",
+			  args[0], args[1], args[2]);
+		return ACT_RET_PRS_ERR;
+	}
+
+	rule->action = ACT_CUSTOM;
+	rule->action_ptr = tcp_exec_action_ssl_upgrade;
+	return ACT_RET_PRS_OK;
+#else
+	memprintf(err, "'%s %s %s' : not implemented", args[0], args[1], args[2]);
+	return ACT_RET_PRS_ERR;
+#endif
+}
+
+/* Parse a "no-ssl-upgrade" action. It takes no argument. It returns
+ * ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
+ */
+static enum act_parse_ret tcp_parse_no_ssl_upgrade(const char **args, int *orig_arg, struct proxy *px,
+						   struct act_rule *rule, char **err)
+{
+#ifdef USE_OPENSSL
+	if (px->mode == PR_MODE_HTTP) {
+		memprintf(err,
+			  "'%s %s %s' : invalid rule as it requires TCP mode",
+			  args[0], args[1], args[2]);
+		return ACT_RET_PRS_ERR;
+	}
+
+	rule->action = ACT_CUSTOM;
+	rule->action_ptr = tcp_exec_action_no_ssl_upgrade;
+	return ACT_RET_PRS_OK;
+#else
+	memprintf(err, "'%s %s %s' : not implemented", args[0], args[1], args[2]);
+	return ACT_RET_PRS_ERR;
+#endif
+}
+
 
 /************************************************************************/
 /*       All supported sample fetch functions must be declared here     */
@@ -2423,12 +2512,16 @@ static struct srv_kw_list srv_kws = { "TCP", { }, {
 }};
 
 static struct action_kw_list tcp_req_conn_actions = {ILH, {
-	{ "silent-drop", tcp_parse_silent_drop },
+	{ "silent-drop",    tcp_parse_silent_drop },
+	{ "ssl-upgrade",    tcp_parse_ssl_upgrade },
+	{ "no-ssl-upgrade", tcp_parse_no_ssl_upgrade },
 	{ /* END */ }
 }};
 
 static struct action_kw_list tcp_req_cont_actions = {ILH, {
-	{ "silent-drop", tcp_parse_silent_drop },
+	{ "silent-drop",    tcp_parse_silent_drop },
+	{ "ssl-upgrade",    tcp_parse_ssl_upgrade },
+	{ "no-ssl-upgrade", tcp_parse_no_ssl_upgrade },
 	{ /* END */ }
 }};
 
diff --git a/src/session.c b/src/session.c
index fdb2404..f80a0f9 100644
--- a/src/session.c
+++ b/src/session.c
@@ -136,6 +136,12 @@ int session_accept_fd(struct listener *l, int cfd, struct sockaddr_storage *addr
 
 	conn_ctrl_init(cli_conn);
 
+	/* postpone ssl upgrade of the connection */
+	if (l->options & LI_O_DEF_SSL_UPG) {
+		cli_conn->flags &= ~CO_FL_SSL_WAIT_HS;
+		cli_conn->flags |= CO_FL_DEF_SSL_UPG;
+	}
+
 	/* wait for a PROXY protocol header */
 	if (l->options & LI_O_ACC_PROXY) {
 		cli_conn->flags |= CO_FL_ACCEPT_PROXY;
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 1017388..11bfdf2 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -83,9 +83,11 @@
 #include <proto/server.h>
 #include <proto/log.h>
 #include <proto/proxy.h>
+#include <proto/raw_sock.h>
 #include <proto/shctx.h>
 #include <proto/ssl_sock.h>
 #include <proto/stream.h>
+#include <proto/stream_interface.h>
 #include <proto/task.h>
 
 /* Warning, these are bits, not integers! */
@@ -3547,6 +3549,56 @@ reneg_ok:
 	return 0;
 }
 
+static long ssl_sock_upgrade_callback(BIO *bio, int cmd, const char *argp,
+				      int argi, long argl, long ret)
+{
+	if ((cmd & (BIO_CB_READ|BIO_CB_RETURN)) == (BIO_CB_READ|BIO_CB_RETURN)) {
+		struct connection *conn = (struct connection *)BIO_get_callback_arg(bio);
+		if (ret >= 0) {
+			struct buffer *buf = si_ic(conn->owner)->buf;
+			bi_fast_delete(buf, ret);
+			if (buf->i == 0)
+				SSL_set_fd(conn->xprt_ctx, conn->t.sock.fd);
+		}
+	}
+	return ret;
+}
+
+int ssl_sock_upgrade(struct connection *conn, struct buffer *buf)
+{
+	BIO *mbio;
+
+	conn->xprt = &ssl_sock;
+	conn->flags &= ~CO_FL_XPRT_READY;
+	conn_sock_want_recv(conn);
+	conn_data_want_recv(conn);
+	if (conn_xprt_init(conn) < 0)  {
+		conn->flags |= CO_FL_ERROR;
+		return 0;
+	}
+
+	if (objt_listener(conn->target) &&
+	    !(objt_listener(conn->target)->options & LI_O_UNLIMITED)) {
+		update_freq_ctr(&global.ssl_per_sec, 1);
+		if (global.ssl_per_sec.curr_ctr > global.ssl_max)
+			global.ssl_max = global.ssl_per_sec.curr_ctr;
+	}
+
+	conn->flags |= (CO_FL_SSL_WAIT_HS | CO_FL_WAIT_L6_CONN);
+	conn->flags &= ~(CO_FL_CONNECTED|CO_FL_DEF_SSL_UPG);
+
+	if (buf && buf->i) {
+		mbio = BIO_new_mem_buf(buf->p, buf->i);
+		SSL_set_bio(conn->xprt_ctx, mbio, NULL);
+		SSL_set_wfd(conn->xprt_ctx, conn->t.sock.fd);
+		BIO_set_callback(mbio, ssl_sock_upgrade_callback);
+		BIO_set_callback_arg(mbio, (char *)conn);
+	}
+	fd_may_recv(conn->t.sock.fd);
+
+	return 1;
+}
+
 /* Receive up to <count> bytes from connection <conn>'s socket and store them
  * into buffer <buf>. Only one call to recv() is performed, unless the
  * buffer wraps, in which case a second call may be performed. The connection's
@@ -5446,6 +5498,24 @@ static int bind_parse_verify(char **args, int cur_arg, struct proxy *px, struct
 	return 0;
 }
 
+/* parse the "defer-ssl-upgrade" bind keyword */
+static int bind_parse_def_ssl_upg(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
+{
+	struct listener *l;
+
+	if (!conf->is_ssl) {
+		if (err)
+			memprintf(err, "'%s' : must be declared after 'ssl' keyword", args[cur_arg]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+
+	list_for_each_entry(l, &conf->listeners, by_bind) {
+		l->xprt = &raw_sock;
+		l->options |= LI_O_DEF_SSL_UPG;
+	}
+	return 0;
+}
+
 /************** "server" keywords ****************/
 
 /* parse the "ca-file" server keyword */
@@ -5892,6 +5962,7 @@ static struct bind_kw_list bind_kws = { "SSL", { }, {
 	{ "crt",                   bind_parse_crt,             1 }, /* load SSL certificates from this location */
 	{ "crt-ignore-err",        bind_parse_ignore_err,      1 }, /* set error IDs to ingore on verify depth == 0 */
 	{ "crt-list",              bind_parse_crt_list,        1 }, /* load a list of crt from this location */
+	{ "defer-ssl-upgrade",     bind_parse_def_ssl_upg,     0 }, /* Defer the SSL connection upgrade */
 	{ "ecdhe",                 bind_parse_ecdhe,           1 }, /* defines named curve for elliptic curve Diffie-Hellman */
 	{ "force-sslv3",           bind_parse_force_sslv3,     0 }, /* force SSLv3 */
 	{ "force-tlsv10",          bind_parse_force_tlsv10,    0 }, /* force TLSv10 */
-- 
2.5.0

Reply via email to