267 lines
14 KiB
Markdown
267 lines
14 KiB
Markdown
# 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` ядра создайте дополнительный файл:
|
||
|
||
```bash
|
||
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`, чтобы порт не пропал после пересоздания БД.
|
||
|
||
Проверка:
|
||
|
||
```bash
|
||
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-пользователя сервера ядра:
|
||
|
||
```bash
|
||
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-сессию или консоль:
|
||
|
||
```bash
|
||
# На сервере ядра:
|
||
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
|
||
```
|
||
|
||
Не запускайте `ssh-keygen` повторно поверх существующего ключа.
|
||
|
||
### Если пользователь допускает только ключ
|
||
|
||
На веб-сервере выведите только публичную часть ключа:
|
||
|
||
```bash
|
||
cat /etc/moonwell/ssh-tunnel/id_ed25519.pub
|
||
```
|
||
|
||
В существующей административной SSH-сессии **сервера ядра** замените имя
|
||
пользователя и вставьте эту строку вместо `ssh-ed25519 AAAA...`:
|
||
|
||
```bash
|
||
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` этого пользователя проверьте настройки:
|
||
|
||
```text
|
||
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 при первом соединении:
|
||
|
||
```bash
|
||
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`.
|
||
|
||
```bash
|
||
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-данные и подключения через туннель:
|
||
|
||
```dotenv
|
||
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. Запуск на веб-сервере
|
||
|
||
Для полного деплоя используйте обновлённый скрипт:
|
||
|
||
```bash
|
||
./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`:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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"; }'
|
||
```
|
||
|
||
Ожидаемый результат:
|
||
|
||
```text
|
||
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](https://man.openbsd.org/ssh),
|
||
[OpenSSH keepalive](https://man.openbsd.org/ssh_config),
|
||
[OpenSSH authorized_keys](https://man.openbsd.org/sshd),
|
||
[OpenSSH server configuration](https://man.openbsd.org/sshd_config),
|
||
[Docker published ports](https://docs.docker.com/engine/network/port-publishing/).
|