Haproxy is a pretty nifty product. Probably not the least due to the fact that it’s author, Willy Tarreau spends hours of his life helping others in setting it up the way they want, sometimes fixing a bug in the process. The latest release marks the end to a 4-year long bugfix that finally went live. It is this sort of things, features that have been wanted for years, now incorporated into the software helping sysadmins and programmers around the world in fulfilling corporate IT needs. Haprpxy to me is more than a loadbalancer, you can piece together solutions as a workaround for more fundamental problems.
That’s what I recently did. A case of a failing network (NAT problems) that wasn’t easily solvable due to the need of having a box around with 2 patched network cards. NAT using the same interface is just not working right. We just needed some outgoing SSL calls to go to the internet and back, through some portforwarded upstream firewall setup. That setup sucks by being limited to only having a private IP range, we have public IP’s that map to the internal one but that is a firewall hell as packets arrive at the port from a range that doesn’t really belong to the network cards IP range.
So in comes HAPROXY, let’s reverse the thinking and consider putting haproxy ‘in front’ of those SSL requests at port 443. We will just hack up the /etc/hosts file or the local DNS to make sure they endup on haproxy instead of directly to the target public IP:
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
#log loghost local0 info
maxconn 4096
# chroot /usr/share/haproxy
user haproxy
group haproxy
daemon
#debug
#quiet
defaults
log global
mode http
option httplog
option dontlognull
retries 3
option redispatch
maxconn 2000
contimeout 5000
clitimeout 50000
srvtimeout 50000
# Host HA-Proxy web stats on Port 3306 (that will confuse those script kiddies)
listen HAProxy-Statistics *:3306
mode http
option httplog
option httpclose
stats enable
stats uri /haproxy?stats
stats refresh 20s
stats show-node
stats show-legends
stats show-desc Workaround haproxy for SSL
stats auth admin:ifIruledTheWorld
frontend ssl_relay 192.168.128.21:443
# this only works with 1.5 haproxy
mode tcp
option tcplog
option socket-stats
# option nolinger
maxconn 300
# use tcp content accepts to detects ssl client and server hello.
# acl clienthello req_ssl_hello_type 1 -> seems to not work
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use_backend ssl_testdomain_prod if { req_ssl_sni -i www.testdomain.nl }
use_backend ssl_testdomain_stag if { req_ssl_sni -i test.testdomain.nl }
default_backend ssl_testdomain_stag
backend ssl_testdomain_stag
mode tcp
#option nolinger
option tcplog
balance roundrobin
hash-type consistent
option srvtcpka
# maximum SSL session ID length is 32 bytes.
stick-table type binary len 32 size 30k expire 30m
# make sure we cover type 1 (fallback)
acl clienthello req_ssl_hello_type 1
acl serverhello rep_ssl_hello_type 2
# use tcp content accepts to detects ssl client and server hello.
tcp-request inspect-delay 5s
tcp-request content accept if clienthello
# no timeout on response inspect delay by default.
tcp-response content accept if serverhello
# SSL session ID (SSLID) may be present on a client or server hello.
# Its length is coded on 1 byte at offset 43 and its value starts
# at offset 44.
# Match and learn on request if client hello.
stick on payload_lv(43,1) if clienthello
# Learn on response if server hello.
stick store-response payload_lv(43,1) if serverhello
#option ssl-hello-chk
server x_testdomain_stag 123.123.123.123:443
backend ssl_testdomain_prod
mode tcp
#option nolinger
option tcplog
balance roundrobin
hash-type consistent
option srvtcpka
# maximum SSL session ID length is 32 bytes.
stick-table type binary len 32 size 30k expire 30m
# make sure we cover type 1 (fallback)
acl clienthello req_ssl_hello_type 1
acl serverhello rep_ssl_hello_type 2
# use tcp content accepts to detects ssl client and server hello.
tcp-request inspect-delay 5s
tcp-request content accept if clienthello
# no timeout on response inspect delay by default.
tcp-response content accept if serverhello
# SSL session ID (SSLID) may be present on a client or server hello.
# Its length is coded on 1 byte at offset 43 and its value starts
# at offset 44.
# Match and learn on request if client hello.
stick on payload_lv(43,1) if clienthello
# Learn on response if server hello.
stick store-response payload_lv(43,1) if serverhello
#option ssl-hello-chk
server x_testdomain_prod 123.123.111.111:443
So what does it do? We will send all request to our internal IP (frontend .21) , who will split this up according to SNI information , if that fails it will go to the ‘staging’ backend. This should only happen to software which doesn’t support this feature. Curl for example knows how to use SNI. Wget also knows. All modern browsers do in fact.
It will analyse the SNI information and play SSL proxy for us. Haproxy is a good puppy! And as sugar on the pie, you can check the stats to see how much has been routed. It’s the other way around, an internal IP is the frontend, while the backends reside on a public IP.
This works for me on the following version
HA-Proxy version 1.5-dev19 2013/06/17
The only thing left to do is trick DNS by overriding the official target IP’s by your internal IP address.