В этой статье я расскажу, как можно организовать доступ к множеству Docker-контейнеров через VNC с использованием noVNC, websockify и SSL/TLS для шифрования и защиты соединений. Добавим защиту паролем для каждого контейнера, и включим шифрование трафика.

Проблема

В моем проекте было несколько Docker-контейнеров с графическими приложениями, к которым приходилось подключаться удаленно через VNC. До тех пор пока контейнеров было несколько штук и они создавались вручную было не сложно выделить им отдельные порты для экспозиции наружу из контейнеров и прописать их в VNC клиенте. Но с развитием проекта, контейнеры пришлось создавать динамически и в разных количествах, что сделало неудобным доступ к ним по разным портам и началась путаница с уже созданными в VNC клиенте подключениями. Захотелось подобрать более удобный вариант для подключений и вот что получилось.

Изначально задача казалась простой: запустить несколько контейнеров с VNC-серверами и обращаться к ним через прокси, например Nginx или Traefik. Однако проблему подключения ко всем контейнерам через один интерфейс с возможностью маршрутизации запросов на конкретные контейнеры по URL с применением Nginx или Traefik решить не удалось. Раньше много раз встречал упоминания/описания noVNC, но никогда не прибегал к использованию, оказалось, что в моем случае - вполне подходящее решение.

Решение: noVNC и websockify

Сформулирую задачу:

  1. Имеем n контейнеров каждый с VNC сервером для доступа, который слушает порт 5900.

  2. Хочется не заморачиваться с выделением каждому контейнеру отдельного внешнего порта, а вместо этого обращаться к каждому из них по отдельному 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:

Описание:

  1. noVNC: Запускается контейнер с веб-интерфейсом noVNC, слушающий на порту NOVNC_HTTP_PORT для HTTP и на порту NOVNC_HTTPS_PORT для HTTPS. В файлы /etc/ssl/private/vnc-key.pem и /etc/ssl/certs/vnc-cert.pem монтируются приватный ключ и сертификат для SSL/TLS шифрования.

  2. VNC-контейнеры: Контейнеры с VNC-серверами, которые запускаются на порту 5900. Пароль для каждого VNC-сервера передаётся через переменные окружения.

  3. Маршрутизация через 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 шифрованием.

Мне этот подход помог в организации доступных через единый интерфейс подключений.

Комментарии (0)