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

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

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

Сгенерил картинку в Midjourney [1], потому что ленился найти фотки своего домашнего сервера

[1]

Что это было

Технически, меня атаковали флудом 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 [1]. Смысл их в том, что если уж трафик идёт через Cloudflare, то его не нужно гонять по голому TCP: транспортом может выступит туннель между конечным и проксирующим сервером. Устанавливается просто: инструкции и команды есть прямо в админке. А дальше всё просто: указываем критерии перенаправления и целевой сервис.

[🖼️ image] [2] Вот кусочек настройки для примера. На 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-читалку [3] и альтернативный Youtube-клиент [4], и это отлично работает.

[1] туннели Cloudflare

[2] [🖼️ image]

[3] RSS-читалку

[4] Youtube-клиент

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

[5] рисков

P.S.

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

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

[6] про кофе