具有 SNI 和不同 SSL 设置的 HAProxy

具有 SNI 和不同 SSL 设置的 HAProxy

我的两个网站都使用了 HAProxy,其中一个是公共网站,一个是私人网站。

www.mysite.com private.mysite.com

目前,我正在像这样使用 haproxy:

frontend mysite_https
  bind *.443 ssl crt /etc/mycert.pem ca-file /etc/myca.pem verify optional no-sslv3
  mode http
  acl domain_www     hdr_beg(host) -i www.
  acl domain_private hdr_beg(host) -i private.
  acl path_ghost     path_beg         /ghost/
  acl clientcert     ssl_c_used

  redirect location https://www.example.com if path_ghost !clientcert
  redirect location https://www.example.com if !domain_www !clientcert

  use_backend bknd_private if domain_private
  use_backend bknd_www     if domain_www

  default_backend bknd_www

这应该做的是请求客户端证书(可选)并继续。如果域名不是 www.example.com 且访问者无法提供正确的证书,或者路径是 /ghost/ 且访问者无法提供正确的证书,则应将其重定向到https://www.example.com

到目前为止,这很好。但是,我收到 Mac 用户使用 Safari 浏览我的网站的投诉,称他们在浏览时不断被要求提供证书https://www.example.com/而 Firefox 仅在浏览时询问https://private.example.com/或者https://www.example.com/ghost/

显然这就是 Safari 的工作方式,所以我无法解决这个问题。我的想法是使用 SNI 来划分不同的前端

frontend mysite_https
  bind *.443 ssl crt /etc/mycert.pem no-sslv3

frontend private_https
  bind *.443 ssl crt /etc/mycert.pem ca-file /etc/myca.pem verify optional no-sslv3

当然,这是行不通的,因为

a. 我无法让两个前端在只有一个公共 IP 的情况下监听端口 443 b. 我还没有找到一种方法来表示“use_frontend if domain_www”或类似的东西。(只能使用 use_backend 或 use-server)

我也尝试用三个 haproxy 服务器来做

frontend haproxy-sni
bind *:443 ssl crt /etc/mycert.pem no-sslv3
mode tcp

tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }

acl domain_www ssl_fc_sni_end -i www.example.com

use-server server1 haproxy-private.lan  if !domain_www
use-server server2 haproxy-public.lan   if domain_www

这行得通,但问题是 haproxy-private 要求提供客户端证书,但请求未到达浏览器。不知何故 haproxy-sni 丢弃了请求。

此外,我现在有三个 haproxy 服务器,这是不可取的(尽管如果我找不到更好的解决方案的话,这是一个可能的选择)。

最好我想要这样的东西(虚构的..不知道真正的选择)

frontend mysite_https
  bind *.443 ssl crt /etc/mycert.pem no-sslv3
  mode http

  acl domain_www     hdr_beg(host) -i www.
  acl domain_private hdr_beg(host) -i private.
  acl path_ghost     path_beg         /ghost/

  ssl_options ca-file /etc/myca.pem verify optional if !www_domain          # made up!
  ssl_options ca-file /etc/myca.pem verify optional if !path_ghost          # made up!

  acl clientcert     ssl_c_used

  redirect location https://www.example.com if path_ghost !clientcert
  redirect location https://www.example.com if !domain_www !clientcert
  ...

我希望有人能帮助我......

答案1

我找到了一个解决这个问题的方法,不需要额外的服务器或服务。但我不确定这是否会产生新的问题。对我来说,它现在似乎有效。

我的做法是,为每个需要不同 SSL 设置的域创建一个前端。然后,我将这些前端的绑定选项设置为高端口(这些端口无法从公共端口访问!)。

我创建了另一个监听端口:443 的前端,以根据 SNI 划分流量,并将后端服务器设置为 127.0.0.1:高端口。

这样,我在 haproxy 中创建了一种循环

[incoming]->[haproxy:443]->[haproxy:7000]->[www.intern.lan]
[incoming]->[haproxy:443]->[haproxy:8000]->[private.intern.lan]

这是配置部分。

frontend frnd_snipt                                             # Frontend_SNI-PassThrough (snipt)
  bind *:443                                                    # Do not use bind *:8443 ssl crt etc....!
  option tcplog
  mode tcp 

  tcp-request inspect-delay 5s
  tcp-request content accept if { req_ssl_hello_type 1 } 

  acl subdomain_is_www   req_ssl_sni -i www.example.com
  acl subdomain_is_www   req_ssl_sni -i example.com
  acl subdomain_is_private req_ssl_sni -i private.example.com

  use_backend bknd_snipt_private if subdomain_is_private
  use_backend bknd_snipt_www  if subdomain_is_www

backend bknd_snipt_www
  mode tcp                                              # tcp mode must match the frontend mode - already set as default in [global]
  server snipt-www 127.0.0.1:7000                       # run without "check", otherwise haproxy checks itself all the time!

backend bknd_snipt_private
  mode tcp     
  server snipt-private 127.0.0.1:8000                   # also, don't add "ssl" when in tcp mode. "ssl" is an http mode option (result in "NO-SRV" when set in tcp)

##### NORMAL HAPROXY PART #####
frontend www_example_com                                # this frontend can be in tcp or http mode...
  bind *:7000 ssl crt /etc/mycert.pem no-sslv3          # www. frontend with normal https
  mode http
  option httplog


frontend private_example_com
  bind *:8000 ssl crt /etc/mycert.pem ca-file /etc/myca.pem verify optional no-sslv3        # private. frontend with client certificate request.
  mode http
  option httplog
  ... # whatever you have in your frontend

如果有人对此有想法,或者知道为什么这可能是一个坏主意,请告诉我。它有效,但我想知道为什么 use_frontend 不是一个选项。可能是因为无论出于什么原因,这都是不应该做的事情。

答案2

最新版本的 haproxy 支持一项名为的设置crt-list,它允许您根据匹配的证书指定不同的 TLS 设置

你可以像这样使用它:

haproxy.conf:

frontend https
    mode http
    bind *:443 ssl crt-list /etc/haproxy/crt-list.conf ca-file ca.pem

    use_backend test if { ssl_fc_sni -i test.area.example.org }
    use_backend private if { ssl_fc_sni -i private.example.org }
    default_backend www

crt-列表.conf:

www.pem [verify none]
www.pem [verify required] *.area.example.org
private.pem [verify required]

更多信息:https://cbonte.github.io/haproxy-dconv/1.9/configuration.html#5.1-crt-list

安全注意事项:始终将您的(敏感)主机名与 SNI 匹配ssl_fc_sni,而不是 HTTP 主机名。否则,攻击者可能会通过发送 TLS SNIwww.example.org但将 HTTP 主机名设置为来绕过您的客户端证书身份验证private.example.org

相关内容