HAProxy用于SSL透传

一个实际需求:需要将一台服务器用作SSL前端,将不同域名的请求转发到不同的后端服务器,要求前端服务器无法得知请求的内容(即我们不信任前端服务器,需要防止它偷看或篡改传输内容)

一个符合直觉的想法:用Apache做反向代理

SSLProxyEngine on
ProxyRequests off
ProxyPass / https://backend.example.com/
ProxyPassReverse / https://backend.example.com/

效果是没有问题的

但…

Apache上原本开启了mod_cache模块,查看日志发现:Apache竟然能缓存我们的请求?!

能缓存,说明前端服务器一定是拆包查看内容了。

Google了一下发现,Apache的SSL反向代理必须拆包,不会进行透传(passthrough)处理,但HAProxy可以满足这个要求。

下面给出用HAProxy实现这个功能的配置文件,以及其中的几个坑:

# global部分为HAProxy的全局配置
# 此处的配置取自HAProxy 1.6.3默认配置文件
global
   # 日志选项,此处的配置会将日志输出到syslog
   # 根据/etc/rsyslog.d/目录下的配置文件,日志会被写入/var/log/haproxy.log
   # 注意:安装HAproxy之后,需要重启rsyslog服务才能开始输出日志
   # service rsyslog restart
   log /dev/log   local0
   log /dev/log   local1 notice
   chroot /var/lib/haproxy
   stats socket /run/haproxy/admin.sock mode 660 level admin
   stats timeout 30s
   user haproxy
   group haproxy
   daemon

   # Default SSL material locations
   ca-base /etc/ssl/certs
   crt-base /etc/ssl/private

   # Default ciphers to use on SSL-enabled listening sockets.
   # For more information, see ciphers(1SSL). This list is from:
   #  https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
   ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
   ssl-default-bind-options no-sslv3

# frontend定义一个前端,也就是绑定本机的一个IP和端口
# (https_frontend只是给这个frontend起个名字,并不重要)
frontend https_frontend
    # 绑定本机443端口,用于提供SSL服务
    bind *:443
    # mode设为tcp时,HAProxy在第四层上进行转发,可以转发任意基于TCP的协议,包括SSL
    # 此时HAProxy不会解密SSL数据包,无法得知其内容
    # 该模式下HAProxy可以在没有SSL证书的情况下为任意域名提供服务
    # 因为HAProxy会将SSL数据包原样转发到配置有证书的SSL服务器上
    # (如果mode设为http,则HAProxy在第七层上做HTTP代理)
    mode tcp

    # 日志选项
    log global
    # 对TCP做详细日志
    option tcplog
    # 不记录空TCP连接,减少日志中的垃圾数据
    option dontlognull

    # 连接超时值,如果不设置,默认选项为无限timeout
    timeout connect 5000
    timeout client 50000
    timeout server 50000

    # TCP检查等待时间,默认值是0s,即不等待
    # 由于TCP是分片传输的,无法保证上层协议的整个数据包一次到达
    # 因此如果不设置一个几秒钟的超时值,HAproxy无法了解到该TCP流的任何信息
    # (例:你不可能在一个TCP流刚刚完成三次握手的时候读出它承载的HTTP协议的cookie)
    tcp-request inspect-delay 5s
    # 如果收到的是SSL协议的clienthello数据包,我们就接受这个连接
    tcp-request content accept if { req_ssl_hello_type 1 }

    # 一条ACL在HAProxy中就是一条匹配规则
    # 类似Apache mod_rewrite的一条RewriteCond
    # 只不过HAProxy不止可以对HTTP的URL进行匹配
    # 还可以匹配四层(TCP)、五层(SSL)、七层(HTTP)的特征
    # 下面定义了3条ACL
    # req_ssl_sni为SSL协议的域名,这部分信息无需解密SSL协议即可读出
    # (参见官方文档:https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#7)
    acl jinzihao_me req_ssl_sni -m str jinzihao.me
    acl www_jinzihao_me req_ssl_sni -m str www.jinzihao.me
    acl http_jinzihao_me req_ssl_sni -m str http.jinzihao.me
#   acl [规则名称]         [变量]    [匹配方式]    [匹配值]
    # 一条use_backend则将一条或多条规则映射到一个backend
    # 类似Apache mod_rewrite的一条RewriteRule
    # (参见官方文档:https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#7.2)
    # 下面定义了2条use_backend
    use_backend nisl if http_jinzihao_me
    use_backend bandwagonhost if jinzihao_me or www_jinzihao_me
#   use_backend [后端名] if [规则1] [与/或/非] [规则2] [与/或/非] [规则3] ...

# backend定义一个后端,也就是路由的一组目标地址和端口
# backend的名字(这里是bandwagonhost)用在上面写到的use_backend语句中
backend bandwagonhost
    mode tcp
    # HAProxy可以用作负载均衡的
    # 只要我们配置好负载均衡规则...
    balance roundrobin
    # ...并给出几台可用的服务器
    server bandwagonhost 104.160.38.132:443
    # (参见官方文档:https://cbonte.github.io/haproxy-dconv/1.8/configuration.html#4.2-balance)

# 另一个backend,在这里我们把它指向本机8043端口上的Apache httpd
backend nisl
    mode tcp
    balance roundrobin
    server nisl 127.0.0.1:8043