У меня есть 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
Конечно, это не работает, потому что
а. У меня не может быть двух интерфейсов, прослушивающих порт 443 только с одним общедоступным IP-адресом. 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
...
Надеюсь, кто-нибудь сможет мне с этим помочь ...
Я нашел решение этой проблемы, не требующее дополнительных серверов или сервисов. Я не совсем уверен, не вызовет ли это новых проблем. Мне кажется, что сейчас это работает.
Я сделал это так, чтобы создать интерфейс для каждого домена, который требовал разных настроек ssl. Затем я установил опцию привязки этих интерфейсов к высоким портам (они недоступны из общедоступных!).
Я создал еще один внешний интерфейс, прослушивающий порт: 443, чтобы разделить трафик на основе SNI, и установил для внутренних серверов значение 127.0.0.1:high-port.
Таким образом, я создал своего рода цикл в 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 не подходит. Может, потому, что этого делать не следует по каким-либо причинам.
последние версии 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-list.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 SNI www.example.org
но установите для имени хоста HTTP значение private.example.org
!