Files
moonwell-web/docs/ssh-core-tunnel.md
2026-10-04 14:33:33 +04:00

14 KiB
Raw Permalink Blame History

SSH-туннель от веба к БД ядра

Для Ubuntu/Debian и production Compose этого проекта. Адрес сервера ядра, SSH-порт и SSH-пользователь заменяются вашими значениями. Настройка выполняется на двух серверах. SSH-пароль и пароль MariaDB — разные данные.

Схема: app → core-db-tunnel:3306 → SSH → 127.0.0.1:13306 → MariaDB ядра. Порт 3306 туннеля доступен только в сети контейнеров веба; на хост он не публикуется. Ключ хранится вне репозитория в /etc/moonwell/ssh-tunnel, в образ он не копируется.

1. На сервере ядра: доступ к MariaDB через localhost

В папке с production compose.yaml ядра создайте дополнительный файл:

cat > compose.ssh-db.yaml <<'YAML'
services:
  db:
    ports:
      - "127.0.0.1:13306:3306"
YAML

docker compose -f compose.yaml -f compose.ssh-db.yaml up -d --no-deps db

Этот шаг пересоздаёт контейнер MariaDB, сохраняя его volume; соединения с БД прервутся на время перезапуска. Выполните его в подходящее для этого время. Если MariaDB уже доступна на localhost сервера ядра, используйте существующий порт и пропустите этот шаг. В будущих командах управления ядром сохраняйте -f compose.ssh-db.yaml, чтобы порт не пропал после пересоздания БД.

Проверка:

ss -lnt | grep ':13306'
docker compose -f compose.yaml -f compose.ssh-db.yaml ps db

При использовании другого имени сервиса замените db на имя MariaDB вашего ядра. На сервере ядра SSH должен разрешать локальный TCP forwarding (AllowTcpForwarding yes или local); ограничения пользователя и ключа должны разрешать 127.0.0.1:13306.

2. На сервере веба: отдельный SSH-ключ

Выполняйте команды от root, как в текущем сеансе. Подставьте адрес, SSH-порт и существующего SSH-пользователя сервера ядра:

CORE_HOST=IP_ИЛИ_ДОМЕН_ЯДРА
CORE_PORT=22
CORE_USER=SSH_ПОЛЬЗОВАТЕЛЬ

apt-get update
apt-get install -y openssh-client
install -d -m 700 /etc/moonwell/ssh-tunnel
ssh-keygen -t ed25519 -N '' \
  -C moonwell-web-db-tunnel \
  -f /etc/moonwell/ssh-tunnel/id_ed25519

ssh-copy-id -p "$CORE_PORT" \
  -i /etc/moonwell/ssh-tunnel/id_ed25519.pub \
  -o UserKnownHostsFile=/etc/moonwell/ssh-tunnel/known_hosts \
  "$CORE_USER@$CORE_HOST"

ssh-copy-id запросит SSH-пароль выбранного пользователя и сохранит публичный ключ. При первом подключении сверьте показанный fingerprint с ключом сервера ядра, проверенным через его существующую SSH-сессию или консоль:

# На сервере ядра:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

Не запускайте ssh-keygen повторно поверх существующего ключа.

Если пользователь допускает только ключ

На веб-сервере выведите только публичную часть ключа:

cat /etc/moonwell/ssh-tunnel/id_ed25519.pub

В существующей административной SSH-сессии сервера ядра замените имя пользователя и вставьте эту строку вместо ssh-ed25519 AAAA...:

TUNNEL_USER=ИМЯ_ПОЛЬЗОВАТЕЛЯ_ТУННЕЛЯ
sudo -H -u "$TUNNEL_USER" sh -c '
set -eu
umask 077
mkdir -p "$HOME/.ssh"
cat >> "$HOME/.ssh/authorized_keys"
chmod 700 "$HOME/.ssh"
chmod 600 "$HOME/.ssh/authorized_keys"
' <<'KEY'
restrict,port-forwarding,permitopen="127.0.0.1:13306" ssh-ed25519 AAAA... moonwell-web-db-tunnel
KEY

Так файл создаётся от имени самого пользователя. restrict отключает дополнительные возможности ключа, а port-forwarding разрешает forwarding к указанной в permitopen цели. При нестандартном AuthorizedKeysFile используйте путь из конфигурации SSH вместо ~/.ssh/authorized_keys.

В уже существующем блоке Match User этого пользователя проверьте настройки:

Match User ИМЯ_ПОЛЬЗОВАТЕЛЯ_ТУННЕЛЯ
    PubkeyAuthentication yes
    AuthenticationMethods publickey
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    AllowTcpForwarding local
    PermitOpen 127.0.0.1:13306
    MaxSessions 0

MaxSessions 0 разрешает forwarding и запрещает shell/command-сессии. После изменения SSH-конфига выполните sudo /usr/sbin/sshd -t, а при успешной проверке — sudo systemctl reload ssh (Ubuntu/Debian). Добавление самого ключа в authorized_keys не требует перезагрузки службы.

Проверка входа и заполнение known_hosts

На веб-сервере выполните ручное подключение с forwarding. Оно использует только публичный ключ для входа и допускает подтверждение fingerprint при первом соединении:

ssh -vNT -p "$CORE_PORT" \
  -i /etc/moonwell/ssh-tunnel/id_ed25519 \
  -o BatchMode=no -o IdentitiesOnly=yes -o PreferredAuthentications=publickey \
  -o PasswordAuthentication=no -o KbdInteractiveAuthentication=no \
  -o StrictHostKeyChecking=ask -o GlobalKnownHostsFile=/dev/null \
  -o UserKnownHostsFile=/etc/moonwell/ssh-tunnel/known_hosts \
  -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:13307:127.0.0.1:13306 \
  "$CORE_USER@$CORE_HOST"

Сверьте fingerprint с ключом сервера ядра и подтвердите его. В выводе ожидается успешная аутентификация using "publickey"; SSH остаётся работающим. Остановите ручную проверку через Ctrl+C. Команда ssh ... true для пользователя с MaxSessions 0 не подходит, поскольку командные сессии ему запрещены. Не задавайте BatchMode=yes при первоначальном подтверждении нового host key: этот режим отключает взаимодействие с пользователем. Контейнер использует BatchMode=yes и строгую проверку уже сохранённого known_hosts.

chmod 600 /etc/moonwell/ssh-tunnel/id_ed25519 /etc/moonwell/ssh-tunnel/known_hosts

3. Настройки веба

Перенесите обновлённый compose.prod.yaml и новые файлы этого проекта на веб-сервер:

  • compose.prod.ssh-tunnel.yaml;
  • docker/ssh-tunnel/Dockerfile;
  • docker/ssh-tunnel/start.sh.

В .env веба укажите фактические SSH-данные и подключения через туннель:

CORE_SSH_HOST=IP_ИЛИ_ДОМЕН_ЯДРА
CORE_SSH_PORT=22
CORE_SSH_USER=SSH_ПОЛЬЗОВАТЕЛЬ
CORE_DB_HOST=127.0.0.1
CORE_DB_PORT=13306

VMANGOS_AUTH_DB_HOST=core-db-tunnel
VMANGOS_AUTH_DB_PORT=3306
VMANGOS_AUTH_DB_DATABASE=realmd
VMANGOS_AUTH_DB_USERNAME=vmangos
VMANGOS_AUTH_DB_PASSWORD="ПАРОЛЬ_БД_ЯДРА"

VMANGOS_CHARACTERS_DB_HOST=core-db-tunnel
VMANGOS_CHARACTERS_DB_PORT=3306
VMANGOS_CHARACTERS_DB_DATABASE=characters
VMANGOS_CHARACTERS_DB_USERNAME=vmangos
VMANGOS_CHARACTERS_DB_PASSWORD="ПАРОЛЬ_БД_ЯДРА"

Здесь CORE_DB_HOST=127.0.0.1 означает localhost сервера ядра, а не веба. Порты 13306 (MariaDB на сервере ядра) и 3306 (туннель в сети веба) различаются. CORE_SSH_PORT — SSH-порт, он может отличаться от 22. Настройки DB_* MySQL самого веба и AWS_* S3 остаются прежними. Удалите старые VMANGOS_AUTH_DB_URL и VMANGOS_CHARACTERS_DB_URL, если они указаны: иначе Laravel может использовать адреса из URL вместо настроек выше. VMANGOS_DOCKER_NETWORK для production с удалённым ядром не используется. Для проверки статуса игрового сервера укажите MOONWELL_REALM_STATUS_HOST равным доступному с веба адресу игрового сервера, а не mangosd из локального Docker.

4. Запуск на веб-сервере

Для полного деплоя используйте обновлённый скрипт:

./scripts/deploy-production.sh --ssh-tunnel

Флаг добавляет файл туннеля во все production-команды Compose, собирает и запускает SSH-контейнер, ждёт его healthy, затем проверяет обе БД через SELECT 1 перед миграциями. Если VMANGOS_AUTH_DB_HOST либо VMANGOS_CHARACTERS_DB_HOST уже равен core-db-tunnel, режим включается автоматически. Файл туннеля задаёт адреса и порты БД приложения независимо от устаревших *_DB_HOST=db в .env; имена баз и учётные данные берутся из .env. Удалите VMANGOS_*_DB_URL, чтобы они не переопределяли адрес подключения. Предупреждение про stalwart относится к отдельному compose.mail.yaml; скрипт не удаляет orphan-контейнеры.

Команды ниже позволяют запустить и проверить туннель отдельно от полного деплоя.

Из папки moonwell-web:

docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml up -d --build core-db-tunnel
docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml ps core-db-tunnel
docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml logs --tail=30 core-db-tunnel

После состояния healthy пересоздайте приложение и nginx:

docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml up -d --no-deps --force-recreate app web
docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml exec -T --user app app php artisan config:cache

Docker запускает туннель после перезагрузки и перезапускает завершившийся SSH-процесс. SSH keepalive обнаруживает обрыв соединения. healthy подтверждает, что SSH создал локальный слушающий порт; доступность самой MariaDB проверяется следующим шагом.

5. Проверка из Laravel

docker compose -f compose.prod.yaml -f compose.prod.ssh-tunnel.yaml exec -T --user app app php artisan tinker --execute='foreach (["vmangos_auth", "vmangos_characters"] as $name) { DB::connection($name)->select("SELECT 1"); echo $name." OK\n"; }'

Ожидаемый результат:

vmangos_auth OK
vmangos_characters OK

Команда выполняет только SELECT 1, игровые аккаунты не меняются. Если tinker не установлен в вашей production-сборке, проверьте наличие laravel/tinker в production-зависимостях перед запуском этой команды.

Ошибки:

  • Permission denied (publickey): SSH-пользователь, публичный ключ или права файла ключа.
  • Host key verification failed: известный ключ не соответствует адресу/порту; сверьте fingerprint и обновите файл known_hosts через подтверждённое подключение.
  • administratively prohibited: сервер SSH запретил forwarding или цель туннеля.
  • connect failed: Connection refused: MariaDB не слушает на адресе/порту CORE_DB_HOST:CORE_DB_PORT на сервере ядра.
  • Access denied for user: туннель работает, но неверны учётные данные или MariaDB не разрешает этому пользователю подключение с адреса хоста Docker.

Документация: OpenSSH forwarding, OpenSSH keepalive, OpenSSH authorized_keys, OpenSSH server configuration, Docker published ports.