Привет всем,

Сегодня хотелось бы оставить заметку относительно реализации почтового балансировщика на HAproxy + Keepalived, для балансировки SMTP/IMAP трафика и создания единой клиентской точки доступа к сервисам.

Возможно вам уже известно что Exchange Server не имеет какого либо нативного сервиса для создания отказаусточивости, и как правило почтовую инфраструктуру добавляют дополнительный слой из серверов, которые решают эти проблемы. На практике это могут быть железные балансировщики, например F5 BIG-IP. Или же более простой и дешевый вариант построенный на haproxy.

Этап планирования

Второй вариант может показаться простым в интеграции, но тоже имеет несколько подводных камней которые стоит учесть на этапе пларирования.

TCP vs. HTTP

В первую очередь стоит определиться с режимом (уровнем) балансировки, haproxy может работать как на L4 транспортном уровне работая только с tcp-трафиком, так и на L7 прикладном уровне взаимодействуя с http-запросами.

У каждого из режимов есть свои плюсы/минусы:

  • HAproxy в режиме TCP, не выполняет проверку содержимого и соответственно работает быстрее, просто перенаправляя трафик на целевой почтовый сервер и порт. В дополнение очень просто и быстро настраивается и выводиться в продакшен. Из минусов, так как мы не проверяем каждый запрос, то не можем выполнять определенные манипуляции над запросами (банально отсечь), уже на уровне балансировщика. Далее при таком режиме нам доступна только проверка порта. И может произойти такой кейс, когда сервис не будет работать, но порт будет открыт. В таком случаи haproxy будет считать что наш проблемный сервер доступен, и не станет выводить его из балансировки.
  • HAproxy в режиме HTTP, при работе в таком режиме мы получаем возможности для более тонкой настройки балансировщика. Проверки доступности сервиса, можно выполнять за счет отправки http-запросов на целевой сервер. Как я и упоминал ранее, за счет просмотра каждого запроса, haproxy может осуществить контроль доступа к определенным службам. Например закрыть доступ к админ-панели Exchange. Да и в целом за счет создания ACL-правил возможно настроить специфичные варианты машрутизации трафика. К дополнительным плюшкам можно добавить реализации SSL, с возможностью использования нескольких ssl-сертификатов.

В процессе тестирования и вывода балансировщиков в прод, мне удалось попробовать оба режима работы. Особой разницы между TCP/HTTP режимами по скорости, я не могу отметить. И после отладки haproxy, все же пришел к http-mode. В дополнение, хочу отметить что переключение балансировщиков в tcp-режим, помогло в траблшутинге проблем связанных с клиенсткими подключениями или NTLM-аутентификацией. По этим причинам, думаю полезно будет поделится обоими версиями конфигураций haproxy.

Использование SSL

SSL Offloading

В выборе режима работы SSL, тоже есть нюансы. Частый пример, который можно встретить это использование SSL Offloading (SSL Разгрузка). Когда SSL-терминирование реализуется на самом балансировщике, haproxy выполняет шифрование/дешифрование запросов и далее проксирует их на целевой сервер в открытом виде по http, тем самым разгружая почтовый сервак. Загвостка состоит в том, что вебсервисы ExchangeServer по умолчанию работают на https и вам необходимо будет перенастроить их на работу http. haproxy-exchange-ssl1.png

SSL Bridging

Второй режим работы SSL называется - SSL Bridging (SSL Мост), в этом случаи haproxy принимая входящие сообщения выполняет их декрипт, и далее для отправки на целевой сервер повторно зашифровывает их. Нюанс кроется в том, что на балансировщике и на exchange должен использоваться один и тот же сертификат. Использование различных сертификатов приведет к ошибкам проверки токена, в следствии чего вызовет проблемы с клиентским доступом. Поэтому при использовании этого режима работы SSL, потребуется выпустить один сертификат с добавлением SAN-объектов или же использовать wildcard-сертификат. В дополнение если планируется использовать ssl-сертификат выпущенный локальным CA, то на серверах-балансировщиках нужно будет обновить рутовый серт, добавив в него сертификаты рутовых/промежуточных локальных центров сертификации. haproxy-exchange-ssl2.png

В этом поинте я остановился на втором варианте, в процессе постараюсь поделится инструкциями как приготовить ssl-сертификат.

Настройка балансировшиков

Время приступать к настройке будушего кластера, собственно схема проекта с которым предстоит работа: haproxy-exchange-scheme1.png На этой схеме имеется два сервера с Exchange Server настроенных в DAG-кластер. Уровнем выше следуют наши балансировшики, на борту каждого крутится haproxy для балансировки почтового трафика, и keepalived для релизации плаваюшего IP. По протоколу VRRP сервера проверяют работоспособность друг-друга. Далее в виде оранжевого облака представлена сущность, это непосредственно наши почтовые клиенты, которые подключаются из внутри скоупа. И наконец, в DMZ-сегменте мы реализуем третий (пограничный/бордер) прокси, задача которого будет прием запросов со стороны внешних клиентов.

Настройка HAproxy

Установка Haproxy

У меня сервера развернуты на базе Almalinux 8.9, и установка haproxy происходит достаточно тривиально из стандартных репозиторириев, командой:

[root@exch-lb01]# yum install -y haproxy

В дополнение, отмечу, что из стандартного списка репозиториев ставится не самая свежая версия программы.

Далее ставим сервис haproxy в автозагрузку:

[root@exch-lb01]# systemctl enable haproxy

И сразу же открываем необходимые порты на фаерволе:

[root@exch-lb01]# firewall-cmd --add-port={25,80,443,143}/tcp --permanent
[root@exch-lb01]# firewall-cmd --reload

Конфигурация для TCP-балансировки

В файл конфигурации haproxy, добавляем:

[root@exch-lb01]# mv /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.default
[root@exch-lb01]# vim /etc/haproxy/haproxy.cfg
---
#######################################
# Global settings
#######################################

global
    log         127.0.0.1 local0   
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon
    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats

#######################################
# Defaults settings
#######################################

defaults
    mode                    tcp
    log                     global
    option                  redispatch
    option                  log-health-checks
    retries                 3

#######################################
# Frontend sections
#######################################

frontend fe_exch2019
    mode tcp
    option tcplog
    bind 0.0.0.0:80
    bind 0.0.0.0:443
    timeout client  1m
    default_backend be_exch2019_http

frontend fe_exch2019_smtp
    mode tcp
    option tcplog
    bind 0.0.0.0:25 name smtp
    timeout client  1m
    
    # Acl for SMTP
    acl smtp_networks src -f /etc/haproxy/haproxy_smtp_whitelist.lst
    tcp-request content reject if !smtp_networks
    
    default_backend be_exch2019_smtp

frontend fe_exch2019_imaps
    mode tcp
    option tcplog
    bind *:143 name imap
    timeout client  1m
    default_backend be_exch2019_imaps

#######################################
# Backend sections
#######################################

backend be_exch2019_http
    mode tcp
    log global
    option tcplog
    timeout connect 10s
    timeout server 1m
    balance roundrobin
    server exch01 exch01.mydomain.local:443 weight 75 check
    server exch02 exch02.mydomain.local:443 weight 25 check

backend be_exch2019_smtp
    mode tcp
    log global
    option tcplog
    timeout connect 10s
    timeout server 1m
    balance roundrobin
    server exch01 exch01.mydomain.local:25 weight 75 check
    server exch02 exch02.mydomain.local:25 weight 25 check

backend be_exch2019_imaps
    mode tcp
    log global
    option tcplog
    timeout connect 10s
    timeout server 1m
    balance roundrobin
    server exch01 exch01.mydomain.local:143 weight 75 check
    server exch02 exch02.mydomain.local:143 weight 25 check

В быстром порядке разберем этот конфиг, здесь представлено четыре контекста.

В глобальном контексте (global) указываются глобальные настройки для haproxy и используемые здесь параметры выставлены по умолчанию, в будущем здесь мы можем изменить параметр log, для включения/выключения логирания.

Контекст defaults содержит общие настройки конторые могут быть унаследованными frontend, backend частями. И тут мы оставляем опции:

  • mode - режим работы haproxy, в этом конфиге настраиваем haproxy для работы в tcp-mode по умолчанию
  • log - как и писал выше, настраивает логировние сервера. В данном случани log global, говорит ссылаться на опцию log в глобальном контексте (global).
  • option - с помошью этого параметра мы можем добавить или отключать какие-то фичи в функционале haproxy. Здесь же мы используем option redispatch, которая включает функционал ребаланса запросов в случаи недоступности какого-либо бекенда. option log-health-checks - эта опция включает регистрацию изменений работоспособности бекендов.
  • retries - этот параметр указывает количество неудачных попыток, после которых бекенд будет считаться недоступным.
  • option tcplog - устанавливает формат логирования для tcp.

Далее следует контекст, в котором мы описываем frontend часть. Здесь настраивается внешней интерфейс взаимодействия с haproxy. Тут мы опеределяем режим работы (http/tcp mode), адрес и порт на котором haproxy будет принимать коннекты. В нашем же случаи представлено описание трех frontend секций с именами - fe_exch2019, fe_exch2019_smtp, fe_exch2019_imap. Каждый из фронтов содержит почти одинаковый набор параметров:

  • mode tcp - определяет режим работы конкретного фронта. По идеи, мы можем не указывать этот параметр, так как выше в defaults он уже определен. Но все же негласно, принято и в каждой секции его указывать. Возможно это сделано для читаемости…
  • bind 0.0.0.0:443 - bind определяет, на каком порты и адресе будет работает конкретный фронтенд.
  • timeout client 1m - данный параметр указывает, в течении какого времени неактивная сессия будет разорвана клиентом. По умолчанию сессия держится около 5 минут, но принято уменьшать таймаут.

Во фронде fe_exch2019_smtp использован acl, для разграничения доступа к smtp-сервису.

# Acl for SMTP
acl smtp_networks src -f /etc/haproxy/haproxy_smtp_whitelist.lst
tcp-request content reject if !smtp_networks

Аксесс лист определяется ключевым словом acl, затем следует название листа smtp_networks. После определяется источник src с опцией -f, которая приказывает взять данные из файла. Далее указывается инструкция, при которой все новые сессии будут обрыватся, если же адрес клиента не содержится в файле acl. Последняя директива в каждом фронте это - default_backend, с помошью ее мы выбираем какой бекенд использовать по умолчанию.

Завершающим элементом нашей конфиги, следуем контекст backend, он содержит настройки пула серверов, на которые haproxy будет маршрутизировать трафик. Здесь также имеется три бекенда с именами - be_exch2019_http, be_exch2019_smtp, be_exch2019_imap. Внутри каждого следуют уже знакомые директивы, и добавляется несколько новых:

  • timeout connect 10s - этот параметр задает максимальное время в течении которого haproxy будет пытатся поднять сессию с бекендом. Если ваши сервера размещаны в разных сетевых сегментах, или dmz-зонах, то рекомендуется увеличивать значение времени. Не рекомендуется устанавливать значение менее 5 секунд.
  • timeout server 1m - с помошью данного параметра мы устанавливаем таймаут на бездействие со стороны сервера. В течении установленного времени сервер должен будет отправить какие-либо данные, иначе сессия будет разорвана.
  • balance roundrobin - данная директива настраивает тип балансировки запросов. В данном случаи мы используем тип roundrobin, при которой каждый сервер будет используется по очередности.

В конце данного контекста описываются пулл с серверами Exchange, на которые haproxy будет распределять запросы.

  • server - каждый сервер в пуле определяется с помошью этого аргумента. Далее указывается имя (alias) участника данного пула. И затем мы указываем IP-адрес/Доменное имя сервера и порт.
  • weight для каждого мембера в конце приписывается параметр веса, при работе бекенда в режиме roundrobin, логика распределения запросов такова что большая часть запросов будет отправлена на сервер с наибольшим числом веса.
  • check эта опция нужна для проверки доступности сервера.

Синтаксис описания участника пула такой:

<Аргумент> <Имя/Alias> <IP-адрес/Domain сервера и порт> <Вес сервера> <Проверка>
  server     exch01      exch01.mydomain.local:143       weight 75      check

Сохраняем конфигурацию, и далее создадим отдельный файл, в котором будут содержаться ip-адреса хостов, которым разрешено использование SMTP.

[root@exch-lb01]# vim /etc/haproxy/haproxy_smtp_whitelist.lst
---
10.8.5.17/32
10.8.5.17/32
...

Для запуска haproxy все готово, для проверки рабоспособности конфигурации можно воспользоваться командой:

[root@exch-lb01]# haproxy -c -V -f /etc/haproxy/haproxy.cfg

Перезапускаем сервис:

[root@exch-lb01]# systemctl restart haproxy

Конфигурация для HTTP-балансировки

В ниже представной конфиге используется сетап с SSL-мостом, где для успешной работы балансировщика нам нужно приготовить один ssl-сертификат, который должен быть установлен на почтовых серверах и на нодах-балансировщиках. Если у вас есть wildcard-сертификат, то нет никаких проблем и можно использовать его.

В моем же случаи балансировщики будут работать внутри инфраструктуры, без доступа со внешки. Соответственно используемый мной ssl-сертификат выпущен локальным CA, с добавлением дополнительных SAN-объектов.

Собственно сам конфиг:

#######################################
# Global settings
#######################################

global
    log         127.0.0.1:514 local0
    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats

    # utilize system-wide crypto-policies
    ssl-default-bind-ciphers PROFILE=SYSTEM
    ssl-default-server-ciphers PROFILE=SYSTEM

#######################################
# Defaults settings
#######################################

defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option                  http-server-close
    option                  httpclose
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    15s
    timeout queue           1m
    timeout connect         10s
    timeout client          15m
    timeout server          15m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 100000

#######################################
# Frontend sections
#######################################

frontend fe_exch2019
    http-response set-header X-Frame-Options SAMEORIGIN
    http-response set-header X-Content-Type-Options nosniff
    http-response set-header Strict-Transport-Security max-age=63072000

    option http-keep-alive
    timeout client 30s

    mode http
    bind *:80
    bind *:443 ssl crt /etc/ssl/certs/exch.pem

    # Redirect from 80 -> 443
    redirect scheme https code 301 if !{ ssl_fc }

    # Acl for Exchange Admin Center
    acl admins_network src -f /etc/haproxy/haproxy_ecp_whitelist.lst
    acl ecp_service url_beg /ecp
    http-request deny if ecp_service !admins_network

    # Acl for OWA
    acl users_networks src -f /etc/haproxy/haproxy_owa_whitelist.lst
    acl owa_service url_beg /owa
    acl owa_service url_beg /OWA
    http-request deny if owa_service !users_networks

    # Acl for assign backend
    acl autodiscover url_beg /Autodiscover
    acl autodiscover url_beg /autodiscover
    acl autodiscover url_beg /AutoDiscover
    acl eas url_beg /Microsoft-Server-ActiveSync
    acl eas url_beg /Microsoft-Server-activeSync
    acl mapi url_beg /mapi
    acl rpc url_beg /rpc
    acl owa url_beg /owa
    acl owa url_beg /OWA
    acl ecp url_beg /ecp
    acl ews url_beg /EWS
    acl ews url_beg /ews
    acl oab url_beg /OAB

    # Backend assign
    use_backend be_exch2019_autodiscover if autodiscover
    use_backend be_exch2019_mapi if mapi
    use_backend be_exch2019_rpc if rpc
    use_backend be_exch2019_owa if owa
    use_backend be_exch2019_eas if eas
    use_backend be_exch2019_ecp if ecp
    use_backend be_exch2019_ews if ews
    use_backend be_exch2019_oab if oab

    # Backend by default
    default_backend be_exch2019_owa

frontend fe_exch2019_smtp
    mode tcp
    option tcplog
    bind *:25 name smtp

    # Acl for SMTP
    acl smtp_networks src -f /etc/haproxy/haproxy_smtp_whitelist.lst
    tcp-request content reject if !smtp_networks
    default_backend be_exch2019_smtp

frontend fe_exchange_imaps
    mode tcp
    option tcplog
    bind *:143 name imap
    default_backend be_exchange_imaps

#######################################
# Backend sections
#######################################

backend be_exch2019_autodiscover
    mode http
    balance source
    timeout server 30s
    timeout connect 4s
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /Autodiscover/HealthCheck.htm
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_mapi
    mode http
    balance source
    timeout server 30s
    timeout connect 4s
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /mapi/healthcheck.htm
    option log-health-checks
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_rpc
    mode http
    balance source
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /rpc/healthcheck.htm
    option log-health-checks
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_owa
    mode http
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /owa/healthcheck.htm
    option log-health-checks
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_eas
    mode http
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /microsoft-server-activesync/healthcheck.htm
    option log-health-checks
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_ecp
    mode http
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /ecp/healthcheck.htm
    option log-health-checks
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_ews
    mode http
    balance source
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /ews/healthcheck.htm
    option log-health-checks
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_oab
    mode http
    balance roundrobin
    option http-keep-alive
    option prefer-last-server
    option httpchk GET /oab/healthcheck.htm
    option log-health-checks
    http-check expect status 200
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019
    mode http
    balance roundrobin
    server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
    server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

backend be_exch2019_smtp
    mode tcp
    balance roundrobin
    option log-health-checks
    server exch01 exch01.mydomain.local:25 weight 75 check
    server exch02 exch02.mydomain.local:25 weight 25 check

backend be_exchange_imaps
    mode tcp
    balance leastconn
    option log-health-checks
    server exch01 exch01.mydomain.local:143 weight 75 check
    server exch02 exch02.mydomain.local:143 weight 25 check

В Global settings по большей части все параметры уже были выставлены по умолчанию. За исключением, включения опции логирования.

log         127.0.0.1:514 local0

Здесь мы перенаправляем весь вывод на локальный rsyslog-сервер. Далее в настройках rsyslog-сервера добавим правила, что бы события по haproxy записывались в отдельный файл:

[root@exch-lb01]# vim /etc/rsyslog.conf
---
# RULES: 
local0.* /var/log/haproxy.log
local0.notice /var/log/happroxy-notice.log

В дополение к этому добавились опции определяющие политики криптошифрования:

ssl-default-bind-ciphers PROFILE=SYSTEM
ssl-default-server-ciphers PROFILE=SYSTEM

На раннем этапе я не стал заморачиваться с выбором SSL/TLS - профилей, оставил системный. В будущем это можно будет допилить для исключения старых и не безопасных версий TLS/SSL.

Defaults settings - внутри этого контекста уже видны изменения в параметрах, собственно во первых по умолчанию мы включаем режим работы haproxy - http. По мимо уже известных нам параметров, определяем:

  • option httplog - включаем расширенное логирование для http;
  • option dontlognull - не логируем пустые (null) соединения;
  • option http-server-close - эта опция включает на сервере режим автоматического закрытия http-соединения, путем добавления заголовка close;
  • option httpclose - с этой опцией сервер проверяет каждое соединение на наличие заголовка close, и там где он отсутствует добавляет;
  • option forwardfor - включаем добавление заголовка X-Forwarded-For, за исключением адресов в этом диапазоне 127.0.0.0/8;
  • timeout http-request - устанавливает максимальное время ожидаения на выполнение запроса. То есть если с клиент установил сессию и не отпраляет данные в течении установленного таймаута, то сессия будет разорвана.
  • timeout queue - таймаут на максимальное время ожидания в очереди на свободный connection slot. Например, если на сервере привышено количество сессий, новые сессии будут падать в очередь на свободный слот. И далее по достижению этого таймаута, если не получат слот, то будут отброшены.
  • timeout http-keep-alive - определяем таймаут, в течении которого сервер будет держать сессию открытой.
  • timeout check - дополнильные таймаут на соединение, которое уже было установлено.
  • maxconn - максимальное количество одновременных соединений, которое может быть обработано одним фронтендом. При увеличении количества одновременных сессий, стоит учитывает размер оперативной памяти. Например, системе с 40000-50000 одновременными конекшенами потребуется около 1gb памяти.

Frontend sections - в контексте описания фронтов у нас имеется три фронтенда - fe_exch2019, fe_exch2019_smtp, fe_exch2019_imaps. Последнии два фронта мы рассмотрели ранее, они просто перенаправляют tcp-запросы на почтовые бекенды.

Фронтенд fe_exch2019 работает в режиме http, и выполняет логику по перераспределения запросов почтовым сервисам. В его настройки включены параметры:

  • http-response - данная опция добавляет заголовки базовой http-безопасности перед отправкой ответа обратно клиенту.
http-response set-header X-Frame-Options SAMEORIGIN
http-response set-header X-Content-Type-Options nosniff
http-response set-header Strict-Transport-Security max-age=63072000
  • option http-keep-alive и timeout client - эти два параметра настраивают удержание сессии между haproxy и клиентом. И в случаи если клиент неактивен в течении 30 секунд, сессия будет считаться разорваной.
option http-keep-alive
timeout client 30s

Для корректности работы NTLM-аутентификации, эти параметры необходимы.

  • mode, bind - Выбор режима работы данного фронтенда, и настройка сетевого сокета:
mode http
bind *:80
bind *:443 ssl crt /etc/ssl/certs/exch.pem

Здесь мы определяем порты, на которых данный фронтенд будет обрабатывать запросы. К порту 443, добавляем опцию ssl, и указываем сертификат. (Как и упоминал ранее, сертификат должен быть только такой же как и на почтовых серверах).

  • redirect - перенаправление на ssl порт
redirect scheme https code 301 if !{ ssl_fc }

Тут мы просто перенаправляем запросы с http на https.

  • acl, http-request deny - далее в конфиге следует два отдельных блока с правилом блокироки http-запроса.
# Acl for Exchange Admin Center
acl admins_network src -f /etc/haproxy/haproxy_ecp_whitelist.lst
acl ecp_service url_beg /ecp
http-request deny if ecp_service !admins_network

В данном случаи в первом access листе из стороннего файла определяем ip-адреса клиентов, которым разрешен доступ на панель управления Exchange Server. Во втором acl определяем uri-path до сервиса ecp. Затем после access-листов устанавливаем запрещающее действие. То есть все запросы на uri /ecp, от клиентов которые не определены в файле /etc/haproxy/haproxy_ecp_whitelist.lst будут отброшены.

Эти правила закрывают доступ на веб-версию outlook.

# Acl for OWA
acl users_networks src -f /etc/haproxy/haproxy_owa_whitelist.lst
acl owa_service url_beg /owa
acl owa_service url_beg /OWA
http-request deny if owa_service !users_networks

Этот блок правил является опциональным, его можно вообще не использовать. Но все же я стараюсь закрывать излишний функционал. По своей логике тут происходит аналогичная магия. Определяется список ip клиентов, котором доступ на ресурс /owa разрешен. И далее запрешающее всем действие.

Следом определяется другая группа ACL, в последующем при помощи этих правил будем выполнять выбор соостветствующего бекенда.

# Acl for assign backend
acl autodiscover url_beg /Autodiscover
acl autodiscover url_beg /autodiscover
acl autodiscover url_beg /AutoDiscover
acl eas url_beg /Microsoft-Server-ActiveSync
acl eas url_beg /Microsoft-Server-activeSync
acl mapi url_beg /mapi
acl rpc url_beg /rpc
acl owa url_beg /owa
acl owa url_beg /OWA
acl ecp url_beg /ecp
acl ews url_beg /EWS
acl ews url_beg /ews
acl oab url_beg /OAB

Параметр url_beg, проверяет начинается ли uri-запроса со значения которое у него указано. Если совпадет, то acl-правилу будет присвоено другое значание true.

Собственно в этом блоке работает логика распределения запросов по бекендам:

# Backend assign
use_backend be_exch2019_autodiscover if autodiscover
use_backend be_exch2019_mapi if mapi
use_backend be_exch2019_rpc if rpc
use_backend be_exch2019_owa if owa
use_backend be_exch2019_eas if eas
use_backend be_exch2019_ecp if ecp
use_backend be_exch2019_ews if ews
use_backend be_exch2019_oab if oab

Здесь все просто, если значение любой переменной true, тогда использовать бекенд с определенным именем. Например, если значение переменной owa является истиной, тогда запрос будет перенаправлен на бекенд с именем - be_exch2019_owa.

В случаи если не по одному правилу для запроса не был определен бекенд, то мы перенаправляем запрос на бекенд который будет использоваться по умолчанию:

# Backend by default
default_backend be_exch2019_owa

Последняя секция Backend sections, содержит список бекендов для веб-сервисов почты, smtp и imap. Не будет рассматривать их всех, рассмотрим только один, так как почти у всех использованы аналогичные параметры.

Бекенды для почтовых веб-сервисов все работают в режиме http. Используемый алгоритм балансировки balance source, означает что выбор бекенд сервера будет основан на хеше ip-адреса клиента. И при совпадении его, запрос клиента будет перенаправлен на один и тот же бекенд-сервер.

Для корретной авторизации и работы NTLM-аутентификации нужны эти параметры:

option http-keep-alive
option prefer-last-server

option http-keep-alive - сохраняет состояние сессии между сервером и клиентом определенное время. А параметр option prefer-last-server - нужен для того, чтобы запросы были направлены на один и тот же сервер в рамках одного сеанса.

Эти параметры нужны для проверки доступности веб-сервера.

option httpchk GET /Autodiscover/HealthCheck.htm
http-check expect status 200

С помошью параметра option httpchk отправляется GET-запрос по адресу сервера сервера с uri - /<ExchangeService>/HealthCheck.htm. http-check expect status 200 - этот параметр проверяем статус код проверки. В данном случаи он должен быть со статусом HTTP 200.

Описание бекендов завершается с определением пула серверов:

server exch01 exch01.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt
server exch02 exch02.mydomain.local:443 check ssl inter 15s verify required ca-file /etc/pki/tls/certs/ca-bundle.crt

Тут мы определяем два сервера exch01, exch02.

  • check ssl - включает проверку доступности серверов поверх ssl.
  • inter 15s - задает интервал между проверками.
  • verify required - при использовании этого параметра haproxy будет запрашивать ssl-сертификат у бекенд сервера.
  • ca-file /etc/pki/tls/certs/ca-bundle.crt - данная директива указывает, какой рутовый сертификат будет использоваться для проверки подлиности ssl-сертификатов сервера.

Собственно с конфигом на этом этапе все. Остаеться только создать файлы /etc/haproxy/haproxy_owa_whitelist.lst и /etc/haproxy/haproxy_ecp_whitelist.lst, в файлы необходимо добавить ip-адреса которым разрешен доступ на OWA и ECP.

И еще один момент, если вы тоже используете ssl-сертификат выпушенный локальным CA, то не забудьте добавить его в рутовый серт на сервере:

[root@exch-lb01]# yum install -y ca-certificates
[root@exch-lb01]# cp /root/mydomain-root.ca /etc/pki/ca-trust/source/anchors/mydomain-root.ca
[root@exch-lb01]# update-ca-trust

Ну и теперь можем проверить конфигу, и запустить балансироващик.

Настройка Keepalived

Итак, мы проработали вопрос балансировки почтовых запросов на сервера, определив для себя оптимальный сетап. В этом разделе нам предстоит поработать над созданием единой точки входа для наших почтовых клиентов.

На оба сервера с haproxy устанавливаем пакет - keepalived:

[All Servers]$ yum install keepalived -y

Для создания схема active/passive напишем две конфига. Конфиг для основного сервера:

[root@exch-lb01]# vim /etc/keepalived/keepalived.conf
---
global_defs {
    enable_script_security
    script_user haproxy
}

vrrp_script haproxy_check {
    script "/usr/bin/systemctl is-active haproxy >/dev/null && echo 1 || echo 0"
    interval 5
}

vrrp_instance EMAIL_VIP1 {
    state MASTER
    interface ens192
    virtual_router_id 233
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass Mypassword
    }
    virtual_ipaddress {
        10.12.8.15
    }
    track_script {
        haproxy_check
    }
}

И конфиг для бекапного сервера:

[root@exch-lb02]# vim /etc/keepalived/keepalived.conf
---
global_defs {
    enable_script_security
    script_user haproxy
}

vrrp_script haproxy_check {
    script "/usr/bin/systemctl is-active haproxy >/dev/null && echo 1 || echo 0"
    interval 5
}

vrrp_instance EMAIL_VIP1 {
    state BACKUP
    interface ens192
    virtual_router_id 233
    priority 75
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass Mypassword
    }
    virtual_ipaddress {
        10.12.8.15
    }
    track_script {
        haproxy_check
    }
}

В обоих конфигурационных файлах у нас имеется одна проверка - haproxy_check, если скрипт в контексте данной проверки не выполняется, то интенс переводится а аварийный режим. И кластерный ip-адрес будет перехвачен резервной нодой.

На нодах в настройках фаервола разрешаем arp:

[All Servers]$ firewall-cmd --permanent --add-rich-rule='rule protocol value="vrrp" accept'
[All Servers]$ firewall-cmd --reload

Ну и запускаем keepalived:

[All Servers]$ systemctl enable --now keepalived

На этом все, постарался понятно и подробно расписать процесс настройки балансировщиков.

Далее я прикрутил экспортера для сбора метрик, в прометеус. Очень удобно оказалось мониторить почту, и видить какие либо проблемы или отклонения по графикам.

И в дополнение все действия по обновлению и настройкам перенес в puppet+gitlab. Теперь когда мне нужно обновить какой либо параметр в конфиге haproxy/keepalived, я просто меняю шаблон конфига в гитлабе и пушу все на сервера. Далее паппет все сам настроит. Очень удобно, так как не надо ходить на какдый сервер и все ручками править.