[MEDIUM] implement 'option ssl-hello-chk' to use CLIENT HELLO health checks.
This makes it possible to relay SSL connections in pure TCP instances while
ensuring the remote end really receives our data eventhough intermediate
agents (firewalls, proxies, ...) might acknowledge the connection.
diff --git a/doc/haproxy-en.txt b/doc/haproxy-en.txt
index 9530731..14554f2 100644
--- a/doc/haproxy-en.txt
+++ b/doc/haproxy-en.txt
@@ -956,6 +956,21 @@
- option httpchk METH URI -> <METH> <URI> HTTP/1.0
- option httpchk METH URI VER -> <METH> <URI> <VER>
+Some people are using HAProxy to relay various TCP-based protocols such as
+HTTPS, SMTP or LDAP, with the most common one being HTTPS. One problem commonly
+encountered in data centers is the need to forward the traffic to far remote
+servers while providing server fail-over. Often, TCP-only checks are not enough
+because intermediate firewalls, load balancers or proxies might acknowledge the
+connection before it reaches the real server. The only solution to this problem
+is to send application-level health checks. Since the demand for HTTPS checks
+is high, it has been implemented in 1.2.15 based on SSLv3 Client Hello packets.
+To enable it, use 'option ssl-hello-chk'. It will send SSL CLIENT HELLO packets
+to the servers, announcing support for most common cipher suites. If the server
+responds what looks like a SERVER HELLO or an ALERT (refuses the ciphers) then
+the response is considered as valid. Note that Apache does not generate a log
+when it receives only an HELLO message, which makes this type of message
+perfectly suit this need.
+
See examples below.
Since version 1.1.17, it is possible to specify backup servers. These servers
@@ -1068,6 +1083,15 @@
server srv1 192.168.1.1 check port 25 inter 30000 rise 1 fall 2
server srv2 192.168.1.2 backup
+# HTTPS relaying with health-checks and backup servers
+
+ listen http_proxy :443
+ mode tcp
+ option ssl-hello-chk
+ balance roundrobin
+ server srv1 192.168.1.1 check inter 30000 rise 1 fall 2
+ server srv2 192.168.1.2 backup
+
# Load-balancing using a backup pool (requires haproxy 1.2.9)
listen http_proxy 0.0.0.0:80
mode http
diff --git a/doc/haproxy-fr.txt b/doc/haproxy-fr.txt
index fd4dc1a..dd02891 100644
--- a/doc/haproxy-fr.txt
+++ b/doc/haproxy-fr.txt
@@ -958,6 +958,24 @@
- option httpchk METH URI -> <METH> <URI> HTTP/1.0
- option httpchk METH URI VER -> <METH> <URI> <VER>
+HAProxy est souvent utilisé pour relayer divers protocoles reposant sur TCP,
+tels que HTTPS, SMTP ou LDAP, le plus commun étant HTTPS. Un problème assez
+couramment rencontré dans les data centers est le besoin de relayer du trafic
+vers des serveurs lointains tout en maintenant la possibilité de basculer sur
+un serveur de secours. Les tests purement TCP ne suffisent pas toujours dans
+ces situations car l'on trouve souvent, dans la chaîne, des proxies, firewalls
+ou répartiteurs de charge qui peuvent acquitter la connexion avant qu'elle
+n'atteigne le serveur. La seule solution à ce problème est d'envoyer des tests
+applicatifs. Comme la demande pour les tests HTTPS est élevée, ce test a été
+implémenté en version 1.2.15 sur la base de messages SSLv3 CLIENT HELLO. Pour
+l'activer, utiliser "option ssl-hello-chk". Ceci enverra des messages SSLv3
+CLIENT HELLO aux serveurs, en annonçant un support pour la majorité des
+algorithmes de chiffrement. Si en retour, le serveur envoie ce qui ressemble à
+une réponse SSLv3 SERVER HELLO ou ALERT (refus des algorithmes), alors la
+réponse sera considérée comme valide. Noter qu'Apache ne produit pas de log
+lorsqu'il reçoit des messages HELLO, ce qui en fait un type de message
+parfaitement adapté à ce besoin.
+
Voir les exemples ci-après.
Depuis la version 1.1.17, il est possible de définir des serveurs de secours,
@@ -1078,6 +1096,15 @@
server srv1 192.168.1.1 check port 25 inter 30000 rise 1 fall 2
server srv2 192.168.1.2 backup
+# relayage HTTPS avec test du serveur et serveur de backup
+
+ listen http_proxy :443
+ mode tcp
+ option ssl-hello-chk
+ balance roundrobin
+ server srv1 192.168.1.1 check inter 30000 rise 1 fall 2
+ server srv2 192.168.1.2 backup
+
# Utilisation d'un groupe de serveurs pour le backup (nécessite haproxy 1.2.9)
listen http_proxy 0.0.0.0:80
mode http
diff --git a/examples/haproxy.cfg b/examples/haproxy.cfg
index ae72150..1add7ff 100644
--- a/examples/haproxy.cfg
+++ b/examples/haproxy.cfg
@@ -53,6 +53,13 @@
server inst1 192.168.114.56:80 check inter 2000 fall 3
server inst2 192.168.114.56:81 check inter 2000 fall 3 backup
+listen ssl-relay 0.0.0.0:8443
+ option ssl-hello-chk
+ balance source
+ server inst1 192.168.110.56:443 check inter 2000 fall 3
+ server inst2 192.168.110.57:443 check inter 2000 fall 3
+ server back1 192.168.120.58:443 backup
+
listen appli5-backup 0.0.0.0:10005
option httpchk *
balance roundrobin
diff --git a/include/types/backend.h b/include/types/backend.h
index 9fa179f..a6a393a 100644
--- a/include/types/backend.h
+++ b/include/types/backend.h
@@ -51,6 +51,7 @@
#define PR_O_BALANCE_SH 0x00400000 /* balance on source IP hash */
#define PR_O_BALANCE (PR_O_BALANCE_RR | PR_O_BALANCE_SH)
#define PR_O_ABRT_CLOSE 0x00800000 /* immediately abort request when client closes */
+#define PR_O_SSL3_CHK 0x01000000 /* use SSLv3 CLIENT_HELLO packets for server health */
#endif /* _TYPES_BACKEND_H */
diff --git a/include/types/proxy.h b/include/types/proxy.h
index 306c31d..50c16ec 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -114,8 +114,8 @@
void *req_cap_pool, *rsp_cap_pool; /* pools of pre-allocated char ** used to build the sessions */
char *req_add[MAX_NEWHDR], *rsp_add[MAX_NEWHDR]; /* headers to be added */
int grace; /* grace time after stop request */
- char *check_req; /* HTTP request to use if PR_O_HTTP_CHK is set, else NULL */
- int check_len; /* Length of the HTTP request */
+ char *check_req; /* HTTP or SSL request to use for PR_O_HTTP_CHK|PR_O_SSL3_CHK */
+ int check_len; /* Length of the HTTP or SSL3 request */
struct {
char *msg400; /* message for error 400 */
int len400; /* message length for error 400 */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 091f6d9..5e4aa51 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -105,6 +105,36 @@
"\r\n"
"<html><body><h1>504 Gateway Time-out</h1>\nThe server didn't respond in time.\n</body></html>\n";
+/* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
+ * ssl-hello-chk option to ensure that the remote server speaks SSL.
+ *
+ * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
+ */
+const char sslv3_client_hello_pkt[] = {
+ "\x16" /* ContentType : 0x16 = Hanshake */
+ "\x03\x00" /* ProtocolVersion : 0x0300 = SSLv3 */
+ "\x00\x79" /* ContentLength : 0x79 bytes after this one */
+ "\x01" /* HanshakeType : 0x01 = CLIENT HELLO */
+ "\x00\x00\x75" /* HandshakeLength : 0x75 bytes after this one */
+ "\x03\x00" /* Hello Version : 0x0300 = v3 */
+ "\x00\x00\x00\x00" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
+ "HAPROXYSSLCHK\nHAPROXYSSLCHK\n" /* Random : must be exactly 28 bytes */
+ "\x00" /* Session ID length : empty (no session ID) */
+ "\x00\x4E" /* Cipher Suite Length : 78 bytes after this one */
+ "\x00\x01" "\x00\x02" "\x00\x03" "\x00\x04" /* 39 most common ciphers : */
+ "\x00\x05" "\x00\x06" "\x00\x07" "\x00\x08" /* 0x01...0x1B, 0x2F...0x3A */
+ "\x00\x09" "\x00\x0A" "\x00\x0B" "\x00\x0C" /* This covers RSA/DH, */
+ "\x00\x0D" "\x00\x0E" "\x00\x0F" "\x00\x10" /* various bit lengths, */
+ "\x00\x11" "\x00\x12" "\x00\x13" "\x00\x14" /* SHA1/MD5, DES/3DES/AES... */
+ "\x00\x15" "\x00\x16" "\x00\x17" "\x00\x18"
+ "\x00\x19" "\x00\x1A" "\x00\x1B" "\x00\x2F"
+ "\x00\x30" "\x00\x31" "\x00\x32" "\x00\x33"
+ "\x00\x34" "\x00\x35" "\x00\x36" "\x00\x37"
+ "\x00\x38" "\x00\x39" "\x00\x3A"
+ "\x01" /* Compression Length : 0x01 = 1 byte for types */
+ "\x00" /* Compression Type : 0x00 = NULL compression */
+};
+
static struct proxy defproxy; /* fake proxy used to assign default values on all instances */
int cfg_maxpconn = DEFAULT_MAXCONN; /* # of simultaneous connections per proxy (-N) */
@@ -888,6 +918,7 @@
free(curproxy->check_req);
}
curproxy->options |= PR_O_HTTP_CHK;
+ curproxy->options &= ~PR_O_SSL3_CHK;
if (!*args[2]) { /* no argument */
curproxy->check_req = strdup(DEF_CHECK_REQ); /* default request */
curproxy->check_len = strlen(DEF_CHECK_REQ);
@@ -907,6 +938,14 @@
curproxy->check_len = snprintf(curproxy->check_req, reqlen,
"%s %s %s\r\n\r\n", args[2], args[3], *args[4]?args[4]:"HTTP/1.0");
}
+ }
+ else if (!strcmp(args[1], "ssl-hello-chk")) {
+ /* use SSLv3 CLIENT HELLO to check servers' health */
+ if (curproxy->check_req != NULL) {
+ free(curproxy->check_req);
+ }
+ curproxy->options &= ~PR_O_HTTP_CHK;
+ curproxy->options |= PR_O_SSL3_CHK;
}
else if (!strcmp(args[1], "persist")) {
/* persist on using the server specified by the cookie, even when it's down */
@@ -1881,7 +1920,13 @@
" | values are set to a non-zero value: clitimeout, contimeout, srvtimeout.\n",
file, curproxy->id);
}
-
+
+ if (curproxy->options & PR_O_SSL3_CHK) {
+ curproxy->check_len = sizeof(sslv3_client_hello_pkt);
+ curproxy->check_req = (char *)malloc(sizeof(sslv3_client_hello_pkt));
+ memcpy(curproxy->check_req, sslv3_client_hello_pkt, sizeof(sslv3_client_hello_pkt));
+ }
+
/* first, we will invert the servers list order */
newsrv = NULL;
while (curproxy->srv) {
diff --git a/src/checks.c b/src/checks.c
index fd0f106..817afc2 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -119,11 +119,19 @@
}
else if (s->result != -1) {
/* we don't want to mark 'UP' a server on which we detected an error earlier */
- if (s->proxy->options & PR_O_HTTP_CHK) {
+ if ((s->proxy->options & PR_O_HTTP_CHK) ||
+ (s->proxy->options & PR_O_SSL3_CHK)) {
int ret;
- /* we want to check if this host replies to "OPTIONS / HTTP/1.0"
+ /* we want to check if this host replies to HTTP or SSLv3 requests
* so we'll send the request, and won't wake the checker up now.
*/
+
+ if (s->proxy->options & PR_O_SSL3_CHK) {
+ /* SSL requires that we put Unix time in the request */
+ int gmt_time = htonl(now.tv_sec);
+ memcpy(s->proxy->check_req + 11, &gmt_time, 4);
+ }
+
#ifndef MSG_NOSIGNAL
ret = send(fd, s->proxy->check_req, s->proxy->check_len, MSG_DONTWAIT);
#else
@@ -151,9 +159,12 @@
/*
- * This function is used only for server health-checks. It handles
- * the server's reply to an HTTP request. It returns 1 if the server replies
- * 2xx or 3xx (valid responses), or -1 in other cases.
+ * This function is used only for server health-checks. It handles the server's
+ * reply to an HTTP request or SSL HELLO. It returns 1 in s->result if the
+ * server replies HTTP 2xx or 3xx (valid responses), or if it returns at least
+ * 5 bytes in response to SSL HELLO. The principle is that this is enough to
+ * distinguish between an SSL server and a pure TCP relay. All other cases will
+ * return -1. The function returns 0.
*/
int event_srv_chk_r(int fd)
{
@@ -177,10 +188,12 @@
*/
len = recv(fd, reply, sizeof(reply), MSG_NOSIGNAL);
#endif
-
- if ((len >= sizeof("HTTP/1.0 000")) &&
+ if (((s->proxy->options & PR_O_HTTP_CHK) &&
+ (len >= sizeof("HTTP/1.0 000")) &&
!memcmp(reply, "HTTP/1.", 7) &&
(reply[9] == '2' || reply[9] == '3')) /* 2xx or 3xx */
+ || ((s->proxy->options & PR_O_SSL3_CHK) && (len >= 5) &&
+ (reply[0] == 0x15 || reply[0] == 0x16))) /* alert or handshake */
result = 1;
}