В этой статье я расскажу, как можно организовать доступ к множеству Docker-контейнеров через VNC с использованием noVNC, websockify и SSL/TLS для шифрования и защиты соединений. Добавим защиту паролем для каждого контейнера, и включим шифрование трафика.
Проблема
В моем проекте было несколько Docker-контейнеров с графическими приложениями, к которым приходилось подключаться удаленно через VNC. До тех пор пока контейнеров было несколько штук и они создавались вручную было не сложно выделить им отдельные порты для экспозиции наружу из контейнеров и прописать их в VNC клиенте. Но с развитием проекта, контейнеры пришлось создавать динамически и в разных количествах, что сделало неудобным доступ к ним по разным портам и началась путаница с уже созданными в VNC клиенте подключениями. Захотелось подобрать более удобный вариант для подключений и вот что получилось.
Изначально задача казалась простой: запустить несколько контейнеров с VNC-серверами и обращаться к ним через прокси, например Nginx или Traefik. Однако проблему подключения ко всем контейнерам через один интерфейс с возможностью маршрутизации запросов на конкретные контейнеры по URL с применением Nginx или Traefik решить не удалось. Раньше много раз встречал упоминания/описания noVNC, но никогда не прибегал к использованию, оказалось, что в моем случае - вполне подходящее решение.
Решение: noVNC и websockify
Сформулирую задачу:
Имеем n контейнеров каждый с VNC сервером для доступа, который слушает порт 5900.
Хочется не заморачиваться с выделением каждому контейнеру отдельного внешнего порта, а вместо этого обращаться к каждому из них по отдельному URL.
Для решения проблемы используем noVNC — веб-клиент для работы с VNC через браузер, и websockify, который будет маршрутизировать запросы к различным VNC-серверам внутри Docker-контейнеров. Это позволит получить доступ к каждому контейнеру через уникальный URL и защищать соединения с помощью SSL/TLS. Сам сервер noVNC также будет в контейнере.
Далее в статье пример настройки noVNC, который каждый может адаптировать для себя.
Шаг 1: Настройка Docker Compose
Начнём с настройки Docker Compose, которая развернёт несколько контейнеров с VNC-серверами (container1, container2, container3) и предоставит к ним доступ через веб-интерфейс noVNC с маршрутизацией запросов через websockify (контейнер novnc).
Пример Docker Compose файла:
services:
novnc:
image: theasp/novnc # noVNC образ
ports:
- "${NOVNC_HTTP_PORT}:8080" # Порт для веб-интерфейса noVNC
- "${NOVNC_HTTPS_PORT}:8443" # Порт для HTTPS с SSL/TLS
volumes:
- ./vnc-key.pem:/etc/ssl/private/vnc-key.pem:ro # Приватный ключ (только для чтения)
- ./vnc-cert.pem:/etc/ssl/certs/vnc-cert.pem:ro # Сертификат
environment:
- VNC_PASSWORD=${VNC_PASSWORD}
command: >
sh -c 'mkdir -p /etc/websockify &&
echo "pushkin: container1:5900" > /etc/websockify/tokens &&
echo "dostoevsky: container2:5900" >> /etc/websockify/tokens &&
echo "chekhov: container3:5900" >> /etc/websockify/tokens &&
/usr/bin/websockify --web /usr/share/novnc --cert=/etc/ssl/certs/vnc-cert.pem --key=/etc/ssl/private/vnc-key.pem 0.0.0.0:8443 --token-plugin TokenFile --token-source /etc/websockify/tokens'
networks:
- test-net
container1:
image: dorowu/ubuntu-desktop-lxde-vnc # Образ для контейнера 1 с VNC-сервером
expose:
- "5900"
environment:
- VNC_PASSWORD=${VNC_PASSWORD} # Использование пароля для доступа к VNC
networks:
- test-net
container_name: container1
container2:
image: dorowu/ubuntu-desktop-lxde-vnc # Образ для контейнера 2 с VNC-сервером
expose:
- "5900"
environment:
- VNC_PASSWORD=${VNC_PASSWORD}
networks:
- test-net
container_name: container2
container3:
image: dorowu/ubuntu-desktop-lxde-vnc # Образ для контейнера 3 с VNC-сервером
expose:
- "5900"
environment:
- VNC_PASSWORD=${VNC_PASSWORD}
networks:
- test-net
container_name: container3
networks:
test-net:
Описание:
noVNC: Запускается контейнер с веб-интерфейсом noVNC, слушающий на порту
NOVNC_HTTP_PORT
для HTTP и на портуNOVNC_HTTPS_PORT
для HTTPS. В файлы/etc/ssl/private/vnc-key.pem
и/etc/ssl/certs/vnc-cert.pem
монтируются приватный ключ и сертификат для SSL/TLS шифрования.VNC-контейнеры: Контейнеры с VNC-серверами, которые запускаются на порту
5900
. Пароль для каждого VNC-сервера передаётся через переменные окружения.Маршрутизация через websockify: Запросы направляются на нужные контейнеры через websockify с использованием токенов.
Обратите внимание на команду, которая формирует список токенов и сохраняет их в файл для настройки websockify. В примере это сделано для простоты, в реальном проекте можно сохранить токены другими способами. Токены определяют то куда будет перенаправлен web запрос с использованием websockify.
command: >
sh -c 'mkdir -p /etc/websockify &&
echo "pushkin: container1:5900" > /etc/websockify/tokens &&
echo "dostoevsky: container2:5900" >> /etc/websockify/tokens &&
echo "chekhov: container3:5900" >> /etc/websockify/tokens &&
/usr/bin/websockify --web /usr/share/novnc --cert=/etc/ssl/certs/vnc-cert.pem --key=/etc/ssl/private/vnc-key.pem 0.0.0.0:8443 --token-plugin TokenFile --token-source /etc/websockify/tokens'
Шаг 2: Защита паролем
Для защиты VNC-серверов паролем используем переменную окружения VNC_PASSWORD
. Пароль можно хранить в .env
файле или через Docker Secrets для более безопасного использования.
Использование .env файла:
Создайте файл .env
в той же директории, где находится docker-compose.yml
, и добавьте в него переменные с паролями и портами для доступов к noNVC:
VNC_PASSWORD=your_secure_password
NOVNC_HTTP_PORT=8080
NOVNC_HTTPS_PORT=443 # Порт по умолчанию для https
В файле Docker Compose мы используем переменную для настройки пароля VNC-сервера:
environment:
- VNC_PASSWORD=${VNC_PASSWORD}
Шаг 3: SSL/TLS для шифрования
Чтобы защитить трафик VNC, мы добавляем SSL/TLS сертификаты в контейнер noVNC, который будет работать через HTTPS.
Генерация сертификатов
Вы можете сгенерировать самоподписанный сертификат с помощью OpenSSL:
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
-keyout vnc-key.pem -out vnc-cert.pem \
-subj "/C=US/ST=My Lovely Town/L=My Lovely Town/O=Example Corp/OU=IT Department/CN=example.com"
Монтирование сертификатов в контейнер
Сгенерированные сертификаты монтируются в контейнер noVNC с флагом только для чтения, чтобы предотвратить их изменение:
volumes:
- ./vnc-key.pem:/etc/ssl/private/vnc-key.pem:ro
- ./vnc-cert.pem:/etc/ssl/certs/vnc-cert.pem:ro
Шаг 4: Подключение к контейнерам
После запуска контейнеров с помощью команды:
docker compose up -d
Вы можете подключаться к контейнерам через браузер по HTTPS с указанием или без указания порта, если наружу открыт порт по умолчанию 443 для htps:
https://my_host/vnc.vnc.html?path=websockify?token=...
вместо точек укажите токен нужного контейнера, чтобы подключиться к нему. В нашем примере:
pushkin = container1
dostoevsky = container2
chekhov = container3
Подключение с самоподписанным сертификатом немного напряжет браузер и он засомневается, что стоит подключаться дальше. Можно не опасаться, и разрешить ему это сделать. В окне откроется такая картинка:
Введем пароль и получим доступ к vnc указанного в параметре token контейнера.
Для удобства небольшой скрипт который выполнит генерацию сертификатов и поможет в управлении Docker Compose:
#!/bin/bash
CERT_KEY="vnc-key.pem"
CERT_CRT="vnc-cert.pem"
generate_certificate() {
echo "Генерация SSL-сертификата..."
openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
-keyout "${CERT_KEY}" -out "${CERT_CRT}" \
-subj "/C=US/ST=My Lovely Town/L=My Lovely Town/O=Example Corp/OU=IT Department/CN=example.com"
echo "Сертификат сгенерирован."
}
start_compose() {
echo "Запуск Docker Compose..."
docker-compose up -d
}
stop_compose() {
echo "Остановка Docker Compose..."
docker-compose down
}
restart_compose() {
echo "Перезапуск Docker Compose..."
docker-compose down
docker-compose up -d
}
echo "1. Генерация сертификата"
echo "2. Запуск Docker Compose"
echo "3. Остановка Docker Compose"
echo "4. Перезапуск Docker Compose"
read -p "Выберите действие (1-4): " choice
case $choice in
1) generate_certificate ;;
2) start_compose ;;
3) stop_compose ;;
4) restart_compose ;;
*) echo "Неверный выбор." ;;
esac
Оценка безопасности
Буквально пара слов про безопасность, в моем проекте высоких требований не предъявлялось, потому что все контейнеры были в закрытой сети. И доступ к ним осуществлялся через ssh соединение с форвардом портов или vpn. Кроме того, требования в самом проекте к безопасности были не очень высокие. При необходимости иметь хорошо защищенный доступ стоит лучше изучить такой вариант подключения. Но несколько общих рекомендаций могу привести:
Защита паролей
Docker Secrets: Рассмотрите использование Docker Secrets в боевых средах для безопасного хранения паролей.
SSL/TLS
Самоподписанные сертификаты можно использовать для тестирования, но для боевых сред, все же, рекомендуется использовать сертификаты от проверенных центров сертификации (например, Let's Encrypt).
Ограничение доступа
Ограничьте порты и сети: Убедитесь, что доступ к VNC-серверам и WebSocket портам открыт только для доверенных пользователей или через VPN.
Заключение
С помощью noVNC, websockify, и Docker получилось удобно организовать доступ к множеству контейнеров через VNC с защитой паролями и SSL/TLS шифрованием.
Мне этот подход помог в организации доступных через единый интерфейс подключений.