Разворачивание HAproxy и настройка единой точки входа для RDP на основе ACL
Данная статья вкратце рассказывает, как реализовать единую точку входа, используя HAproxy и ACL для RDP-серверов.
Всё делалось на базе Ubuntu Server. На других дистрибутивах Linux могут потребоваться дополнительные шаги по установке.
Данная страница служит напоминанием автору, как это реализовано, для быстрого развёртывания подобного на другом оборудовании и отладки работы уже существующих вариантов.
Установка HAproxy
Для быстрой установки HAproxy перейдите на этот сайт и выберите, какая у вас ОС и какая версия HAproxy вас интересует. Лучше всего выбирать версию 3.0 LTS.

Получите готовый набор команд для установки в терминале.
Первый и самый простой этап выполнен. Переходим к этапу настройки Haproxy.
Конфигурация HAproxy
После установки перейдите в конфиг HAproxy для настройки. Он находится по пути /etc/haproxy/haproxy.cfg
Пример конфига для единой точки входа:
Замените #IPrds* на IP своего RDS
global log 127.0.0.1 local0 log 127.0.0.1 local1 notice maxconn 1000 user haproxy group haproxy daemon stats socket /tmp/haproxy.sock ssl-server-verify none defaults mode tcp option tcplog option tcp-smart-accept option tcp-smart-connect frontend ft_rdp mode tcp bind *:3389 name rdp #timeout client 1h persist rdp-cookie log global option tcplog tcp-request inspect-delay 2s tcp-request content accept if RDP_COOKIE acl remoteRDSH01_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDSH01 acl remoteRDSH02_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDSH02 acl remoteRDSH03_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDSH03 acl remoteRDSH04_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDSH04 acl remoteRDS01_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDS01 acl remoteRDS02_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDS02 acl remoteRDS03_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDS03 acl remoteRDS04_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDS04 acl remoteRDS05_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDS05 acl remoteRDS06_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDS06 use_backend remoteRDSH01_BK if remoteRDSH01_ACL use_backend remoteRDSH02_BK if remoteRDSH02_ACL use_backend remoteRDSH03_BK if remoteRDSH03_ACL use_backend remoteRDSH04_BK if remoteRDSH04_ACL use_backend remoteRDS01_BK if remoteRDS01_ACL use_backend remoteRDS02_BK if remoteRDS02_ACL use_backend remoteRDS03_BK if remoteRDS03_ACL use_backend remoteRDS04_BK if remoteRDS04_ACL use_backend remoteRDS05_BK if remoteRDS05_ACL use_backend remoteRDS06_BK if remoteRDS06_ACL default_backend DF_RDP backend remoteRDSH01_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS02 #IPrds1:3389 weight 10 check backend remoteRDSH02_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS03 #IPrds2:3389 weight 10 check backend remoteRDSH03_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS04 #IPrds3:3389 weight 10 check backend remoteRDSH04_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS05 #IPrds4:3389 weight 10 check backend remoteRDS01_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS06 #IPrds5:3389 weight 10 check backend remoteRDS02_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS07 #IPrds6:3389 weight 10 check backend remoteRDS03_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS08 #IPrds7:3389 weight 10 check backend remoteRDS04_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS09 #IPrds8:3389 weight 10 check backend remoteRDS05_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS10 #IPrds9:3389 weight 10 check backend remoteRDS06_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS11 #IPrds10:3389 weight 10 check backend DF_RDP mode tcp balance leastconn log global option tcplog stick-table type string len 32 size 10k expire 8h stick on rdp_cookie(mstshash),bytes(0,6) option tcp-check tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS01 #IPrds11:3389 weight 10 check listen stats bind *:9000 mode http stats enable #stats hide-version stats show-node stats realm Haproxy\ Statistics stats uri /
Теперь давайте объясню главные моменты, что написано в этом файле.
acl remoteRDSH##_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDSHxx
Данная команда обозначает, что для RDS## требуется RDP-куки, и она смотрит первые 20 байтов и сравнивает их с файлом ACL.
use_backend remoteRDSHxx_BK if remoteRDSHxx_ACL
Обозначает, что если RDP-куки нашлась в ACL, то перенаправить на нужный RDS.
Так же в конфиге есть DF_RDP
. Он нужен, чтобы отправлять всех пользователей, которых нет ни в одном ACL, туда. Так же, если его не будет, то мы не сможем подключиться. Поэтому можете написать IP, которого нет. Костыль? Да :)
И самое последнее. Если вы откроете браузер и перейдёте на IP:9000 машины, где крутится HAproxy, вы получите статистику.

Создание ACL для HAproxy
Стоит учесть, что файлы ACL /etc/haproxy/remoteRDSHxx
ДОЛЖНЫ быть созданы ЗАРАНЕЕ. Иначе HAproxy откажется запускаться. Файлы могут быть полностью пустыми. Так что для простоты просто используйте команду:
touch remoteRDSHxx
В RDP-куки НЕ передаётся имя пользователя ЕСЛИ ОНО НА РУССКОМ. Создавайте пользователей ТОЛЬКО на английском.
Редактировать ACL "на горячую" нельзя. Изменения вступят в силу лишь ПОСЛЕ перезапуска HAproxy.
sudo systemctl restart haproxy
Заполнение ACL и как это делать правильно. Стоит сразу определить, что используют пользователи:
- Windows со стандартным RDP-клиентом / "Толстый" клиент
- Linux и его зоопарк RDP-клиентов / "Тонкий" клиент
Windows
Если у нас ПК не в домене и подключение происходит просто по имени пользователя, то проблем у нас нет. Можем спокойно открывать файлы с ACL и пишем всех пользователей с новой строчки. Как тут:
admin 1060Admin 960Admin
Если же у нас ПК в домене, то стоит учесть, стандартный RDP-клиент Windows "брезгует" и при подключении к HAproxy не передаёт ИМЯ пользователя, а первые 9 СИМВОЛОВ. Если же проворачивать такое напрямую, то клиент будет передавать имя пользователя.
Есть два решения данной проблемы:
- Использовать АЛЬТЕРНАТИВНЫЙ клиент RDP под Windows (как пример, mRemoteNG или Parallels Client)
- Сделать костыль со стороны ACL (быстрое решение проблемы, но неправильное)
С первым вариантом вроде бы всё понятно. А второй требует внимания. В ACL мы будем писать не просто имя пользователя, а по вот такой форме:
Имя_Пользователя@Домен
Но мы должны оставить ТОЛЬКО 9 символов. Это лишь быстрое решение проблемы, стоит рассмотреть вариант смены RDP-клиента.
Linux
Если же у нас Linux-машина и/или "тонкий" клиент, то мы можем спокойно писать имя пользователя, не важно, в домене ли ПК или нет. Клиенты под Linux передают столько символов, сколько напишут, и ТОЛЬКО имя пользователя, даже если пользователь писал домен.

Процесс добавления новых пользователей в ACL
Данный пункт предназначен для небольшой группы лиц. :)
Итак, боец, тебе требуется добавить нового пользователя в ACL, чтобы тот мог пользоваться единой точкой входа?
Краткая информация:
IP: 10.10.76.103 / 10.10.76.11 Пользователь: user Пароль: Наш стандартный :)
Подключаемся к серверу по SSH.
Все ACL лежат по пути /etc/haproxy/
.
Все имена РДС совпадают с именами в AD.
План действий по добавлению пользователя:
- Узнаем, на каком из РДС будет работать пользователь.
- Получаем имя учетной записи пользователя из AD.
- Открываем через nano ACL нужного РДС и заносим нового пользователя с новой строки в самом конце файла.
sudo nano /etc/haproxy/remoteRDSXX
В терминале работает автодополнение по клавише TAB
- Сохраняем файл и выходим.
Ctrl + s
Ctrl + x
- Перезапускаем HAproxy.
sudo systemctl restart haproxy
- Profit!
Процесс добавления нового сервера в конфиг HAproxy
Данный пункт предназначен для небольшой группы лиц. :)
Итак, боец, тебе требуется добавить новый РДС сервер в HAproxy?
Краткая информация:
IP: 10.10.76.103 / 10.10.76.11 Пользователь: user Пароль: Наш стандартный :)
Подключаемся к серверу по SSH.
Конфиг прокси лежит по пути /etc/haproxy/haproxy.cfg
План действий по добавлению сервера. Пример добавления RDS07:
- Перед тем как лезть руками в конфиг, сделайте его копию.
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.old
- Открывает файл через nano.
sudo nano /etc/haproxy/haproxy.cfg
- В файле находим блок
frontend ft_rdp
. Там будет блок, начинающийся на:
acl remoteRDSXX_ACL
- По образу и подобию пишем такую же строчку, только заменяем его на свое имя. Пример:
acl remoteRDS07_ACL req.rdp_cookie(mstshash),bytes(0,19) -m str -i -f /etc/haproxy/remoteRDS07


- Опускаемся ниже и находим блок команд, начинающийся на:
use_backend remoteRDSXX_BK
- По образу и подобию пишем такую же строчку, только заменяем его на свое имя. Пример:
use_backend remoteRDS07_BK if remoteRDS07_ACL

- Самое последнее. Опускаемся почти до самого конца файла до строчки:
backend DF_RDP
- Перед этой строчкой делаем по образу других, пишем вот такой код:
backend remoteRDS07_BK mode tcp balance leastconn log global stick-table type string len 32 size 10k expire 8h stick on req.rdp_cookie(mstshash),bytes(0,19) option tcplog tcp-check connect port 3389 default-server inter 3s rise 2 fall 3 server TS11 10.10.76.55:3389 weight 10 check
- Изменить
remoteRDS07_BK
на свое имя. - Изменить
TS11
наTSxx+1
. - Изменить IP на свой.
- Изменить

- Сохраняем файл и выходим.
Ctrl + s
Ctrl + x
- Создать файл ACL. Пример:
sudo touch /etc/haproxy/remoteRDS07
- Перезапускаем HAproxy.
sudo systemctl restart haproxy
- Profit!
План отката:
- Копируем наш старый конфиг на место сломанного.
sudo cp /etc/haproxy/haproxy.cfg.old /etc/haproxy/haproxy.cfg
- Перезапускаем HAproxy.
sudo systemctl restart haproxy
- Profit!
Итог
Если теперь подключиться через RDP-клиент к серверу, где мы развернули HAProxy, нас должно перенаправить на один из RDS на основе ACL. HAProxy почти не потребляет ресурсов. Виртуалки с 2 ГБ ОЗУ и одним процессором должно хватить.
- Плюсы данного решения:
- Единая точка входа на множество серверов.
- Слабое потребление ресурсов.

- Минусы данного решения:
- Слабая отказоустойчивость. Если один из серверов не работает, то HAProxy будет до последнего перенаправлять на этот сервер.
- Стандартный RDP-клиент в Windows некорректно работает с таким решением.