Несколько хостов FreeIPA за HTTP-proxy: настраиваем HAProxy 2+
Путь инженера в телекоме часто начинается со службы технической поддержки. Если вы хотите вырастить из новичков высококлассных специалистов, нужно дать им возможность работать над выходящими за рамки служебных обязанностей задачами. Мы стараемся помогать активным коллегам в развитии — это один из главных принципов HOSTKEY с момента основания компании. Публикуем заметку о реализации проксирования административной панели FreeIPA через HAProxy, написанную нашим инженером техподдержки Александром Тряпкиным.
(А здесь можно прочесть, как синхронизировать FreeIPA с Active Directory).
Проблема
У нас есть три хоста административной панели 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
Она удалит из ответа хоста заголовок, ответственный за появление навязчивого окна.
Итоги
Сравнив решение задачи с помощью rsprep и через http-response, можно лучше разобраться в логике HAProxy и научиться работать с HTTP-запросами на более глубоком уровне.
_________
Как видите, поддерживать инициативу инженеров технической поддержки полезно для бизнеса. Мы не только помогаем специалистам развиваться и делать карьеру (что опять-таки выгодно работодателю), но и получаем полезные клиентам прикладные разработки, а также интересные заметки для корпоративного блога. Надеемся, наше решение пригодится читателям.
А специальный промокод «Я С ХАБРА» тоже может пригодиться: назовите его консультанту на сайте при размещении заказа, чтобы получить дополнительную скидку. Платить можно как всегда в рублях с НДС российской компании или в евро — компании в Нидерландах.