Несколько хостов FreeIPA за HTTP-proxy: настраиваем HAProxy 2+
Проблема
У нас есть три хоста административной панели FreeIPA (freeipa01.inside.mydomain.ru, freeipa02.inside.mydomain.ru и freeipa03.inside.mydomain.ru). Необходимо обеспечить доступ к ним по одному доменному имени: freeipa.mydomain.ru. При кажущейся простоте задачи, для ее решения пришлось приложить некоторые усилия, поскольку в Интернете не нашлось готовых рецептов для HAProxy версии 2.0 и выше.
Каждая инсталляция FreeIPA привязана к своему доменному имени, а значит нам потребуется править заголовки входящих и исходящих HTTP-запросов. Портал самообслуживания должен быть закрыт действующим сертификатом, при этом работающие в бэкенде хосты FreeIPA не должны изменяться, чтобы не затронуть взаимодействие между клиентами и серверами через API.
В старых версиях HAProxy (1+) для редактирования HTTP-заголовков использовался метод reqrep/rsprep. В сети есть инструкции по настройке HAProxy и FreeIPA с помощью этого метода, но в версии 2.0 он был помечен как deprecated, а в версии 2.1 полностью выведен из использования. Вместо rsprep мы будем использовать метод http-response.
Решение
Для начала отредактируем созданный по умолчанию конфигурационный файл HAProxy. В нем можно выделить четыре секции: global, defaults, frontend и backend. Первые две мы трогать не будем (достаточно стандартных значений), а вот frontend и backend опишем подробно:
#Секция frontend
frontend main
bind :80
redirect scheme https code 301 if !{ ssl_fc } # редиректим на https
frontend main_ssl
bind :443 ssl crt /etc/haproxy/ssl/ # используем сертификаты из директории
use_backend freeipa if { ssl_fc_sni freeipa.mydomain.ru } # в случае если обращаются к freeipa.mydomain.ru используем backend FreeIPA
#Секция backend
backend freeipa
mode http
balance roundrobin # по очереди распределяем нагрузку по хостам
cookie SERVERID insert indirect nocache httponly secure # добавляем cookie для направления трафика на основе него
#acl для request на основе добавленного cookei
acl hdr_req_ipa01 req.hdr(Cookie) -m sub ipa01
acl hdr_req_ipa02 req.hdr(Cookie) -m sub ipa02
acl hdr_req_ipa03 req.hdr(Cookie) -m sub ipa03
#--------------------------------------------------------------------------
#В зависимости от того, каким cookie помечен наш запрос, изменяем заголовки Host и Referer
http-request set-header Host freeipa01.inside.mydomain.ru if hdr_req_ipa01
http-request replace-header Referer ^https?://freeipa\.mydomain\.ru(.*)$ https://freeipa01\.inside\.mydomain\.ru\1 if hdr_req_ipa01
http-request set-header Host freeipa02.inside.mydomain.ru if hdr_req_ipa02
http-request replace-header Referer ^https?://freeipa\.mydomain\.ru(.*)$ https://freeipa01\.inside\.mydomain\.ru\1 if hdr_req_ipa02
http-request set-header Host freeipa03.inside.mydomain.ru if hdr_req_ipa03
http-request replace-header Referer ^https?://freeipa\.mydomain\.ru(.*)$ https://freeipa01\.inside\.mydomain\.ru\1 if hdr_req_ipa03
#--------------------------------------------------------------------------
#acl для response на основе заголовка Location
acl hdr_ipa01 res.hdr(Location) -m sub freeipa01.inside.mydomain.ru
acl hdr_ipa02 res.hdr(Location) -m sub freeipa02.inside.mydomain.ru
acl hdr_ipa03 res.hdr(Location) -m sub freeipa03.inside.mydomain.ru
#--------------------------------------------------------------------------
#В зависимости от того с какого хоста пришел ответ редактируем заголовки Set-Cookie и Location Без редактирования заголовка Location мы столкнемся со следующей проблемой: пользователь при переходе по ссылке freeipa.mydomain.ru будет переброшен на один из хостов freeipa0x.inside.mydomain.ru (это важный момент пропущенный во всех найденных руководствах)
http-response replace-header Set-Cookie ^Domain=freeipa01\.inside\.mydomain\.ru(.*) Domain=freeipa\.mydomain\.ru\1 if hdr_ipa01
http-response replace-value Location ^https?://freeipa01\.inside\.mydomain\.ru(.*)$ https://freeipa\.mydomain\.ru\1 if hdr_ipa01
http-response replace-header Set-Cookie ^Domain=freeipa02\.inside\.mydomain\.ru(.*) Domain=freeipa\.mydomain\.ru\1 if hdr_ipa02
http-response replace-value Location ^https?://freeipa02\.inside\.mydomain\.ru(.*)$ https://freeipa\.mydomain\.ru\1 if hdr_ipa02
http-response replace-header Set-Cookie ^Domain=freeipa03\.inside\.mydomain\.ru(.*) Domain=freeipa\.mydomain\.ru\1 if hdr_ipa03
http-response replace-value Location ^https?://freeipa03\.inside\.mydomain\.ru(.*)$ https://freeipa\.mydomain\.ru\1 if hdr_ipa03
#--------------------------------------------------------------------------
#Здесь указываем наши хосты FreeIPA
server ipa01 freeipa01.inside.mydomain.ru:443 check port 443 inter 5s rise 2 fall 5 cookie ipa01 weight 9 ssl verify none
server ipa02 freeipa02.inside.mydomain.ru:443 check port 443 inter 5s rise 2 fall 5 cookie ipa02 weight 1 ssl verify none
server ipa03 freeipa03.inside.mydomain.ru:443 check port 443 inter 5s rise 2 fall 5 cookie ipa03 weight 3 ssl verify none
#check port 443 - проверяем жив ли хост по 443 порту.
#inter 5s - проверяем доступность с интервалом 5 секунд.
#rise 2 fall 5 - если 2 раза проверка скажет что хост недоступен он будет исключен из балансировки и возвращен после 5 успешных проверок
#cookie ipa0x - указывает какое cookie будет добавляться “cookie SERVERID insert”
#ssl verify none - терминация SSL-сертификата игнорируя ошибки
#weight 3 указываем приоритет распределения нагрузки
Также вы можете столкнуться с надоедливым окном базовой авторизации в браузерах Chrome, Edge, IE и некоторых других.

Появление этого окна описано в багрепорте и там же есть решение проблемы, но при помощи HAProxy ее можно обойти без изменения конфигурации хостов. Для этого добавим в секцию backend конфигурационного файла следующую строку:
http-response del-header www-authenticate
Она удалит из ответа хоста заголовок, ответственный за появление навязчивого окна.