Уйти от DDoS без инфры

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

· 3 минуты на чтение
Уйти от DDoS без инфры
Сгенерил картинку в Midjourney, потому что ленился найти фотки своего домашнего сервера

Что это было

Технически, меня атаковали флудом HTTP-запросами. Запросы сыпались массово в http(s)://IP_адрес_сервера/, а источник был распределённым: я насчитал порядка двух тысяч адресов. Никто не выдвигал никаких требований и не связывался со мной, так что цели атаки мне до сих пор не ясны.

Называя сервер «подкроватным» я немножко иронизирую, но суть в целом верна: это компьютер, собранный для хранения личных данных, стоящий в моей комнате в родительской квартире на небольшом бесперебойнике. Когда-то мы держали с друзьями там сервер Minecraft, интернет-радио и даже чат-бота для ICQ, а нынче на нём расположилась личная файлопомойка и пара других личных веб-сервисов и сайтов, включая вот этот. Когда-нибудь я, может, даже напишу пост о том, что и как у меня селфхостится. Всё аккуратно распихано по разным докер-контейнерам и как-то живёт. На 80 и 443 порту крутится nginx в контейнере (хотел переехать на traefik, но как-то не до того), маршрутизирующий трафик дальше в нужный порт. Собственно, nginx и принял нагрузку на себя, отдавая на все запросы страницу с ошибкой. Его ресурсов не хватало на такую нагрузку, поэтому запросы стали падать с таймаутом.

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

Прикрываемся Cloudflare-ом

Да, атака была максимально тупой, и конкретно её можно было бы попытаться решить иными путями. Но часть сайтов, включая этот блог, уже обслуживались Cloudflare, поэтому было решено уехать под него полностью. Везде, где можно, ползунок был передвинут с "DNS only" на "Proxied".

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

Поскольку сервер размещён в домашней сети за NAT-ом, к правилам файерволла роутера было добавлено требование: прокидывать трафик с 80 и 443 портов только для входящих запросов из пула IPv4-подсетей Cloudflare. Это частично спасло ситуацию: поток флуда прекратился. Но часть запросов так и не доходила до сервера, поскольку шла через IPv6, а мой старенький микротик не давал возможность создавать списки IPv6-диапазонов, несмотря на актуальную версию прошивки. Решение было оставлено как некоторый fallback, но в целом признано провальным.

Туннеллируемся

Идеальное же решение оказалось куда более хитрым, и в принципе позволило бы совсем закрыть 80 и 443 порты на роутере. Более того, оно позволяет жить вообще без торчащих наружу портов и без статического IP. Подкроватный хостинг стал простым как никогда!

Это решение — туннели Cloudflare. Смысл их в том, что если уж трафик идёт через Cloudflare, то его не нужно гонять по голому TCP: транспортом может выступит туннель между конечным и проксирующим сервером. Устанавливается просто: инструкции и команды есть прямо в админке. А дальше всё просто: указываем критерии перенаправления и целевой сервис.

Вот кусочек настройки для примера. На 80 порту всё так же висит nginx, отдающий только статику, поэтому ему приходится прописывать домен полностью. И ещё указывать маппинг на 127.0.0.1 в /etc/hosts.
💡
Хозяйке на заметку: если вы, как и я, любите docker-compose,то наверняка хоть раз задавались вопросом: как открыть порт из контейнера не совсем наружу, но для других сервисов на данном хосте. Да, дома выручает NAT с файерволлом, а ещё есть ufw, но должен же быть иной путь?
Всё просто: прописывайте в docker-compose.yaml целевой порт с указанием IP-адреса, например 127.0.0.1:8001. В таком случае к нему можно будет обратиться через loopback, но для внешних клиентов сервис будет недоступным.
Например, так (см скрин выше, на 8088 доступен этот сайт):
ports:
- 127.0.0.1:8088:2368

И да, я повторюсь, это решение очень неплохо подходит для домашнего хостинга вообще без статического белого IP-адреса. Cloudflare даже даёт возможность прокинуть таким образом SSH-терминал или VNC-клиент, и при желании прикрыть его (как и любой другой сервис) дополнительной авторизацией. Я использую его уже несколько месяцев для маленького компьютера, который нынче выполняет для меня роль домашнего сервера, обслуживающего Homeassistant, RSS-читалку и альтернативный Youtube-клиент, и это отлично работает.

Наверное, я не смогу рекомендовать это решение крупным сервисам, как минимум из-за некоторых рисков. Небольшим предприятиям это вполне может подойти, хотя скорее с платными тарифами, которые, впрочем, не такие уж и страшные. Ну а для тех, кто не имеет колоссальных вычислительных мощностей для содержания маленького бложика, пожалуй, такое бесплатное решение будет идеальным.

P.S.

Перед публикацией этого поста проверил — DDoS всё ещё продолжается, хотя и упирается в закрытые порты. Зачем? Кому это надо? Если у вас есть идеи — поделитесь, пожалуйста. Мне правда любопытно — может прошлый пост про кофе кого-то обидел?

Обсудить пост и поделиться мнениями можно в телеге.

Mastodon