[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.
This commit is contained in:
Willy Tarreau 2006-07-09 16:42:34 +02:00
parent 2738a14941
commit f3c692090e
7 changed files with 127 additions and 10 deletions

View File

@ -956,6 +956,21 @@ request, 3 other forms help to forge a request :
- 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 @@ Examples :
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

View File

@ -958,6 +958,24 @@ accepte donc 4 formes :
- 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 @@ Exemples :
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

View File

@ -53,6 +53,13 @@ listen appli4-backup 0.0.0.0:10004
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

View File

@ -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 */

View File

@ -114,8 +114,8 @@ struct proxy {
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 */

View File

@ -105,6 +105,36 @@ const char *HTTP_504 =
"\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 @@ int cfg_parse_listen(char *file, int linenum, char **args)
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);
@ -908,6 +939,14 @@ int cfg_parse_listen(char *file, int linenum, char **args)
"%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 */
curproxy->options |= PR_O_PERSIST;
@ -1881,7 +1920,13 @@ int readcfgfile(char *file)
" | 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) {

View File

@ -119,11 +119,19 @@ int event_srv_chk_w(int fd)
}
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 @@ int event_srv_chk_w(int fd)
/*
* 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 @@ int event_srv_chk_r(int fd)
*/
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;
}