В этой статье я расскажу как в условиях большого разнообразия настроек подключения к инфраструктуре и большого внутреннего штата организовать контролируемую единую точку входа к удалённым рабочим столам на серверах заказчиков через Apache Guacamole.
Проблемы, которые решает пример в статье:
- Централизованный доступ внутренних пользователей к удалённым рабочим столам на серверах заказчиков (всё разнообразие заказчиков в одной системе);
- Доступ к VPN заказчика есть только у администраторов системы (сильно снижаем вероятность утечки настроек доступа);
- Администрирование доступа к подключениям (администратор самостоятельно разрешает и блокирует доступ к серверам заказчиков, нет необходимости обращаться к IT-службе заказчика);
- История сессий остаётся в системе (кто подключился и как долго был подключен). Также есть возможность записывать сеансы (не рассматривается в рамках статьи).
Для упрощения статьи, я буду описывать вариант подключения к RDP через OpenVPN (из-за широкого распространения), но подготовленный репозиторий позволяет организовать также подключения через IKEv2, IPSEC/L2TP, WireGuard, FortiGate SSL-VPN и Kerio Control VPN. Сам механизм проброса портов при этом не привязан к RDP, через него можно организовать подключение к RemoteApp, VNC, SSH, Telnet и другим TCP-сервисам.
На момент написания, все используемые в статье дистрибутивы являются свободно распространяемые (бесплатные) и поддерживаемые сообществом (обновляемые):
- Docker — платформа с открытым исходным кодом для автоматизации разработки, доставки и развёртывания приложений в средах с поддержкой контейнеризации. Позволяет «упаковать»»» приложение со всем окружением и зависимостями в изолированный контейнер, а затем запустить его в целевой системе.
- Portainer.IO CE (Community Edition) — универсальный веб-интерфейс для управления контейнерными технологиями, в первую очередь Docker. Это мост между сложным CLI-интерфейсом Docker и пользователем, предоставляющий графическую оболочку для выполнения всех необходимых операций.
- Nginx Proxy Manager (NPM) — это инструмент для управления обратными прокси-серверами на основе веб-сервера Nginx. Он разработан, чтобы упростить настройку прокси-хостов, SSL-сертификатов и настроек безопасности.
- Apache Guacamole — бесклиентный шлюз удалённого рабочего стола с открытым исходным кодом. Позволяет пользователям управлять удалёнными компьютерами, серверами и виртуальными машинами напрямую через веб-браузер. При этом на стороне пользователя не требуется устанавливать дополнительные программы, плагины или расширения.
- OpenVPN — это кроссплатформенный инструмент для безопасного туннелирования IP-сетей через единственный UDP или TCP-порт. Программа поддерживает широкий спектр конфигураций, динамических IP-адресов и NAT, а также позволяет настраивать удаленный доступ и VPN-соединения типа «точка-точка».
Реализовывать я буду это на сервере (хосте) с ОС Ubuntu 24.04 LTS с внешним IP (для организации доступа к сервису Apache Guacamole).
1. Установка Docker
Все инструменты, которые я планирую использовать в статье, удобнее устанавливать через контейнеры, но для этого на хосте требуется установить Docker.
Обновляем список пакетов и устанавливаем все доступные обновления системы.
sudo apt update && sudo apt upgrade -yCode language: Bash (bash)Устанавливаем необходимые системные пакеты (сертификаты, curl, GPG и инструменты определения версии ОС), которые требуются для добавления официального репозитория Docker.
sudo apt install -y ca-certificates curl gnupg lsb-releaseCode language: Bash (bash)Добавляем официальный репозиторий Docker, сохранив его GPG-ключ в /etc/apt/keyrings/, чтобы система могла безопасно устанавливать пакеты Docker.
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullCode language: Bash (bash)Обновляем индекс пакетов, чтобы система увидела новые репозитории и доступные версии пакетов.
sudo apt update
Code language: Bash (bash)Устанавливаем Docker Engine и связанные компоненты (CLI, containerd, Buildx и Compose) из официального репозитория Docker.
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginCode language: Bash (bash)По окончании установки сервис Docker должен автоматически запуститься. Проверяем, что сервис Docker успешно запущен и работает корректно.
sudo systemctl status dockerCode language: Bash (bash)

2. Установка Portainer.IO
Управлять контейнерами в командной строке можно, но как по мне, гораздо удобнее это делать в специальном менеджере.
Для этого создадим каталог, где разместим docker-compose.yml — это файл конфигурации, описывающий, какие контейнеры запускать и как они должны работать вместе.
sudo mkdir /docker-compose-data/portainer # Создаём каталог
cd /docker-compose-data/portainer # Переходим в созданный каталог
sudo echo "" > docker-compose.yml # Создаём в каталоге пустой файл конфигурацииCode language: Bash (bash)Файл конфигурации надо будет заполнить.
services:
portainer: # Название контейнера внутри файла конфигурации
image: portainer/portainer-ce:latest # Используемый для разворачивания образ. В продуктовых сервисах, лучше указать конкретную версию вместо latest (последнюю доступную), чтобы избежать случайного обновления
container_name: portainer # Название контейнера в системе
restart: unless-stopped # Контейнер никогда не остановиться
ports:
- "9000:9000" # Внешний порт (порт, который виден на хосте) : Внутренний порт (порт внутри контейнера, с которым связан внешний порт)
volumes:
- /var/run/docker.sock:/var/run/docker.sock # Проброс Docker-сокета внутрь контейнера, чтобы Portainer мог напрямую управлять Docker на хосте (контейнерами, образами, томами и сетями)
- portainer_data:/data # Хранилище, которое доступно внутри контейнера в виде каталога /data
networks: [proxy] # Указываем, что наш контейнер подключен к сети с именем "proxy"
volumes:
portainer_data: # Объявляем, что используем хранилище (на хосте это хранилище будет расположено в /var/lib/docker/volumes/portainer_data/_data/)networks:
networks:
proxy: # Объявляем, что используем эту сеть
external: true # и она внешняя
Code language: Dockerfile (dockerfile)В файле мы указали, что сервис portainer подключен к сети proxy, но этой сети ещё нет в нашем докере. Исправим это:
docker network create proxyCode language: Bash (bash)Теперь можно запустить наш Portainer.IO и начать управление контейнерами через него:
sudo docker compose up -dCode language: Bash (bash)Убедится, что Portainer.IO запустился и работает, можно командой:
sudo docker ps

Если сервис запущен, то пора к нему подключаться. На компьютере, где доступен хост по IP, в браузере набираем http://X.Y.W.Z:9000(где X.Y.W.Z — это IP-адрес хоста, а 9000 — внешний порт, который указан в конфигурационном файле). Нас встретит окно установки пароля администратора, вводим и запоминаем (в целях безопасности, время установки пароля ограничено, чтобы запустить процедуру ещё раз, перезапустите сервис).

Переходим в локальный докер и видим пока что только один стек (это наш portainer).

Теперь пора установить Nginx Proxy Manager (NPM).
3. Установка Nginx Proxy Manager
В списке Stacks в Portainer.IO добавляем новый стек (кнопка «+ Add stack»)
- Указываем название стека npm
- Build method: Web editor
- Содержимое: <ниже в блоке>
- Access control: Enable access control: Administrators
- Actions: Deploy the stack.
Содержимое настроечного файла стека:
services:
app:
image: 'docker.io/jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80' # Порт HTTP
- '81:81' # Порт админ-панели
- '443:443' # Порт HTTPS
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks: [proxy] # В одной сети с portainer
networks:
proxy:
external: trueCode language: Dockerfile (dockerfile)После того, как Deploy завершится, в списке Stacks появится NPM. Зайдём теперь в панель администрирования NPM, для этого в браузере перейдём на адрес http://X.Y.W.Z:81 (где X.Y.W.Z — это IP-адрес хоста, а 81— внешний порт, который указан в конфигурации стека NPM). В первом окне система попросить установить логин и пароль администратора.
Чтобы не оперировать IP адресами надо объявить DNS для наших сервисов.
Как и говорилось ранее, хост должен иметь внешний (белый) IP, а значит и DNS записи вы наверняка должны понимать как к нему прописать, но если вы вдруг (как и я) делаете этот пример на виртуальной машине, то можно такие имена прописать в hosts файле.
Например:
- npm.my.host — X.Y.W.Z (где X.Y.W.Z — это внешний IP-адрес хоста)
- portainer.my.host — X.Y.W.Z (где X.Y.W.Z — это внешний IP-адрес хоста)
И прописать их в NPM, чтобы адреса перенаправлялись на нужные сервисы:

- Domain Names: portainer.my.host (то имя, которое указано в DNS для сервиса portainer)
- Scheme: http
- Forward Hostname / IP: portainer (имя сервиса в докере, оно будет доступно для NPM, т.к. они в одной сети proxy)
- Forward Port: 9000 (порт из настроечного файла portainer)

- SSL Certificate: Request a new Certificate
- Force SSL: True
- HTTP/2 Support: True
4. Установка Apache Guacamole
Теперь установим сам Apache Guacamole. Для авторизации в примере буду использовать встроенную авторизацию Guacamole через PostgreSQL. Пользователи, группы, подключения и права доступа будут храниться в его базе данных, никаких внешних LDAP, Active Directory или SSO для этого примера не требуется.
В типовой Docker-установке Guacamole состоит из трёх частей: веб-приложения guacamole, сервиса guacd, который непосредственно устанавливает RDP/SSH/VNC-соединения, и базы PostgreSQL. В нашем случае guacd дополнительно подключим к сети guac-vpn-transit, через которую он позже будет видеть VPN-контейнеры.
Сначала создадим отдельную Docker-сеть для взаимодействия guacd с VPN-контейнерами.
docker network create guac-vpn-transitCode language: Bash (bash)
Сеть создаётся один раз. В неё не надо подключать PostgreSQL и веб-интерфейс Guacamole — только guacd и контейнеры с VPN. Сеть proxy у нас уже создана ранее для работы через Nginx Proxy Manager.
В Portainer.IO создаём новый Stack для Apache Guacamole.
- Stacks → Add stack
- Name: guacamole
- Build method: Web editor
- Access control: Enable access control: Administrators
Перед разворачиванием стека в разделе Environment variables добавим переменную POSTGRES_PASSWORD и зададим для неё сложный пароль. В сам текст стека пароль не вставляем.
Содержимое настроечного файла стека:
services:
guac-init:
image: guacamole/guacamole:1.6.0
entrypoint: ["/bin/sh", "-c"]
command:
- cp /opt/guacamole/extensions/guacamole-auth-jdbc/postgresql/schema/*.sql /initdb/
volumes:
- guac_init:/initdb
restart: "no"
guac-db:
image: postgres:17-alpine
container_name: guac-db
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- guac_db:/var/lib/postgresql/data
- guac_init:/docker-entrypoint-initdb.d:ro
depends_on:
guac-init:
condition: service_completed_successfully
healthcheck:
test: ["CMD-SHELL", "pg_isready -U guacamole_user -d guacamole_db"]
interval: 10s
timeout: 5s
retries: 5
networks:
- guac-internal
guacd:
image: guacamole/guacd:1.6.0
container_name: guacd
restart: unless-stopped
networks:
- guac-internal
- guac-vpn-transit
guacamole:
image: guacamole/guacamole:1.6.0
container_name: guacamole
restart: unless-stopped
environment:
GUACD_HOSTNAME: guacd
GUACD_PORT: 4822
POSTGRESQL_ENABLED: "true"
POSTGRESQL_HOSTNAME: guac-db
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USERNAME: guacamole_user
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
WEBAPP_CONTEXT: ROOT
REMOTE_IP_VALVE_ENABLED: "true"
depends_on:
guac-db:
condition: service_healthy
guacd:
condition: service_started
networks:
- guac-internal
- proxy
volumes:
guac_db:
guac_init:
networks:
guac-internal:
driver: bridge
proxy:
external: true
guac-vpn-transit:
external: true
Code language: Dockerfile (dockerfile)
Контейнер guac-init нужен только при первом разворачивании: он копирует стандартную схему базы данных Guacamole в отдельный volume. PostgreSQL при создании новой базы автоматически выполняет эти SQL-файлы. Сама база хранится в volume guac_db, поэтому перезапуск или пересоздание контейнеров guacamole и guacd не удаляет пользователей и настройки подключений.
guacd специально не публикуем наружу через ports:. У него нет собственной авторизации, поэтому доступ к нему должен оставаться только внутри Docker-сетей.
Нажимаем Deploy the stack. После запуска должны появиться контейнеры guacamole, guacd и guac-db. Контейнер guac-init после выполнения своей задачи остановится — это нормально.
Теперь опубликуем Guacamole через уже установленный Nginx Proxy Manager.
- Domain Names: guac.my.host
- Scheme: http
- Forward Hostname / IP: guacamole
- Forward Port: 8080
- Websockets Support: True
- Block Common Exploits: True
В разделе SSL запрашиваем сертификат, включаем Force SSL и HTTP/2 Support. Благодаря параметру WEBAPP_CONTEXT=ROOT Guacamole будет открываться просто по адресу https://guac.my.host/, без дополнительного /guacamole/ в конце.
При первом входе используем стандартную учётную запись администратора.
Login: guacadmin
Password: guacadminCode language: HTTP (http)
Первым делом меняем пароль администратора. Дальше уже можно создавать обычных пользователей непосредственно в интерфейсе Guacamole: Settings → Users → New User. Например, создадим пользователя ivan.petrov и дадим ему доступ только к тем подключениям, которые ему действительно нужны.
В рабочей системе стандартный пароль guacadmin оставлять нельзя. Он нужен только для первого входа после инициализации базы.
Подключения в Guacamole можно создавать уже сейчас, даже если VPN-контейнеры мы развернём только в следующем разделе. Пока соответствующего VPN-контейнера нет, подключение просто не сможет установить RDP-сессию.
Для примера создадим три вымышленных RDP-подключения к серверам двух организаций.
У ООО Ромашка будет один RDP-сервер. Эта организация подключается через OpenVPN:
- Name: ООО Ромашка — RDS
- Protocol: RDP
- Hostname: vpn-romashka
- Port: 3389
- Username: demo-rdp
- Password: ExampleOnly!2026
У ООО Василёк будет два RDP-сервера, доступных через одно L2TP/IPsec-подключение. Для первого сервера создадим:
- Name: ООО Василёк — 1С RDS
- Protocol: RDP
- Hostname: vpn-vasilek
- Port: 3389
- Username: demo-user
- Password: ExampleOnly!2026
Для второго сервера:
- Name: ООО Василёк — Администрирование
- Protocol: RDP
- Hostname: vpn-vasilek
- Port: 3390
- Username: demo-admin
- Password: ExampleOnly!2026
Обратите внимание: для двух серверов ООО Василёк используется одно и то же имя vpn-vasilek, но разные порты — 3389 и 3390. В следующем разделе один VPN-контейнер будет перенаправлять эти порты на два разных RDP-сервера внутри сети организации.
Имена vpn-romashka и vpn-vasilek — это не DNS-имена серверов заказчиков. Это имена VPN-контейнеров в нашей общей Docker-сети guac-vpn-transit. Логины и пароли RDP здесь тоже вымышленные. В рабочей системе используем реальные учётные данные удалённых серверов и выдаём пользователям Guacamole доступ только к нужным подключениям.
На этом сам Guacamole готов: пользователи авторизуются в нём локально, настройки подключений лежат в PostgreSQL, веб-интерфейс доступен через NPM, а guacd уже подключен к отдельной внутренней сети, в которой дальше появятся VPN-контейнеры.
5. Установка VPN соединения
Чтобы не собирать для каждого заказчика отдельный контейнер вручную, я вынес используемые мной VPN-forwarder в отдельный репозиторий docker-vpn-forwarders. Идея простая: одно VPN-подключение — один отдельный контейнер, в котором поднимается VPN и публикуются только нужные TCP-порты во внутреннюю сеть Docker.
Важно: контейнеры из репозитория специально не публикуют RDP-порты на хост. Подключиться к ним напрямую по внешнему IP нашего Docker-хоста нельзя. Доступ получает только guacd (или другой контейнер), который мы сами подключили к сети guac-vpn-transit.
Чтобы пример был ближе к реальной эксплуатации, настроим сразу две вымышленные организации. ООО Ромашка подключается через OpenVPN и имеет один RDP-сервер. ООО Василёк подключается через L2TP/IPsec PSK и имеет два RDP-сервера. Для каждой организации создадим свой Stack, свой каталог с секретами и свой VPN-контейнер.
Проверим, что общая Docker-сеть для guacd и VPN-контейнеров уже существует.
docker network inspect guac-vpn-transitCode language: Bash (bash)
Эту сеть мы создали при установке Guacamole в предыдущем разделе. Если команда вернула информацию о сети, дополнительно ничего создавать не требуется.
5.1. ООО Ромашка — OpenVPN и один RDP-сервер
Предположим, что заказчик передал нам готовый профиль OpenVPN. Внутри его сети RDP-сервер имеет адрес 10.20.30.40:3389.
Создадим отдельный каталог для секретов ООО Ромашка.
sudo mkdir -p /opt/vpn-forwarders/secrets/romashka
sudo chmod 700 /opt/vpn-forwarders/secrets/romashkaCode language: Bash (bash)
В этот каталог помещаем переданный заказчиком файл client.ovpn. Текущая реализация OpenVPN-forwarder также ожидает файл key_password с паролем приватного ключа. Секреты в GitHub не храним.
/opt/vpn-forwarders/secrets/romashka/
├── client.ovpn
└── key_passwordCode language: Bash (bash)
Ограничим права:
sudo chmod 600 /opt/vpn-forwarders/secrets/romashka/client.ovpn
sudo chmod 600 /opt/vpn-forwarders/secrets/romashka/key_passwordCode language: Bash (bash)В Portainer.IO создадим Stack для ООО Ромашка непосредственно из GitHub.
- Stacks → Add stack
- Name: vpn-romashka
- Build method: Git Repository
- Repository URL: https://github.com/pantifeek/docker-vpn-forwarders.git
- Repository reference: refs/heads/main
- Compose path: portainer/stacks/openvpn.yml
- Access control: Enable access control: Administrators
Образ будет собран прямо из исходников репозитория, отдельный Docker Registry для этого не нужен.
В Environment variables зададим параметры подключения ООО Ромашка.
VPN_CONTAINER_NAME=vpn-romashka
SECRET_DIR=/opt/vpn-forwarders/secrets/romashka
TRANSIT_NETWORK=guac-vpn-transit
PORT_FORWARDS=3389:10.20.30.40:3389
HEALTH_MODE=allCode language: Dockerfile (dockerfile)
Строка 3389:10.20.30.40:3389 означает: контейнер vpn-romashka слушает внутри Docker-сети порт 3389 и всё, что приходит на этот порт, отправляет через OpenVPN на RDP-сервер 10.20.30.40:3389.
Нажимаем Deploy the stack. После сборки и запуска в сети guac-vpn-transit появляется контейнер vpn-romashka. Для Guacamole сервер ООО Ромашка теперь будет доступен как vpn-romashka:3389.
5.2. ООО Василёк — L2TP/IPsec и два RDP-сервера
Во второй организации используется L2TP/IPsec PSK. Предположим, что внутри сети ООО Василёк есть два сервера:
- 10.50.10.21:3389 — терминальный сервер 1С;
- 10.50.10.22:3389 — сервер для администрирования.
Оба сервера доступны через одно VPN-подключение, поэтому отдельный VPN-контейнер для каждого RDP нам не нужен. Один контейнер vpn-vasilek будет слушать два разных внутренних порта.
Подготовим каталог с секретами L2TP/IPsec.
sudo mkdir -p /opt/vpn-forwarders/secrets/vasilek
sudo chmod 700 /opt/vpn-forwarders/secrets/vasilek
sudo install -m 600 /dev/null /opt/vpn-forwarders/secrets/vasilek/username
sudo install -m 600 /dev/null /opt/vpn-forwarders/secrets/vasilek/password
sudo install -m 600 /dev/null /opt/vpn-forwarders/secrets/vasilek/pskCode language: Bash (bash)
В файлы username, password и psk записываем соответственно логин VPN, пароль пользователя и Pre-Shared Key, которые выдал заказчик. Я специально не привожу эти значения даже в вымышленном примере, чтобы не приучать хранить секреты прямо в Stack или в истории команд.
/opt/vpn-forwarders/secrets/vasilek/
├── username
├── password
└── pskCode language: Bash (bash)Проверим поддержку PPP на Docker-хосте.
test -c /dev/ppp && echo "/dev/ppp доступен" || echo "/dev/ppp не найден"Code language: Bash (bash)
Stack L2TP/IPsec передаёт устройство /dev/ppp внутрь контейнера, поэтому оно должно существовать на Docker-хосте.
Создадим второй Stack в Portainer.IO.
- Stacks → Add stack
- Name: vpn-vasilek
- Build method: Git Repository
- Repository URL: https://github.com/pantifeek/docker-vpn-forwarders.git
- Repository reference: refs/heads/main
- Compose path: portainer/stacks/l2tp-ipsec.yml
- Access control: Enable access control: Administrators
В Environment variables зададим параметры ООО Василёк.
VPN_CONTAINER_NAME=vpn-vasilek
SECRET_DIR=/opt/vpn-forwarders/secrets/vasilek
TRANSIT_NETWORK=guac-vpn-transit
VPN_SERVER=vpn.vasilek.example
PORT_FORWARDS=3389:10.50.10.21:3389;3390:10.50.10.22:3389
HEALTH_MODE=allCode language: Dockerfile (dockerfile)
Здесь уже два правила перенаправления, разделённые точкой с запятой:
vpn-vasilek:3389 → 10.50.10.21:3389
vpn-vasilek:3390 → 10.50.10.22:3389Code language: CSS (css)
То есть один L2TP/IPsec-туннель обслуживает сразу два сервера. Для Guacamole они выглядят как один hostname с разными портами.
После заполнения переменных нажимаем Deploy the stack. В результате у нас одновременно работают два независимых VPN-контейнера: vpn-romashka с OpenVPN и vpn-vasilek с L2TP/IPsec. У каждого свои секреты, маршруты, состояние VPN, health-check и логи.
В репозитории есть готовые Stack-файлы и для других вариантов подключения: IKEv2, WireGuard, FortiGate SSL-VPN и Kerio Control VPN Client. Но принцип остаётся тем же — один независимый VPN заказчика разворачиваем отдельным Stack и подключаем его к общей сети guac-vpn-transit.
OpenVPN, IKEv2, L2TP/IPsec и WireGuard в репозитории помечены как candidate — это рабочая основа, но конкретное подключение всё равно надо проверять на конкретном VPN-шлюзе. FortiGate и Kerio сильнее зависят от поведения клиентского ПО и версии шлюза, поэтому для них отдельно описаны ограничения.
6. Организация подключения через VPN
Теперь свяжем три RDP-подключения, которые создали в Guacamole, с двумя VPN-контейнерами. В итоге схема выглядит так:
Браузер
│
▼
Apache Guacamole
│
▼
guacd
│
├── vpn-romashka:3389
│ │ OpenVPN
│ ▼
│ 10.20.30.40:3389
│
└── vpn-vasilek
│ L2TP/IPsec
├── :3389 → 10.50.10.21:3389
└── :3390 → 10.50.10.22:3389
Apache Guacamole не знает маршруты сетей заказчиков и не подключается к VPN самостоятельно. Для него существуют только два адреса в Docker-сети: vpn-romashka и vpn-vasilek. Что находится за этими контейнерами, Guacamole уже не интересует.
Контейнер guacd должен быть подключен к той же сети guac-vpn-transit.
networks:
guac-vpn-transit:
external: trueCode language: Dockerfile (dockerfile)
Эта сеть внешняя, поэтому её не надо создавать заново при каждом разворачивании Stack. К ней подключены guacd, vpn-romashka и vpn-vasilek.
6.1. Подключение ООО Ромашка
В разделе установки Guacamole мы уже создали подключение ООО Ромашка — RDS. Его основные параметры должны выглядеть так:
- Name: ООО Ромашка — RDS
- Protocol: RDP
- Hostname: vpn-romashka
- Port: 3389
- Username / Password / Domain — учётные данные пользователя уже на RDP-сервере ООО Ромашка.
Когда пользователь открывает это подключение, guacd соединяется с vpn-romashka:3389. Контейнер принимает TCP-соединение и через OpenVPN перенаправляет его на 10.20.30.40:3389.
6.2. Два сервера ООО Василёк через один VPN
У ООО Василёк два RDP-сервера, но VPN-подключение одно. Поэтому в Guacamole создаём два подключения с одинаковым hostname и разными портами.
Первое подключение — терминальный сервер 1С.
- Name: ООО Василёк — 1С RDS
- Protocol: RDP
- Hostname: vpn-vasilek
- Port: 3389
- Username / Password / Domain — учётные данные RDP-сервера ООО Василёк.
Цепочка подключения:
Guacamole → vpn-vasilek:3389 → L2TP/IPsec → 10.50.10.21:3389Второе подключение — сервер администрирования.
- Name: ООО Василёк — Администрирование
- Protocol: RDP
- Hostname: vpn-vasilek
- Port: 3390
- Username / Password / Domain — учётные данные второго RDP-сервера ООО Василёк.
Цепочка подключения:
Guacamole → vpn-vasilek:3390 → L2TP/IPsec → 10.50.10.22:3389Важно не перепутать разные учётные данные. Логин, пароль и PSK VPN находятся только на Docker-хосте и используются VPN-контейнером. Логин и пароль RDP хранятся в настройках соответствующего подключения Guacamole. Пользователь Guacamole вообще не должен знать VPN-реквизиты заказчика.
Самое важное здесь, что нигде нет конструкции ports: с публикацией RDP на хост. Порты 3389 и 3390 доступны только внутри сети guac-vpn-transit. На внешнем IP сервера они не появляются.
За состоянием VPN и проброса портов контейнеры следят самостоятельно.
- проверяется, что VPN-процесс и интерфейс работают;
- проверяется маршрут до целевого сервера;
- проверяется доступность целевого TCP-порта;
- проверяется, что локальный процесс socat, который занимается перенаправлением соединения, жив и слушает нужный порт.
Если сломался только проброс порта, контейнер перезапускает только socat, не разрывая VPN без необходимости. Если проблема уже в самом VPN или маршруте, выполняется переподключение. После нескольких неудачных восстановлений основной процесс контейнера завершается, а Docker благодаря restart: unless-stopped запускает контейнер заново.
Если внутри VPN серверы доступны по DNS-именам, это тоже предусмотрено.
Для IKEv2, L2TP/IPsec, WireGuard, FortiGate и Kerio в проекте предусмотрен split-DNS: публичное имя VPN-шлюза продолжает разрешаться обычным DNS Docker, а внутренние имена серверов заказчика могут разрешаться через DNS, полученный от VPN или явно указанный в параметрах подключения.
Для OpenVPN работа DNS зависит от настроек самого .ovpn-профиля, поэтому в примере ООО Ромашка я специально использовал IP-адрес RDP-сервера. Для первого запуска это позволяет отделить проблемы VPN от проблем разрешения внутренних имён.
В итоге получаем достаточно простую модель. У ООО Ромашка один OpenVPN-туннель и один RDP-сервер. У ООО Василёк один L2TP/IPsec-туннель и два RDP-сервера. Пользователь видит в Guacamole три обычных подключения и не знает, какой VPN используется за каждым из них.
Для добавления нового заказчика схема не меняется: создаём отдельный каталог с секретами, новый Stack из того же Git-репозитория и необходимые подключения в Guacamole. Если меняется Dockerfile или логика forwarder, обновляем Git-репозиторий и выполняем Redeploy Stack в Portainer с пересборкой образа. Настройки конкретных заказчиков при этом остаются на Docker-хосте и в GitHub не попадают.

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