MAATRIX / Блог / Антипаттерн: база данных открыта наружу «на время отладки»

Антипаттерн: база данных открыта наружу «на время отладки»

MAATRIX

Нужно было один раз подключиться к продакшн-базе локальным клиентом — DBeaver, pgAdmin, MongoDB Compass — и проще всего показалось открыть порт СУБД наружу в firewall. Подключились, посмотрели данные, всё получилось. А правило firewall осталось — потому что закрыть его планировалось «потом», а потом либо забылось, либо превратилось в «ну оно же работает, зачем трогать». Разберём, как именно так делают, что именно идёт не так технически, и как получить тот же самый удобный доступ, не оставляя порт базы данных открытым всему интернету.

Как именно так делают

Сценарий почти всегда один и тот же: разработчику нужно посмотреть данные в проде — разобраться с багом, свериться со значением, накатить ручной фикс. Открыть SSH и работать через psql в терминале неудобно, хочется свой привычный GUI-клиент с деревом таблиц, автодополнением и визуальным редактором запросов. Самый быстрый путь к этому — открыть порт СУБД наружу.

На VPS с ufw это одна команда:

sudo ufw allow 5432/tcp comment 'temp debug access'

Комментарий temp debug access — это, как правило, единственный след того, что доступ задумывался как временный. Через ufw также открывают 3306 (MySQL/MariaDB), 27017 (MongoDB), 6379 (Redis), 9200 (Elasticsearch) — по тому же паттерну.

В облачной security group то же самое делается ещё быстрее — через один инбаунд-роль в консоли AWS/DigitalOcean/Yandex Cloud:

Type: PostgreSQL
Protocol: TCP
Port: 5432
Source: 0.0.0.0/0

0.0.0.0/0 означает «с любого IP-адреса в интернете». В консоли это выглядит как обычная строка в таблице правил — ничего не намекает на то, что этим правилом порт базы данных доступен буквально всем, у кого есть интернет и IP-адрес сервера.

Дальше — подключение локальным клиентом напрямую по публичному IP:

Host: 203.0.113.45
Port: 5432
Database: production
User: postgres
Password: ********

DBeaver или pgAdmin подключаются, показывают таблицы, всё работает так же удобно, как если бы база стояла локально на ноутбуке. Отдельный частный случай — Docker-образы некоторых СУБД в дефолтной конфигурации вообще не требуют пароля или требуют пароль по умолчанию из документации (mongo, redis без --requirepass, ранние версии некоторых образов Elasticsearch без включённой security-фичи). Если такой контейнер запущен с -p 27017:27017 на сервере, где порт уже открыт в firewall, — база доступна снаружи вообще без какой-либо аутентификации.

Почему это кажется нормальным временным решением

Открыть порт — это буквально одна команда, а настроить SSH-туннель или VPN — это, кажется, «отдельная задача на потом», когда просто нужно быстро посмотреть одно значение в таблице. С точки зрения «сейчас, срочно, разберусь с доступом позже» решение выглядит рациональным: цель — не оставить дыру навсегда, а получить доступ на 10 минут и тут же закрыть порт обратно.

Проблема в том, что «закрыть обратно» — это отдельное действие, которое ничем не привязано к моменту его необходимости. Оно не встроено ни в один процесс, не имеет напоминания, не проверяется CI/CD. Специалист переключается на следующую задачу, инцидент считается решённым, а правило firewall остаётся жить своей жизнью — иногда неделями, иногда годами, пока его случайно не найдёт аудит безопасности или, что куда хуже, сканер.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Проблема первая: сканеры находят открытый порт за часы

Весь диапазон адресов IPv4 непрерывно и автоматически сканируется ботами на предмет открытых портов известных сервисов — это не гипотетическая угроза, а фоновый процесс интернета, который идёт постоянно, вне зависимости от того, интересен ли конкретно ваш сервер кому-то персонально. Инструменты вроде masscan и zmap способны пройти весь IPv4-диапазон по одному порту за разумное время на одной машине, и именно так работают и легальные проекты вроде Shodan/Censys, и вредоносные боты, ищущие уязвимые базы для атаки.

Порт 5432, 3306, 27017, 6379, 9200 — это не случайные номера, а порты по умолчанию конкретных, узнаваемых по баннеру СУБД, и они входят в списки портов, которые сканируют в первую очередь именно потому, что за ними обычно стоят данные. На практике это означает, что вновь открытый порт популярной базы данных обнаруживается автоматическим сканером обычно в пределах часов после появления в интернете, а не дней и тем более не недель — это ориентир, основанный на характере автоматического сканирования, а не измеренная лично цифра, и в вашем конкретном случае время может отличаться в любую сторону. Рассчитывать на «нас не найдут, мы маленькие» не стоит: сканеры не выбирают цели по значимости, они проходят весь диапазон адресов подряд.

Проблема вторая: слабая аутентификация — прямой путь к потере данных

Если порт СУБД открыт наружу, а аутентификация на нём слабая — дефолтный пароль, простой пароль, повторно используемый пароль, или, в худшем случае, аутентификация вообще не включена, как бывает в дефолтной конфигурации некоторых Docker-образов, — обнаружение сканером почти сразу превращается в компрометацию.

Это не абстрактный сценарий: за последние годы задокументировано множество массовых, широко освещённых инцидентов именно с базами MongoDB и Elasticsearch, которые администраторы открывали «временно» без пароля или с паролем по умолчанию для отладки, а автоматизированные боты находили их сканированием, скачивали (или просто удаляли) содержимое и оставляли записи с требованием выкупа за возврат данных — притом что часто никакой копии у атакующего не было вообще, и восстанавливать было уже нечего. Общая черта всех этих случаев одна: база была открыта наружу «ненадолго», а нашли её и опустошили быстрее, чем кто-либо успел вернуться и закрыть порт.

Важно понимать порядок причинности: открытый порт — это не сама уязвимость, а необходимое условие для того, чтобы слабая аутентификация вообще стала доступна снаружи для перебора или обхода. Без открытого порта у атакующего просто нет сетевого пути к базе, вне зависимости от того, насколько слабый на ней пароль.

Как сделать правильно: разовый доступ через SSH-туннель

Для одноразовой или редкой отладки — посмотреть данные, разобраться с инцидентом — не нужен открытый порт вообще. SSH-туннель пробрасывает порт базы данных на локальную машину через уже существующее SSH-соединение к серверу, и снаружи по-прежнему открыт только порт SSH (который и так должен быть открыт для администрирования сервера):

ssh -L 5432:localhost:5432 user@203.0.113.45 -N

Флаг -L 5432:localhost:5432 означает «слушать локальный порт 5432 на моей машине и пробрасывать всё, что туда придёт, на localhost:5432 уже с точки зрения сервера» — то есть на саму базу, которая на сервере слушает только локальный интерфейс, а не 0.0.0.0. Флаг -N говорит SSH не открывать интерактивную оболочку, а только держать туннель. После этого в DBeaver или pgAdmin указывается адрес localhost:5432 — клиент подключается к локальному порту на своей машине, а SSH прозрачно пробрасывает трафик на сервер и обратно, зашифрованным внутри уже существующего SSH-соединения.

Ключевое условие, чтобы это вообще работало как защита: сама СУБД должна слушать только localhost (или внутренний IP), а не 0.0.0.0, и порт СУБД должен быть закрыт в firewall наружу. Для PostgreSQL это настраивается в postgresql.conf:

listen_addresses = 'localhost'

Для MongoDB — в mongod.conf:

net:
  bindIp: 127.0.0.1

Туннель закрывается вместе с завершением SSH-сессии (Ctrl+C в терминале, где он запущен, или закрытие терминала) — никакого отдельного шага «не забыть закрыть порт» не остаётся, потому что порт снаружи и не открывался вовсе.

Регулярный доступ команды: VPN, whitelist IP, bastion-хост

SSH-туннель отлично закрывает разовый случай, но неудобен, когда к базе регулярно обращается несколько человек из команды — открывать туннель вручную перед каждой сессией в DBeaver быстро надоедает. Для этого сценария есть три рабочих подхода, в порядке возрастания строгости:

WireGuard как приватная сеть для команды. Поднимается один раз на сервере (или на отдельном узле), после чего каждый участник команды получает свой конфиг клиента и подключается к внутренней сети, в которой сервер БД виден по внутреннему IP — так, будто он в том же офисе. Порт СУБД при этом остаётся закрытым для интернета и открыт только для адресов внутри WireGuard-подсети. Как поднять WireGuard на VPS с нуля разобрано в статье про установку WireGuard на VPS, а типовая схема доступа именно для распределённой команды — в статье VPN для удалённой команды.

Белый список конкретных IP в firewall — вариант проще VPN, если у команды статические IP-адреса (офис с фиксированным адресом, статический IP дома через провайдера):

sudo ufw allow from 198.51.100.10 to any port 5432 proto tcp
sudo ufw allow from 198.51.100.20 to any port 5432 proto tcp
sudo ufw deny 5432/tcp

Порядок правил важен: конкретные разрешения должны идти раньше общего запрета. Слабое место этого подхода — динамические IP (домашний интернет без статического адреса, работа из кафе или в поездке) требуют постоянно обновлять список, а сам IP-адрес можно подделать в отдельных сценариях (IP spoofing на уровне пакета) сложнее, чем украсть пароль, но это не защита от компрометации самого разрешённого хоста. Базовые принципы настройки firewall на VPS — в статье про установку и настройку ufw.

Bastion-хост — самый строгий вариант, оправданный при высоких требованиях к безопасности или регуляторных требованиях (доступ к продакшн-данным с логированием сессий). Отдельный небольшой сервер с открытым только SSH становится единственной точкой входа: администратор сначала подключается по SSH к bastion, а уже с него — ко внутренней сети, где стоит база. Сама база не имеет ни белого списка внешних IP, ни отдельного VPN-конфига на каждого — только доступ с bastion по приватной сети. Это добавляет одно лишнее звено в цепочке подключения, зато даёт единую точку аудита («кто и когда заходил на bastion») и возможность мгновенно отозвать доступ одному человеку, не трогая конфигурацию остальных.

Сравнение подходов:

ПодходПорт БД открыт наружуПодходит дляСложность внедрения
0.0.0.0/0 на порт БДДа, всему интернетуНикогда для продакшенаМинимальная (и в этом ловушка)
SSH-туннень на разовую сессиюНет, открыт только SSHРедкая ручная отладка одним человекомНизкая
WireGuard для командыНет, открыт только UDP WireGuardКоманда с регулярным доступомСредняя
Белый список IPНет, открыт только для перечисленных адресовКоманда со статическими IPНизкая-средняя
Bastion-хостНет, открыт только SSH на bastionВысокие требования к аудиту доступаВысокая

Если база растёт и разговор идёт уже не про один сервер для отладки, а про production-инстанс с требованиями к производительности и резервному копированию, стоит отдельно прикинуть, какой VPS вообще нужен под такую нагрузку — разбор конкретных требований есть в статье про выбор VPS для базы данных.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Что делать прямо сейчас, если такое правило уже открыто месяцами и я это только что обнаружил?

Первым делом закрыть порт в firewall (sudo ufw deny 5432/tcp или удалить правило 0.0.0.0/0 в security group), затем проверить логи подключений СУБД на предмет обращений с незнакомых IP, и сменить пароль на всякий случай — даже если явных следов компрометации нет, лучше исходить из того, что порт мог быть просканирован за всё время, пока был открыт.

SSH-туннель работает медленнее, чем прямое подключение?

На практике разница для обычной работы с GUI-клиентом (запросы, просмотр таблиц) незаметна — узкое место почти всегда сама сеть и латентность до сервера, а не шифрование SSH. Ощутимая разница появляется только при передаче действительно больших объёмов данных через туннель (полный дамп, миграция), где стоит рассмотреть прямой pg_dump по SSH вместо GUI-клиента.

Можно ли просто ограничить доступ к базе на уровне самой СУБД (pg_hba.conf), не трогая firewall?

Это дополняет, но не заменяет закрытый порт. Правильная настройка pg_hba.conf действительно ограничивает, с каких адресов и какими методами можно аутентифицироваться на уровне PostgreSQL — но пока порт открыт в firewall, СУБД всё ещё принимает TCP-соединение и отвечает на попытку логина, что даёт атакующему возможность брутфорсить или искать уязвимости в самом протоколе аутентификации. Закрытый порт в firewall — первый и обязательный слой, pg_hba.conf — второй.

А если нужен доступ разработчика с ноутбука, который постоянно меняет сеть (кафе, коворкинг, дом)?

Это ровно тот случай, где статический белый список IP не работает, а WireGuard подходит лучше всего — клиент подключается к внутренней VPN-сети независимо от того, из какой физической сети он вышел в интернет, и с точки зрения firewall на сервере БД всегда виден один и тот же внутренний IP из WireGuard-подсети.

Есть ли разница между «временно открыл на час» и «оставил на неделю» с точки зрения риска?

Разница есть, но не такая большая, как кажется — учитывая, что автоматическое сканирование порта обычно происходит в пределах часов, даже открытие «на час для отладки» уже попадает в зону риска обнаружения ботом. Единственный по-настоящему безопасный вариант — не открывать порт вообще, а не «открыть покороче».

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →