Все записи
7 мин

«А база точно не торчит наружу?» — один вопрос превратился в аудит безопасности сервера

безопасностьинфраструктураclaude code

Если хотите проверить, торчит ли ваша база данных или Redis в интернет, посмотрите в docker-compose, на какой адрес контейнер пробрасывает порт. Если там стоит 0.0.0.0 или просто номер порта без адреса перед ним — значит сервис слушает весь внешний интерфейс и доступен любому, кто знает IP и порт. А если перед портом стоит 127.0.0.1, то достучаться можно только с самого сервера, и снаружи он закрыт наглухо. Я к этому пришёл не из учебника, а после одного короткого неудобного вопроса, который сам же себе и задал, и который вместо ответа «да нормально всё» развернулся в полноценный аудит безопасности всего сервера, где у меня уживается не один проект.

Дело было так. Сижу я по работе над Картарой, гоняю задачи, и вдруг ловлю себя на простой мысли, которую почему-то раньше никогда не додумывал до конца. И спрашиваю прямо: а API, база и Redis у меня вообще не должны быть «наружу» и под логином-паролем? Вопрос-то элементарный, на уровне «а дверь я запер?», но именно такие элементарные вопросы обычно и оказываются теми, на которые ты честно не знаешь ответа.

Самый неудобный вопрос — тот, который боишься задать себе сам

Знаете, в чём прикол вайбкодинга и вообще разработки с ИИ, когда ты соло и командуешь всем с телефона? Ты запускаешь проекты быстро, MVP за вечер, второй проект на том же сервере через неделю, потом третий, и всё это поднимается через docker-compose, всё работает, юзеры заходят, и ты уже переключился на следующую задачу. А понимание того, где что развёрнуто и как именно проброшены порты, в голове постепенно растворяется. Я честно поймал себя на том, что уже не держу это в голове, и первый мой рефлекс был даже не про безопасность, а про банальное «а где она вообще, эта Картара лежит». Серверов-то у меня не десяток, всё в одном месте, но даже это «одно место» к определённому моменту превращается в коробку, куда ты складывал проекты по мере того, как они рождались, и заглядывать туда лишний раз уже лень.

И вот в этом и есть подвох. Когда ты поднимаешь сервис локально, чтобы потыкать, тебе удобно открыть порт наружу — подключился клиентом к базе, посмотрел, закрыл. А потом этот же compose уезжает на боевой сервер как есть, вместе с открытым портом, и про это никто не вспоминает, пока что-нибудь не случится. Базы и Redis обычно вешают на нестандартные порты, чтобы не путаться, когда на одной машине крутится несколько проектов, и вот это создаёт ложное ощущение защищённости. Мол, кто там угадает, что у меня база висит не там, где принято. Да никто и не угадывает — боты сканируют весь диапазон портов подряд, им вообще плевать, стандартный он или нет.

Что значат эти 0.0.0.0 и 127.0.0.1 на пальцах

Тут стоит на секунду остановиться и разобрать саму суть, потому что без неё весь пост превращается в «поменял буковки и молодец». Когда вы в docker-compose пишете проброс порта и перед ним стоит 0.0.0.0 (или вообще ничего не стоит, потому что это и есть дефолт), вы говорите контейнеру: слушай этот порт на всех сетевых интерфейсах машины разом. А у сервера, который смотрит в интернет, один из этих интерфейсов — публичный, с белым IP. То есть ваша база в этот момент доступна не только вашему же бэкенду на этой же машине, а вообще любому, кто знает адрес сервера и нужный порт. А вот 127.0.0.1 — это loopback, петля, адрес «сам на себя»: порт, привязанный к нему, слушает только запросы, пришедшие изнутри самого сервера, и снаружи к нему пакеты физически не доходят. При этом ваш собственный бэкенд, который крутится на той же машине, продолжает ходить в базу как ни в чём не бывало, потому что для него это локальный запрос. Вот почему правка с 0.0.0.0 на 127.0.0.1 — безопасная по своей сути: снаружи дверь закрывается, изнутри всё работает как работало.

Что мы реально пошли проверять

План родился сразу и по описанию был простой, а по исполнению муторный. Надо было пройтись по всем docker-compose на сервере, по каждому проекту, и глазами посмотреть, какие сервисы на какие адреса пробрасывают порты. Не «вроде всё ок», а реально открыть каждый файл и пройти строчку за строчкой: вот это API, вот это база, вот это кэш, и где из них привязка к 127.0.0.1, а где порт болтается на 0.0.0.0, открытым на весь мир.

Я тут сразу оговорюсь по-честному, как привык. Код по этой задаче писал не я руками — я и не умею, и никогда руками не писал, я стал «разрабом» только через ИИ и агентский кодинг. Я оркестрировал: Claude Code прошёлся по конфигам, а я задавал вопросы и принимал решения. Кстати, не всегда такой аудит проходит гладко — однажды Claude вообще заблокировал мне мой же security-аудит, упёршись в свой cyber-safeguard, но это уже отдельная история. И вот когда мне предложили вариант «давай пройдусь по всем compose'ам и поправлю биндинги на 127.0.0.1», первая моя реакция была не «давай скорее», а ровно та, которая и должна быть у человека с боевым сервером: а это ничего не сломает? можно так-то?

Вот это «а это ничего не сломает» — на мой взгляд, самая важная фраза во всей истории. Потому что менять биндинги портов на работающем проде — это вам не «поправил строчку и забыл». Если у тебя, скажем, API ходит в базу не через внутреннюю docker-сеть, а через внешний адрес и проброшенный порт (так бывает, когда конфигурацию собирали наспех), то ты привязываешь базу к 127.0.0.1, перезапускаешь — и приложение внезапно не может достучаться до собственной базы. И вот сидишь ты ночью, юзеры пишут, что ничего не открывается, а ты пытаешься понять, почему. docker-compose вообще умеет молча подложить такую свинью — у меня уже был случай, когда деплой прошёл, контейнер поднялся, а фича не заработала из-за непроброшенной переменной. Поэтому сначала проверяем, что закрытие порта ничего не отрежет, и только потом закрываем.

Почему внутренняя сеть docker — и есть нормальный ответ

Если разбираться чуть глубже, то правильная картина такая. Контейнеры внутри одного docker-compose видят друг друга по именам сервисов через внутреннюю сеть, и API совершенно не обязан ходить в базу через внешний порт хоста — он обращается к ней напрямую внутри этой сети. То есть наружу порт базы вообще не нужно пробрасывать ни на какой адрес, ни на 127.0.0.1, ни тем более на 0.0.0.0. Локальный проброс на 127.0.0.1 нужен только если ты сам иногда подключаешься к базе с сервера через SSH-туннель — вот для этого его и оставляют. А открытый на 0.0.0.0 порт базы не нужен почти никогда, и если он там есть, это просто забытый хвост от того момента, когда ты тестировал локально.

И вот тут мы и упираемся в ту самую глубину, которую я люблю докручивать. Поверхностный ответ — «повесь логин-пароль, и всё». Но логин-пароль на базе, которая торчит наружу, это как поставить хороший замок на дверь, которую ты при этом оставил приоткрытой и подпёр кирпичом. Да, пароль усложнит жизнь, но любой дефолтный креденшл, который ты забыл сменить, любая старая версия с известной уязвимостью — и тебя выгребают. Redis тут вообще отдельная классика, защита по дефолту слабенькая, и открытый наружу Redis — это то, на чём горели ребята куда серьёзнее меня. А если порт просто не торчит наружу, то и атаковать нечего: бот сканирует и видит закрытую дверь. Безопасность через недоступность всегда надёжнее безопасности через «ну там же пароль стоит».

Что я из этого вынес

Главный вывод тут даже не технический. Когда ты соло-разработчик и у тебя несколько проектов на одном сервере, ты неизбежно теряешь понимание того, что и как у тебя развёрнуто. И это не потому, что ты раздолбай, а потому, что человеческая голова не контейнер для хранения всех биндингов всех портов всех проектов, которые ты поднял за полгода. И единственная защита тут — это привычка периодически задавать себе тупой вопрос и идти проверять руками, а не отмахиваться «да наверное всё ок».

По итогу план был такой: пройти по всем compose, найти все сервисы, которые слушают на 0.0.0.0, проверить, что их закрытие ничего не отрежет в работающих приложениях, и пересадить биндинги на 127.0.0.1 там, где локальный доступ всё-таки нужен, а где не нужен — убрать проброс совсем. Никакой магии, просто дисциплина. И эту проверку отлично делает связка из ИИ-агента и человека, если вы вообще используете ии для автоматизации такой рутины: агент быстро прочёсывает десятки конфигов и показывает, где что торчит, а человек решает, что можно трогать, а что сначала надо перепроверить, чтобы не уронить прод.

Если вы тоже катаете свои MVP и пет-проекты на одном арендованном сервере через docker-compose — задайте себе сегодня этот неудобный вопрос. Откройте конфиги и посмотрите, на какой адрес у вас слушают база и Redis. Очень может быть, что прямо сейчас они на 0.0.0.0 спокойно отвечают любому желающему из интернета, а вы об этом даже не думали — ровно как не думал и я, пока сам себя не спросил. Про то, как один забытый порт способен слить чужие данные, у меня уже была отдельная история — почитайте как друг открыл моё приложение с моего телефона, и AI рассказал ему мою жизнь, там та же болезнь, только с другого боку. Вот и делайте выводы.