14 KiB
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.