Организация доступа к серверам заказчиков

В этой статье я расскажу как в условиях большого разнообразия настроек подключения к инфраструктуре и большого внутреннего штата организовать контролируемую единую точку входа к удалённым рабочим столам на серверах заказчиков через Apache Guacamole.

Проблемы, которые решает пример в статье:

  1. Централизованный доступ внутренних пользователей к удалённым рабочим столам на серверах заказчиков (всё разнообразие заказчиков в одной системе);
  2. Доступ к VPN заказчика есть только у администраторов системы (сильно снижаем вероятность утечки настроек доступа);
  3. Администрирование доступа к подключениям (администратор самостоятельно разрешает и блокирует доступ к серверам заказчиков, нет необходимости обращаться к IT-службе заказчика);
  4. История сессий остаётся в системе (кто подключился и как долго был подключен). Также есть возможность записывать сеансы (не рассматривается в рамках статьи).

Для упрощения статьи, я буду описывать вариант подключения к 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 не попадают.


Опубликовано

в

,

от

Комментарии

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

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