Table of Contents

Исходная ситуация

На одном физическом сервере, который имеет единственный реальный IP-адрес работают несколько виртуальных HTTPS web-серверов. В том числе и XenApp с Citrix Secure Gateway. У каждого сервера свой сертификат.

Задача - настроить reverse-proxy для этих сервисов, чтобы на real_IP:443 работали все сервисы и терминация SSL трафика происходила не на reverse-proxy, а на web-серверах. Также, мне нужно, чтобы через порт 443 проксировался ssh-трафик на заданный хост.

До этого момента в качестве reverse-proxy использовался nginx. И на первый взгляд он пригоден для этой цели. Например, сайт XenApp открывался. Но как оказалось, для работы с Citrix Secure Gateway он не подходит. Скорее всего не все HTTPS-запросы Citrix Receiver в заголовке имеют информацию об имени сервера (без SNI). В результате - nginx не понимал куда проксировать такой запрос. Однако на последних версиях Citrix Secure Gateway 3.3.5 и Linux Citrix Receiver 13.7 x64, вероятно, все заработает и через nginx.

Видимо нужен reverse-proxy, работающий в режиме tcp и поддерживающий SNI (Server Name Indication), который сможет неидентифицированные с помощью SNI запросы отправлять на дефолтный сервер.

Решение

Решением стало внедрение haproxy (версии не ниже 1.5).
Устанавливать на Ubuntu 16.04 можно из стандартного репозитория. Устанавливаем haproxy и проверяем версию:

sudo apt-get install haproxy
haproxy -vv

В выводе этой команды должно быть:

HA-Proxy version 1.6.3 2015/12/25

и

OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes

Теперь в файл конфигурации /etc/haproxy/haproxy.cfg пишем что-то такое:

global
  log 127.0.0.1 local5 notice
  user haproxy
  group haproxy

# Adjust the timeout to your needs
defaults
  timeout client 30000s
  timeout server 30000s
  timeout connect 10s

#############################################################
############ Frontend HTTPS in tcp mode
###########################################################
frontend https_proxy
  bind *:443
  mode tcp
  log 127.0.0.1 local6
  option tcplog
  tcp-request inspect-delay 5s
  tcp-request content accept if { req_ssl_hello_type 1 }

  use_backend ssh if { payload(0,7) -m bin 5353482d322e30 }
  use_backend xd_https if { req_ssl_sni -i xd.autosys.tk }
  use_backend sstp_https if { req_ssl_sni -i sstp.autosys.tk }
#  default_backend xd_https
  default_backend sstp_https
  
##########################################################
###-------------- FrontEnds HTTP ------------########
######################################################

frontend http
#   option httplog
   log 127.0.0.1 local7 debug                   #/var/log/http.log
   mode tcp
   bind *:80
# redirect to HTTPS
   redirect scheme https if { hdr(Host) -i xenapp.autosys.tk } !{ ssl_fc }
   use_backend ssh if { payload(0,7) -m bin 5353482d322e30 }
   
#########################################################################
####----- Backends----------######################
########################################################################

backend sstp_https
  mode tcp
  server sstp_ssl 192.168.77.138:443

backend xd_https
  mode tcp
#  option ssl-hello-chk
  server xdwebi xd.autosys.tk:443 check

backend ssh
  mode tcp
  server ssh_haproxy 127.0.0.1:22

Старые версии продуктов Citrix (Citrix Secure Gateway и Citrix Receiver) не работали без строки default_backend xd_https (вероятно не в каждом запросе HTTPS был заголовок SNI), однако на последних версиях (Citrix Secure Gateway 3.3.5 и Linux Citrix Receiver 13.7 x64) все работает без этой строки, что позволяет запустить на порте 443 еще и SSTP-VPN на базе SoftEther, указав именно его в качестве дефолтного бекенда для SSL-траффика :).
Также в приведенном конфиге показано как пробросить SSH с портов 80 и 443.

Отказоустойчивая балансировка Secure Gateway

Предположим, у нас работает Citrix SecureGateway, но иногда нам нужно с его хостом производить какие-то манипуляции без остановки сервиса. Выход - настройка балансировки. Поднимаем второй хост с Web-интерфейсом и Secure Gateway и настраиваем также как и первый. Сертификат берем тот же что и на первом хосте. Вся магия будет на haproxy в раздельчике backend. В него нужно добавить balance source, тип хеширования IP-адреса источника hash-type consistent и второй сервер с SecureGateway.

backend xenapp
  option tcplog
  log ${LOCAL_SYSLOG}:514 local1 debug
  hash-type consistent 
  balance source
  mode tcp
  option ssl-hello-chk
  server xenappsrv xenapp.domain.com:443 check
  server xenappsrv2 xenapp2.domain.com:443 check

В результате у нас будет балансировка типа source, при которой клиент будет работать с тем сервером, к которому он подключился изначально. Тип хеширования можно не задавать (по-умолчанию это map-based), но иногда лучше работает consistent. Также, иногда нужно немного расширить таймаут проверки ssl-hello-check. Для этого перед строкой option ssl-hello-chk добавить timeout check 5000 (ну или сколько надо в миллисекундах).

Включаем логи на для haproxy

HAproxy очень продвинутый балансировщик, поэтому писать логи в просто файлы он не может…
Но может писать логи на удаленный или локальный сервер syslogd.

Сначала нужно разрешить syslogd принимать логи по UDP.
Для этого в файле /etc/default/syslogd нужно заменить SYSLOGD=““ на SYSLOGD=”-r”.

Затем в файле /etc/syslog.conf нужно прописать facilities для логов. Напомню, что логи можно писать как в один файл все, так и в разные файлы и на разные сервера. Прямо в начало /etc/syslog.conf я добавил:

# Log HAproxy events
local0.*        -/var/log/haproxy.log
local1.*        -/var/log/xenapp.mydomain.org.log
local2.*        -/var/log/mydomain.org.log

Тем самым я объявил facilities с именами local10, local1 и local2.

А теперь можно настроить логи в конфигурации haproxy. У меня получилось примерно так:

global
log ${LOCAL_SYSLOG}:514 local0 notice


defaults
  option tcplog
  log global
# Adjust the timeout to your needs
  timeout client 3000s
  timeout server 3000s
  timeout connect 10s

# Single VIP
listen ssl :443
  tcp-request inspect-delay 5s
  tcp-request content accept if { req_ssl_hello_type 1 }
  use_backend xenapp if { req_ssl_sni -i xenapp.mydomain.org }
  use_backend mydomain if { req_ssl_sni -i mydomain.org }
  default_backend xenapp

backend xenapp
  log ${LOCAL_SYSLOG}:514 local1 debug
  mode tcp
  server xenappsrv xenapp.local:443

backend mydomain
  log ${LOCAL_SYSLOG}:514 local2 debug
  mode tcp
  server mydomainsrv mydomain.local:443

Всё. Еще может понадобиться создать файлы логов, а то syslogd откажется стартовать.
Перезапускаем haproxy и syslogd и радуемся логам.