Apache Guacamole в Docker: рабочий стол в браузере

Содержание11 разделов
Apache Guacamole — открытый HTML5-шлюз для удалённого доступа по RDP, VNC и SSH через браузер. В связке с XFCE и xrdp он позволяет запустить графическую оболочку на сервере без монитора, а PostgreSQL и TOTP обеспечивают хранение подключений и двухфакторную проверку подлинности.
Цель конфигурации — не выставлять RDP и веб-панель в сеть общего пользования. Порт 3389 принимает соединения только от адреса шлюза, а Guacamole доступен исключительно в локальной сети.
Как устроена схема доступа?
Браузер обращается к веб-приложению Guacamole на домашнем сервере. Компонент guacd устанавливает RDP-соединение с удалённым сервером и передаёт изображение веб-приложению, которое отрисовывает рабочий стол на элементе canvas.
Компонент | Размещение | Назначение |
|---|---|---|
Guacamole | Домашний сервер | Веб-интерфейс, пользователи и подключения |
guacd | Домашний сервер | Соединение с xrdp и передача потока |
PostgreSQL | Домашний сервер | Хранение учётных записей, параметров и истории |
XFCE и xrdp | Удалённый сервер | Графический сеанс без физического монитора |
ufw | Удалённый сервер | Ограничение доступа к порту |
Где размещать панель Guacamole?
Перенос RDP за HTML5-шлюз не устраняет необходимость защищать точку входа. Компрометация панели может раскрыть сразу все сохранённые подключения.
Вариант | Доступ | Преимущество | Ограничение |
|---|---|---|---|
Публичная панель с HTTPS | Из любой сети | Не требует предварительного подключения к частной сети | Веб-приложение доступно для сканирования и атак |
Локальная панель через WireGuard | После подключения частного канала | Служба не публикуется в сеть общего пользования | Ключи необходимо настроить на каждом устройстве |
Локальная панель и список разрешённых адресов | Панель в локальной сети, RDP через внешний канал | Меньше компонентов и проще перенос | Требуется постоянный внешний адрес шлюза |
Далее рассматривается третий вариант. Для постоянной эксплуатации предпочтителен WireGuard: внутренние адреса не зависят от смены внешнего адреса провайдером.
Как подготовить XFCE и xrdp на Ubuntu 24.04?
Исходное состояние — Ubuntu 24.04 без графической подсистемы. Команды выполняются в сеансе root.
Установка графической оболочки
Пакет xorgxrdp необходим для запуска сервера Xorg через xrdp. Пакет dbus-x11 обеспечивает работу сеансовой шины сообщений.
export DEBIAN_FRONTEND=noninteractive
apt update
apt install -y xfce4 xfce4-terminal xfce4-taskmanager dbus-x11 xorgxrdp xrdpПеременная DEBIAN_FRONTEND отключает интерактивные запросы во время установки пакетов.
Создание отдельного пользователя
Для графического сеанса создаётся учётная запись без входа через root. Первая команда генерирует случайный пароль, который следует сохранить в менеджере паролей и использовать при создании пользователя.
openssl rand -base64 48 | tr -d '/+=' | cut -c1-24
adduser --gecos "" "<username>"
usermod -aG sudo "<username>"Членство в группе sudo требуется только для администрирования системы из графического сеанса.
Отключение локального дисплейного менеджера
На сервере без монитора локальный экран входа не используется. Его отключение также предотвращает конфликты между локальным сеансом и сеансом xrdp на уровне D-Bus и logind.
systemctl disable --now lightdm 2>/dev/null
systemctl set-default multi-user.targetЦелевое состояние multi-user.target не запрещает запуск графики: xrdp создаёт отдельные сеансы через sesman по мере подключения пользователей.
Настройка xrdp
Служба добавляется в группу ssl-cert для доступа к ключу сертификата. Файл .xsession указывает, какую оболочку запускать после проверки подлинности.
adduser xrdp ssl-cert
echo "startxfce4" > "/home/<user>/.xsession"
chown "<username>:<username>" "/home/<user>/.xsession"
chmod +x "/home/<user>/.xsession"
systemctl enable --now xrdpПодавление запросов polkit
В Ubuntu 24.04 правила polkit записываются на JavaScript в файлах .rules. Следующая конфигурация разрешает операции colord для членов группы sudo и устраняет типовой запрос пароля при входе через xrdp.
tee /etc/polkit-1/rules.d/49-xrdp-nopasswd.rules > /dev/null <<'EOF'
polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.color-manager.") === 0 &&
subject.isInGroup("sudo")) {
return polkit.Result.YES;
}
});
EOF
systemctl restart polkitНе следует глобально отключать проверку подлинности для PackageKit и NetworkManager: такие правила расширяют возможности компрометации сеанса без необходимости.
Как закрыть порт 3389 с помощью ufw?
UFW применяет первое совпавшее правило, поэтому разрешение для адреса шлюза должно находиться выше общего запрета. В первой команде <IP> заменяется внешним адресом домашней сети.
ufw allow from "<IP>" to any port 3389 proto tcp comment 'Guacamole RDP'
ufw deny 3389/tcp comment 'RDP blocked'
ufw reload
ufw status numberedЕсли запрещающее правило уже расположено выше, разрешение добавляется на первую позицию:
ufw insert 1 allow from "<IP>" to any port 3389 proto tcp comment 'Guacamole RDP'
ufw status numberedПри использовании IPv6 проверяется, по какой версии протокола guacd соединяется с сервером. Для обеих версий создаются отдельные разрешающие правила с соответствующими адресами.
Как развернуть Guacamole в Docker Compose?
Стек состоит из PostgreSQL, guacd и веб-приложения Guacamole. Общий том initdb передаёт SQL-скрипты из образа Guacamole в каталог одноразовой инициализации PostgreSQL.
Конфигурация стека
Сохраните описание как compose.yaml. В публикации порта <IP> заменяется локальным адресом машины, на которой развёрнута панель.
services:
postgres:
image: postgres:16-alpine
container_name: guac-postgres
restart: unless-stopped
environment:
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- pgdata:/var/lib/postgresql/data
- initdb:/docker-entrypoint-initdb.d:ro
networks:
- guacnet
healthcheck:
test: ["CMD-SHELL", "pg_isready -U guacamole_user -d guacamole_db"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
guacd:
image: guacamole/guacd:1.6.0
container_name: guac-guacd
restart: unless-stopped
networks:
- guacnet
guacamole:
image: guacamole/guacamole:1.6.0
container_name: guac-web
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
guacd:
condition: service_started
environment:
GUACD_HOSTNAME: guacd
GUACD_PORT: "4822"
POSTGRESQL_ENABLED: "true"
POSTGRESQL_HOSTNAME: postgres
POSTGRESQL_PORT: "5432"
POSTGRESQL_DATABASE: guacamole_db
POSTGRESQL_USERNAME: guacamole_user
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRESQL_USER_PASSWORD_MIN_LENGTH: "12"
POSTGRESQL_USER_PASSWORD_REQUIRE_MULTIPLE_CASE: "true"
POSTGRESQL_USER_PASSWORD_REQUIRE_DIGIT: "true"
POSTGRESQL_USER_PASSWORD_REQUIRE_SYMBOL: "true"
POSTGRESQL_USER_PASSWORD_PROHIBIT_USERNAME: "true"
TOTP_ENABLED: "true"
TOTP_ISSUER: "Guacamole"
WEBAPP_CONTEXT: ROOT
volumes:
- initdb:/opt/guacamole/extensions/guacamole-auth-jdbc/postgresql/schema:ro
ports:
- "<IP>:8080:8080"
networks:
- guacnet
volumes:
pgdata:
initdb:
networks:
guacnet:
driver: bridgeСкрипты инициализации выполняются только при первом запуске PostgreSQL с пустым томом pgdata. Для существующей базы применяется отдельная процедура обновления схемы из официального руководства.
Создание пароля и запуск контейнеров
Следующие команды создают .env со случайным паролем базы данных и запускают стек. Команда umask ограничивает доступ к файлу текущим пользователем.
umask 077
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -base64 48 | tr -d '/+=' | cut -c1-32)" > .env
docker compose up -d
docker compose psФайл .env не следует помещать в систему контроля версий или передавать вместе с публичной конфигурацией.
Почему порт привязан к локальному адресу?
Запись "<IP>:8080:8080" публикует панель только на указанном интерфейсе. Публикация 8080:8080 или привязка к 0.0.0.0 открывает порт на всех адресах хоста.
Проверка запуска и связности
Первые две команды проверяют состояние контейнеров и наличие таблиц. В третьей <IP> заменяется адресом удалённого сервера с xrdp.
docker ps --filter name=guac
docker exec guac-postgres psql -U guacamole_user -d guacamole_db -c "\dt" | head
docker exec guac-guacd nc -zv "<IP>" 3389Если таблицы отсутствуют, проверяются журналы guac-postgres и guac-web, а также монтирование тома initdb. Ошибка соединения с портом 3389 обычно указывает на несовпадение внешнего адреса шлюза с разрешающим правилом ufw.
Как выполнить первый вход и включить TOTP?
Панель открывается по адресу http://<IP>:8080, где <IP> — локальный адрес сервера Guacamole. Начальные учётные данные — guacadmin и guacadmin.
TOTP — механизм одноразовых кодов, действующих ограниченное время. В официальном образе переменная TOTP_ENABLED автоматически подключает соответствующее расширение; отдельный список EXTENSIONS не требуется.
- Войдите с начальными учётными данными и отсканируйте QR-код в приложении одноразовых кодов.
- Сохраните секрет восстановления в защищённом хранилище, если приложение предоставляет такую возможность.
- Смените пароль
guacadminс учётом заданных требований сложности. - Создайте отдельную учётную запись администратора, проверьте вход и второй фактор, после чего отключите
guacadmin.
Порядок подключения и параметры расширения приведены в документации TOTP.
Как импортировать подключение и снизить нагрузку?
Массовое добавление подключений выполняется импортом YAML. Сохраните файл как connections.yml, замените адрес сервера, имя пользователя и пароль, затем загрузите его через «Параметры» → «Подключения» → «Импорт».
- name: Сервер — рабочий стол
protocol: rdp
parameters:
hostname: "<IP>"
port: "3389"
username: "<username>"
password: "ПАРОЛЬ"
security: "any"
ignore-cert: "true"
width: "1600"
height: "900"
dpi: "96"
color-depth: "16"
disable-audio: "true"
enable-wallpaper: "false"
enable-theming: "false"
enable-font-smoothing: "true"
enable-full-window-drag: "false"
enable-desktop-composition: "false"
enable-menu-animations: "false"
resize-method: "display-update"
users:
- guacadminЕсли начальная учётная запись уже отключена, вместо guacadmin указывается созданный пользователь панели. Разрешение на подключение можно также назначить через веб-интерфейс после импорта.
Глубина цвета 16 бит, отключённые обои, анимация и перетаскивание окон без содержимого уменьшают объём графических данных. Это особенно заметно при работе через канал с высокой задержкой или ограниченной пропускной способностью.
Для SSH предпочтителен отдельный ключ, используемый только Guacamole. Ограничения задаются в authorized_keys, а отзыв доступа выполняется удалением одной записи.
Как организовать резервные копии и обновления?
Логическая копия PostgreSQL создаётся без остановки всего стека и включает пользователей, подключения, историю и данные второго фактора.
umask 077
docker exec guac-postgres pg_dump -U guacamole_user -d guacamole_db -Fc > guacamole-db.dumpФайл guacamole-db.dump и .env хранятся в зашифрованном резервном хранилище. Копирование тома pgdata работающей базы без дополнительных мер согласованности не заменяет pg_dump.
Обновление образов в пределах указанных тегов выполняется стандартными командами Compose:
docker compose pull
docker compose up -d
docker compose psДля перехода на новую версию Guacamole изменяется тег 1.6.0, предварительно изучаются примечания к выпуску и требования к обновлению схемы базы. Перед таким обновлением создаётся отдельная резервная копия.
Локальный адрес сервера панели закрепляется в настройках DHCP маршрутизатора. Если адрес изменится, Docker не сможет привязать порт 8080 к прежнему интерфейсу.
Какие ограничения у удалённого рабочего стола в браузере?
- Для терминала, текстовых редакторов и административных утилит задержка обычно не мешает работе. Видео, быстрая прокрутка и интенсивная отрисовка заметно сильнее зависят от канала и производительности сервера.
- Несколько независимых мониторов, аппаратное ускорение и трёхмерная графика не являются сильными сторонами этой схемы.
- Передача файлов выполняется через интерфейс Guacamole, а не привычным перетаскиванием между локальным и удалённым рабочими столами.
- Служебное меню вызывается сочетанием
Ctrl+Alt+Shift. - Отключение звука, обоев и визуальных эффектов снижает нагрузку, но не устраняет сетевую задержку.
Частые вопросы
Нужно ли вручную создавать схему PostgreSQL?
В приведённой конфигурации ручная выгрузка SQL не требуется. Общий том initdb передаёт скрипты из образа Guacamole в PostgreSQL, который выполняет их при первой инициализации пустой базы.
Для существующей базы простое повторное монтирование тома не обновляет схему. Применяются скрипты обновления для конкретной версии.
Почему панель недоступна, хотя контейнеры запущены?
Проверяется, что адрес в публикации порта принадлежит локальному интерфейсу хоста. Сопоставляются вывод ip address, настройки DHCP и результат docker compose ps.
Если адрес совпадает, проверяются правила межсетевого экрана и отсутствие конфликта порта 8080 с другой службой.
Почему после входа отображается чёрный экран?
Типовые причины — активный локальный сеанс, отсутствие xorgxrdp, неверный .xsession или конфликт сеансовых компонентов D-Bus. Проверяются состояние xrdp, содержимое .xsession и журналы сеанса.
Можно ли использовать динамический внешний адрес?
Технически правило ufw можно обновлять автоматически после смены адреса, но такой механизм добавляет отдельную точку отказа. Для динамического адреса надёжнее частный канал WireGuard с постоянными внутренними адресами.
Зачем закрывать RDP, если задан сложный пароль?
Ограничение источников уменьшает доступность службы для перебора и сетевых атак независимо от стойкости пароля. Второй фактор на панели не заменяет фильтрацию порта 3389, поскольку проверяется на другом уровне.
Заключение
Apache Guacamole в Docker Compose объединяет рабочий стол XFCE, xrdp, PostgreSQL и TOTP в управляемую схему удалённого доступа без установки клиентов на каждое устройство. Закрепите адреса, ограничьте RDP одним доверенным источником, настройте зашифрованные резервные копии и используйте WireGuard, чтобы сохранить удобство браузерного доступа без публикации инфраструктурных служб.