Балансировка Exchange Server 2019 и корпоративного портала на одном внешнем IP

от автора

Привет, Хабр! На связи Алексей Ежков из из Cloud4Y. Один внешний IPv4, десятки пользователей Exchange и растущий трафик портала — звучит как головоломка? В этой статье я покажу, как мы решили её, заведя всё хозяйство за единственным IP и обеспечив максимальную защиту.

Допустим, в компании «Фирмачка» (доменное имя — firmachka.pro) количество пользователей Exchange Server 2019 перевалило за сотню, а трафик к двум фронтендам корпоративного сайта вырос после маркетинговой кампании. У провайдера — только один публичный IPv4.

Требуется:

  1. Один внешний IP-адрес.

  2. SNI-маршрутизация нескольких DNS-имён.

  3. SSL-offload (TLS 1.2 / 1.3).

  4. Защита от DoS, health-checks, отказоустойчивость для DAG-кластера Exchange и двух веб-узлов.

DNS-записи и сама DAG-инфраструктура настроены корректно и в данной статье не обсуждаются.

Перед началом убедитесь, что:

  • Все необходимые DNS-записи (A-записи для mail, autodiscover, portal, lb) указывают на публичный IP-адрес HAProxy.

  • Порты 25, 80, 443 открыты на файрволе до HAProxy.

  • Exchange DAG и веб-портал настроены и работают корректно во внутренней сети.

  • У вас есть sudo-доступ к серверу с HAProxy.

2. Топология и окружение

• HAProxy ≥ 2.4 из backports/debian-bookworm.
• Exchange Server 2019 CU13 (две ноды DAG).
• Портал на IIS 10 / ASP.NET Core.

3. Откуда берутся файлы *.pem в /etc/ssl/private

3.1. Получаем wildcard-сертификат

Сертификат нужен один — *.firmachka.pro, чтобы покрыть mailautodiscoverportallb и любые будущие имена.

ACME-клиент выпускает wildcard через DNS-01-валидацию:

DOMAIN="firmachka.pro" CERT_DIR="/etc/ssl/private"  certbot certonly \         --agree-tos \         --manual \         --preferred-challenges dns \         -d "*.${DOMAIN}" -d "${DOMAIN}" \         --manual-public-ip-logging-ok  # склейка в формат HAProxy cat /etc/letsencrypt/live/${DOMAIN}/privkey.pem \     /etc/letsencrypt/live/${DOMAIN}/fullchain.pem \     > ${CERT_DIR}/wildcard.${DOMAIN}.pem chmod 600 ${CERT_DIR}/wildcard.${DOMAIN}.pem 

Для автоматического продления wildcard-сертификатов через DNS-01 челлендж обычно используются DNS-плагины Certbot

3.2. DH-параметры

openssl dhparam -out /etc/haproxy/certs/dhparam.pem 2048 

Файл подключается директивой ssl-dh-param-file. Без собственных DH-параметров часть сканеров ругается на «weak prime reuse».

После автоматического продления сертификата выполняйте systemctl reload haproxy, иначе HAProxy продолжит отдавать старую цепочку

4. Полный haproxy.cfg

4.1. global

global     log /dev/log    local0 notice     stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners     stats timeout 30s     user haproxy     group haproxy     daemon      maxconn 20000      ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets prefer-client-ciphers     ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384     ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384      ssl-dh-param-file /etc/haproxy/certs/dhparam.pem     tune.ssl.default-dh-param 2048 

Пояснения построчно:

  • log /dev/log local0 notice — журналируем в rsyslog; уровень notice экономит диск.

  • stats socket — пригодится для Runtime API и атомарной перезагрузки.

  • maxconn 20000 — лимит по памяти: примерно 50 КБ/conn ⇒ ≈1 ГБ RSS.

  • ssl-default-bind-options — отключаем TLS 1.0/1.1, запрещаем session tickets (защита от атак resumption-tracking), навязываем клиенту наш порядок шифров.

  • ssl-default-bind-ciphersuites — suite-ы для TLS 1.3.

  • tune.ssl.default-dh-param — fallback-значение, если ssl-dh-param-file недоступен.

На заметку
Частая ошибка — оставить ssl-default-bind-options без prefer-client-ciphers; тогда старые браузеры выберут RC4.

4.2. defaults

defaults     mode    http     log     global     option  httplog     option  dontlognull     option  forwardfor     option  redispatch     option  http-server-close     retries 3          http-request set-header X-Forwarded-Proto https if { ssl_fc }      timeout connect 10s     timeout client  300s     timeout server  300s     errorfile 403 /etc/haproxy/errors/403.http     errorfile 503 /etc/haproxy/errors/503.http 

Ключевые моменты:

  • option http-server-close — клиенту keep-alive, а на внутренние сервера соединение закрываем после ответа; экономия на TIME_WAIT.

  • forwardfor + set-header X-Forwarded-Proto — OWA корректно формирует ссылки вида https://.

  • Таймауты 300 s подобраны под длительные загрузки вложений в OWA.

На заметку
Забытый option httpclose ведёт к обрыву крупных загрузок через OWA/OWA Light.

4.3. frontend ft_http — порт 80

frontend ft_http     bind *:80     mode http      acl host_portal hdr(host) -i portal.firmachka.pro     use_backend be_portal if host_portal      redirect scheme https code 301 if !host_portal 

Разбираем:

  1. Портал доступен и по HTTP :80 (SEO-требование).

  2. Всё остальное принудительно уходит на HTTPS (301).

4.4. frontend ft_https — порт 443

frontend ft_https     bind *:443 ssl crt /etc/ssl/private/wildcard.firmachka.pro.pem     mode 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; includeSubDomains"      # Защита от DoS     stick-table type ip size 200k expire 30s store http_req_rate(10s)     http-request track-sc0 src     http-request deny status 429 if { sc_http_req_rate(0) gt 100 }      # ---------- ACL ----------     acl host_portal        hdr(host) -i portal.firmachka.pro     acl host_autodiscover  hdr(host) -i autodiscover.firmachka.pro      acl url_ecp path_beg -i /ecp      # !!! ВАЖНО: Укажите вашу административную подсеть !!!     acl admin_network src 192.168.20.0/24     http-request deny if url_ecp !admin_network      # ---------- Маршрутизация ----------     use_backend be_portal_https      if host_portal     use_backend be_exchange_unified  if host_autodiscover      default_backend be_exchange_unified 

Пояснения:

  • Один wildcard-сертификат обслуживает все имена.

  • autodiscover.firmachka.pro направляется в тот же backend, что и mail.

  • Security-заголовки убирают mixed-content и click-jacking.

  • stick-table фильтрует > 100 запросов/10 s ⇒ HTTP 429 Too Many Requests.

  • Защита ECP: доступ только из админской подсети. Exchange не умеет ограничивать сам.

На заметку
• Таблица 200 k на 30 s ≈ 14 MB RAM. Статус в Runtime API: show table ft_https.
• Если нужен IPv6, заведите отдельную stick-таблицу type ipv6.

4.5. frontend ft_smtp — порт 25

frontend ft_smtp     bind *:25     mode tcp          # Ограничение скорости подключений     stick-table type ip size 50k expire 1m store conn_rate(10s)     tcp-request connection track-sc0 src     tcp-request connection reject if { sc_conn_rate(0) gt 10 }          default_backend be_smtp 
  • Layer 4 proxy, никаких STARTTLS-offload; Exchange сам отдаёт сертификат.

  • 10 conns / 10 s → reject (борьба с ботнетами, не влияя на легитимный MX-трафик).

На заметку
Серые листы (greylisting) любят быстро пересоединяться. Подберите лимит под вашу почтовую политику.

4.6. backend be_portal

backend be_portal     mode http     balance roundrobin     option httpchk GET /     http-check expect status 200     server portal01 192.168.20.10:80  check inter 3s rise 2 fall 3     server portal02 192.168.20.11:80  check inter 3s rise 2 fall 3 
  • rise 2 / fall 3 — узел объявляется «здоровым» после двух успешных чеков и «больным» после трёх неудач.

  • Схема round-robin подходит: сессии портала хранятся в Redis-кластер, нет state-stickiness.

4.7. backend be_portal_https

backend be_portal_https     mode http     balance roundrobin     option httpchk GET /     http-check expect status 200     server portal01 192.168.20.10:443 ssl verify none check inter 3s rise 2 fall 3     server portal02 192.168.20.11:443 ssl verify none check inter 3s rise 2 fall 3 
  • Внутренний трафик также шифруется; verify none допустим, так как это изолированная VLAN.

  • Если у портала собственный internal-CA, замените на verify required ca-file /etc/ssl/certs/ca_portal.pem.

4.8. backend be_exchange_unified

backend be_exchange_unified     mode http     balance source     option httpchk GET /owa/healthcheck.htm     http-check expect status 200     server ex01 192.168.0.100:443 ssl verify none check inter 3s rise 2 fall 3     server ex02 192.168.0.101:443 ssl verify none check inter 3s rise 2 fall 3 

Почему balance source?

Exchange клеит сессию OWA к конкретному ClientAccessServer. Sticky-cookie тоже решит задачу, но source-hash проще и не ломает ActiveSync с legacy-устройств.

healthcheck.htm — официальный endpoint; возвращает «200 OK Healthy» без аутентификации.

На заметку
После CU-обновления Microsoft иногда меняет ответ с «Healthy» на «OK». Ловите 503? Сравните тело ответа в curl -k.

4.10. backend be_smtp

backend be_smtp     mode tcp     balance roundrobin     option smtpchk EHLO firmachka.pro     server ex01 192.168.0.100:25 check     server ex02 192.168.0.101:25 check 

option smtpchk посылает EHLO и ждёт кода 250. Это держит порт 25 в «green» даже при частичном падении Exchange.

4.11. listen stats

listen stats     bind *:8404     stats enable     stats uri /stats     stats realm "HAProxy Statistics"     stats auth admin:ChangeMeNow!     stats refresh 30s 
  • Веб-панель по HTTP (не HTTPS). Ограничьте внешним файрволом!

  • stats socket из блока global позволяет вместо веб-GUI пользоваться socat /run/haproxy/admin.sock - и собирать метрики в Prometheus-exporter.


5. Безопасность и производительность

  1. TLS 1.3 + строгий список шифров, SessionTickets отключены.

  2. Один wildcard-сертификат упрощает сопровождение и закрывает все поддомены.

  3. stick-table фильтрует 100 req/10 s или 10 conn/10 s на SMTP.

  4. maxconn 20000 + Linux 5.10 (SO_REUSEPORT) дают 5–6 Гбит/с без CPU-узких мест. Опция SO_REUSEPORT позволяет HAProxy эффективнее использовать несколько ядер CPU, распределяя входящие соединения между несколькими рабочими процессами (worker processes) HAProxy, если они запущены.

  5. Health-checks раз в 3 s обеспечивают SLA ≥ 99.9 % без избыточного сетевого шума.


6. Тестирование

haproxy -c -f /etc/haproxy/haproxy.cfg          # проверка синтаксиса curl -k https://lb.firmachka.pro/owa/healthcheck.htm  # Проверяем доступность и работоспособность Health Check страницы Exchange OWA curl -k https://autodiscover.firmachka.pro/autodiscover/autodiscover.xml -H "Content-Type:text/xml"  # Имитируем запрос к службе Autodiscover Exchange через балансировщик swaks --to recipient@example.com --from sender@firmachka.pro --server mail.firmachka.pro --port 25 -ehlo test  # Тестирование работы SMTP-сервера openssl s_client -connect lb.firmachka.pro:443 -servername portal.firmachka.pro \    # Получаем информацию о выбранной версии TLS и наборе шифров         -tlsextdebug -status | grep -E 'Selected|TLSv' 

На заметку
Если openssl сообщает New, TLSv1.2, а не TLSv1.3, проверьте версию OpenSSL на клиенте.

7. Эксплуатация

  • Горячий reload: haproxy -f /etc/haproxy/haproxy.cfg -c && systemctl reload haproxy.

  • Runtime-API: echo "show table ft_https" | socat stdio /run/haproxy/admin.sock.

  • Мониторинг: github.com/prometheus/haproxy_exporter читает stats socket.


8. Частые проблемы

Симптом

Причина

Решение

503 на OWA после продления wildcard-серта

забыли reload

systemctl reload haproxy

401/403 при входе в /ecp

ACL admin_network не совпадает с новой VPN-подсетью

скорректировать CIDR

Серый список SMTP режет доставку

stick-table слишком строг

увеличить лимит conn_rate


9. Итог

Мы получили:

  • Wildcard-сертификат *.firmachka.pro, покрывающий все сервисы.

  • SNI-маршрутизацию четырёх DNS-имён (mailautodiscoverportallb) на одном IP.

  • SSL-offload + TLS 1.3 без компромиссов в безопасности.

  • Отказоустойчивые Exchange 2019 DAG и веб-кластер за одним фронтом.

  • Базовую DoS-защиту и удобный мониторинг без лишнего железа.

Официальная документация для углубления:


ссылка на оригинал статьи https://habr.com/ru/articles/933070/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *