С самого начала своего существования Умное голосование каждый год подвергалось DDoS-атакам — в период со дня публикации рекомендаций до дня выборов. Первую атаку мы зафиксировали в 2019 году, аккурат за неделю до голосования. Разумеется, мы ее ждали. К тому времени на сайте уже была развернута защита от атак, и она с честью приняла на себя удар и так же с честью упала. Пожалуй, самая масштабная атака произошла в этом году, 2022-м, и с ней мы успешно справились. Миллиарды запросов к сайту и тысячи заблокированных адресов за сутки — такова была цена доступности Умного голосования на московских муниципальных выборах. В посте мы рассказываем об истории Умного голосования и опыте нашей команды по борьбе с DDoS: о методах атак, архитектуре и технологиях по защите.
Чтобы понять некоторые из принятых технических решений, необходимо ненадолго заглянуть в прошлое. История Умного голосования восходит к 2018 году, когда Алексей Навальный впервые выдвинул идею консолидированного протестного голосования. К тому времени в России уже несколько лет работала система интернет-цензуры, осуществляемая Роскомнадзором. РКН рассылал списки заблокированных сайтов провайдерам, и те были обязаны блокировать к ним доступ.
Разумеется, команда Навального понимала, что попытки заблокировать сайт будут предприняты примерно сразу после его открытия, а значит, нужны были такие решения по хостингу, какие РКН на том этапе технически не мог заблокировать. В то время на провайдерах еще не было массово установлено оборудование DPI, и блокировки осуществлялись в лучшем случае по IP-адресу. Соответственно, сайт нужно было размещать на IP-адресах, общих с другими важными сайтами, чтобы РКН не смог его заблокировать, не сломав пол-интернета. В ретроспективе это решение оказалось совершенно правильным, и сайт так никогда и не был заблокирован по IP.
Итак, в начале 2019 года был запущен сайт на домене vote2019.appspot.com. Домен appspot.com принадлежит Google, и в нем автоматически создаются поддомены для всех проектов, использующих App Engine. Все сайты в appspot.com доступны по одним и тем же IP-адресам. Мы не рискнули тогда запускаться на отдельном домене для сайта, чтобы имя сайта прямо кричало, что оно работает на общей площадке и блокировать его нельзя.
В то время уже существовали коммерческие системы защиты от DDoS, например, в России был популярен Qrator. Но именно тот факт, что фронтенд приложения должен был работать на Google App Engine, не позволял нам использовать готовые решения. Если бы мы спрятали сайт за одним из них, то сервис защиты от DDoS выделил бы сайту отдельный IP-адрес, и он был бы немедленно заблокирован Роскомнадзором.
Достоинством App Engine является отличная масштабируемость — пока на сайт нагрузки нет, будет запущено всего 1-2 инстанса приложения. А если приедет большая нагрузка, то в течение нескольких секунд App Engine сможет поднять тысячи инстансов нашего приложения и переварить нагрузку. Учитывая все это, мы приняли решение строить собственную систему защиты от DDoS на базе App Engine.
Архитектура
Чтобы не разориться на App Engine, мы решили не запускать на нем тяжеловесное Умное голосование целиком. Вместо этого мы стали гонять там легковесный кеширующий прокси-сервер на Go, который проверяет трафик на признаки атаки и либо отбивает запросы, либо отдает ответы из кеша, либо проксирует их в основное приложение. Go в первую очередь был выбран из-за того, что он компилируемый — в результате приложение и запускается быстро, и ресурсов тратит мало, что очень важно для быстрого масштабирования при росте нагрузки и для экономии денег соответственно.
Поскольку во время атаки каждый инстанс прокси-сервера видит только малую долю всего трафика, принимать локальные решения о том, кого блокировать, а кого нет, качественно не получится — без полноты информации хороших решений, как известно, не принять. Поэтому был придуман отдельный бэкенд-сервис — «координатор», который собирает статистику со всех проксей, находит паттерны в трафике и посылает обратно нашу «политику партии» — кого мы баним, а кого нет.
Координатор также предоставляет базовые административные функции — показ статистики, мониторинг, ручное управление политикой банов и т. д. Координатор записывает свое состояние и всю телеметрию в базу данных для того, чтобы ему не были страшны перезапуски.
Анализ трафика
Все прокси Front Shield ведут локальный реестр обслуженных запросов и раз в 2 секунды отправляют его координатору в HTTP-запросе. В ответ координатор возвращает текущий бан-лист — список правил, по которым должны баниться запросы.
На стороне координатора все запросы анализируются, и если по каким-то IP-адресам обнаруживается подозрительная активность, то они попадают в бан на сутки. Анализ трафика делается асинхронно с приемом данных от инстансов прокси, поэтому решения могут чуть-чуть отставать от реального времени. На практике, если какой-то IP-адрес начинает резко слать много запросов, в бан он попадает в среднем через 5 секунд (2 секунды на доставку отчетов о трафике координатору, 1 секунда на анализ, 2 секунды на доставку бан-листов обратно на фронтенд).
Крещение огнем
Итак, остается около недели до голосования 2019 года. Приходит алерт от мониторинга, что сайт Умного голосования недоступен. Мы смотрим в консоль Front Shield и видим небольшой и очень короткий всплеск трафика. Было даже странно, что система не справилась даже с такой скромной нагрузкой. Начинаем разбираться, и все быстро встает на свои места — консоль самого App Engine показывает 20 тысяч запросов в секунду. Просто инстансы Front Shield приняли все эти запросы и массово упали от нехватки памяти. App Engine увидел нагрузку и быстро добавил инстансов, но атакующие, видимо, убедились, что в силах положить сайт, и быстро остановили атаку, рассчитывая устроить то же самое в день голосования.
Так началась борьба меча и щита. На каждую проблему, обнаруженную нападающими, мы придумывали решения. В данном случае была реализована техника load shedding, которая анализирует загрузку процесса и, если она приближается к своему пределу, начинает возвращать ошибки на раннем этапе обработки запроса, не дожидаясь падения. Это позволяет избежать каскадных аварий, когда падения одних инстансов увеличивают нагрузку на оставшиеся и дальше все разваливается, как домино.
В день голосования 2019 года все прошло довольно спокойно — было несколько атак без всяких последствий. Леонид Волков тогда постил графики Front Shield у себя в соцсетях.
Выборы 2020
После повсеместного внедрения ТСПУ на провайдерах особого смысла держать сайт Умного голосования живым не было. Именно поэтому наша команда выпустила дополнительные инструменты, такие как приложение под Android и iOS и телеграм-бот, в которых можно было узнать рекомендуемых кандидатов. Подробнее о технологиях для обхода блокировок, примененных в нашем приложении, можно почитать в нашем другом нашем посте. Поскольку в мобильном приложении пользователям не надо запоминать доменное имя и оно не является брендом, мы могли позволить себе менять домены раз в минуту, и DDoS-атаки просто не успевали сфокусироваться на сервере, как он уже менялся.
Веб-сайт же, разумеется, мы могли бы перенести на другой домен (не в appspot.com), но, во-первых, особого смысла с точки зрения блокировок в этом не было — любой сайт был бы во внесудебном порядке успешно заблокирован РКН, как только стал бы хоть немного заметен, во-вторых, скорее всего, если бы мы даже и закрылись за CloudFlare или Google Cloud Armor, как показывает практика, во время больших атак процентов 10 трафика все равно пролезает в бэкенд, и нам бы все равно приходилось тратить ресурсы на его обработку и загрузку CPU. Система, которая «знает» трафик, «знает» паттерны поведения пользователей, может быть гораздо более точной в обнаружении ботов и может защитить бэкенд гораздо эффективнее.
Так и вышло: DDoS-атак на бэкенд приложения практически не было, а атаки на сайт мы успешно отбили — ничего особенно выдающегося в тот год не было.
Выборы 2021
В этот год были очень крупные и важные выборы — избирались депутаты Госдумы и мы понимали, что атаки на Умное голосование достигнут беспрецедентных масштабов. Поэтому мы сделали еще одно крупное обновление — добавили еще один слой блокировок перед прокси Front Shield. У App Engine есть встроенный файрвол, который можно настраивать через API. Он позволяет вообще не платить за отбивание запросов с забаненных адресов — они даже не доходят до приложения. У этого решения есть один минус — жесткое ограничение на число правил файрвола: их всего 1000. Нам надо было придумать, как использовать их максимально эффективно. Мы заметили, что не все боты одинаково производительны — некоторые могут слать во много раз больше трафика, чем большинство других. Поэтому мы научили координатор собирать статистику: с каких IP-адресов идет больше всего трафика, который мы определяем как DDoS, те и заносить в файрвол. Но как только адрес попадал в файрвол, мы тут же «теряли» его из мониторинга — узнать, продолжает ли он отправлять запросы, а если да, то в каком количестве, становилось невозможно. Поэтому был реализован алгоритм, который со временем «забывает» старые грехи IP-адресов и замещает их в таблице новыми, самыми «злыми» на данный момент. Если разбаненный адрес продолжит слать трафик, то он быстро вернется в бан.
В неделю перед голосованием, когда мы опубликовали рекомендации, злоумышленники не просто начали заливать Front Shield огромным трафиком, но и стали действовать креативно. Они искали изъяны в системе и били так, чтобы пробить все слои кеширования при помощи рандомизации и достать до бэкенда. У нас во время голосования (а оно в этот раз было трехдневным) дежурили команды разработчиков и бэкенда, и Front Shield. Какие-то атаки мы отражали, дорабатывая код Front Shield прямо на лету, — скажем, делали нормализацию запросов или регулировали параметры анализа статистики. Например, одним из паттернов атаки было использование какого-то гигантского ботнета, где каждый из IP-адресов слал относительно мало трафика, но суммарно выходило очень много, и нам пришлось увеличить интервал сбора статистики в памяти координатора, чтобы обнаруживать этих ботов на основании более длительного времени. Какие-то ошибки приходилось исправлять прямо на бэкенде. Например, мы обнаружили, что поиск адреса «а а а а а а а а а а а а а а а а а а а а а а» был очень дорогим, и при помощи небольшого исправления алгоритма быстро справились с проблемой.
Надо отметить, что в основном атаки шли на бэкенд приложения, потому что сайт был к тому времени уже заблокирован, и его важность была сильно ниже, чем у приложения.
Выборы 2022
Все началось с осени 2021 года. Кремль начал под ноль зачищать информационное пространство, и под нож попали социальные сети. Пожалуй, Instagram стал той их роковой ошибкой, в результате которой клиенты VPN прописались в телефонах у огромного количества даже технически не подкованных россиян, и внезапно оказалось, что важность сайта Умного голосования снова возросла.
Вечером 10 сентября 2022 года, аккурат перед ключевым днем голосования, на сайт была запущена огромная DDoS-атака, которая продлилась более суток. Она продолжилась и после выборов, поэтому мы были вынуждены после окончания голосования совсем отключить Front Shield, чтобы не расходовать ресурсы зря. Миллиарды запросов к сайту и тысячи заблокированных адресов за сутки — такова была цена доступности Умного голосования на московских муниципальных выборах.
К сожалению, из-за принципа работы файрвола App Engine мы так никогда и не узнаем, сколько трафика пришло на сайт, но во время кратковременных сбоев, о которых мы напишем ниже, на какое-то время мы переставали обновлять файрвол, и в пиках прокси обрабатывала до 250 тысяч запросов в секунду, а отдаваемый сервисом трафик достигал 100 гигабит в секунду. Скорее всего, реальный масштаб атаки был в несколько раз больше.
Один из обычных методов борьбы с DDoS — территориальные ограничения. Например, если нас не интересует трафик из Южной Америки, то можно его сразу блокировать, а не пытаться даже обслуживать. В случае же Умного голосования так делать было нельзя, потому что в условиях блокировок всем пользователям из России приходилось пользоваться VPN, и страна, которую мы видели на фронтенде, была какой угодно, но только не Россией.
Одной ошибкой, которую мы совершили, была отдача неверных заголовков кеширования. Дело в том, что перед тем, как запросы достигают приложения на App Engine, они обрабатываются Google Frontend, который имеет свой собственный слой кеширования. Если приложение своими заголовками Cache-Control позволяет кешировать ответы, то Google Frontend это запомнит и будет в будущем отдавать их из кэша. Приложение даже не догадывалось, что у нас есть такие гигантские источники запросов и не могло занести их в файрвол.
Это продолжалось в течение нескольких часов. Поменяв заголовок Cache-Control с «public, max-age=120» на «private, max-age=120», мы решили проблему, и боты быстро улетели в бан.
Был еще ночной «тест» со стороны нападающих накануне основной атаки в день голосования. Количество забаненных запросов держалось на уровне 20-30k rps, но иногда пробы достигали и 250 тысяч. Напомним, что мы считаем трафик уже после файрвола, то есть сколько там было до него — мы не знаем, но, скорее всего, в несколько раз больше.
Анализируя логи, мы поняли, что они отыскали один из эндпоинтов, на который у нас не была включена нормализация, и рандомизированными запросами смогли на несколько минут уложить бэкенд. Мы поспешно добавили в правила нормализацию для этого эндпоинта.
В день голосования атака усилилась. Front Shield без остановки блокировал не менее 40 тысяч запросов в секунду (опять же, это то, что прошло через файрвол), а ко второй половине дня голосования трафик дорос до 100 тысяч запросов в секунду.
Нам пришлось несколько раз добавлять памяти координатору из-за огромного количества данных, которые он должен был держать и обрабатывать. В мирное время eму нужно около 50 мегабайт, во время прошлых DDoS-атак он мог вырасти до гигабайта, а в этот раз, особенно под конец дня, ему понадобилось больше 20 гигов.
В какой-то момент мы заметили, что координатор сломался. Оказалось, что он уже 45 минут не отвечает ни на какие запросы и находится в жестком дауне. Мы быстро обнаружили причину — из-за большого числа операций записи binlog базы данных переполнил диск, и все записи остановились. Как только мы решили эту проблему, координатор «проснулся» и принял первые за 45 минут отчеты от проксей — 200 тысяч запросов в секунду. Скорее всего, на протяжении этих 45 минут трафик на прокси плавно нарастал, поскольку никто не обновлял файрвол, и дорос до 200 тысяч.
Причудливый узор во время пика трафика — это артефакт мониторинга, возникший из-за билинейной интерполяции точек и большого пропуска. Хотя уж больно он похож на кривую сложности Dwarf Fortress!
Когда голосование близилось к концу, App Engine показывал, что за последние сутки было обслужено более 2,4 миллиарда запросов, и это отражало только то, что прошло через файрвол и смогло добраться до прокси. Оценочно можно предположить, что трафика было в несколько раз больше. Скорее всего, около 10 миллиардов запросов за сутки.
Таким образом, нас защитило несколько слоев: файрвол отбивал запросы от самых крупных ботов, прокси Front Shield отбивала «длинный хвост» с более мелких, далее прокси из локального кэша отдавала часть ответов на популярные запросы, и все остальное уже шло в бэкенд. На бэкенде запросы встречала «батарея» nginx с дисковым кешем, ну и дальше уже само приложение. Такая многослойная котлета позволила нам сохранять устойчивость сервиса на протяжении всего времени работы сайта.
За один только день голосования в бан-лист Front Shield было внесено более 30 тысяч IP-адресов. Злоумышленникам не удалось достичь своей цели от слова совсем. Все системы функционировали штатно — мы держали нагрузку, и сайт отлично работал, быстро и надежно. Также работали телеграм-бот и приложение, которые были полностью изолированы от сайта.
А у нас в трекере висят тикеты — вынести все захардкоженные настройки в файлы конфигурации, написать документацию и выложить исходники Front Shield в общий доступ. Надеемся, у нас до них дойдут руки :)
Сейчас сайт votesmart.appspot.com выключен, так как DDOS-атака продолжается и по сей день. Вероятно, заказ на атаку был оплачен авансом вперед на несколько дней, и потрачены бюджетные средства. Выборы закончились, и мы решили не использовать серверные ресурсы без необходимости.
Комментарии (26)
Geckelberryfinn
14.09.2022 13:26+1А сколько людей в вашем департаменте занимаются отражением DDos и разработкой Front Shield ?
darnley
14.09.2022 13:30+15Во-первых, спасибо огромнейшее за работу! И за конкретно эту и вообще.
Во-вторых, извините за выпячивание мелочей, но какая библиотека рисует такие графики? Ну чтобы никогда ей самому не пользоваться. Мне кажется, выкатить код, при котором график умеет чуток двигаться по уменьшению икса, — это позор. Это скорее для результатов ДЭГ подходит :)NavalnyTeam Автор
14.09.2022 15:12+12Спасибо вам! Это библиотека Chart.js :) Но она тут не причём - просто мы включили билинейную интерполяцию, чего нельзя делать для временных рядов. Пока в них не было разрывов по 45 минут, всё было ок. Появился разрыв - её тоже порвало. Конечно это всё отключается легко.
litalen
14.09.2022 13:59+1Скажите пожалуйста, а почему просто не продублировать данные на какой-нибудь гитхаб? Вы считаете пользователей своего сайта достаточно продвинутыми, чтобы использовать VPN для доступа к вашему многострадальному сайту, но недостаточно чтобы самостоятельно нажать Ctrl+F на странице?
Geckelberryfinn
14.09.2022 14:02+4Вроде так и делали в 21 году. Не помню, то ли застрайкали репозиторий, то ли роскомпозор оперативно вмешался.
litalen
14.09.2022 14:19+2Да, вот и не понимаю почему прекратили так делать. РКН не вмешивался вроде, да и не мог отдельную страницу заблокировать под HTTPS.
Там гитхаб оштрафовали в итоге, но спустя почти 3 месяца после собственно голосования, когда гитхаб отказался удалять данные. Да и не гитхабом единым же, не вижу недостатка сервисов на которых можно просто запостить текст. Текст можно даже ЭЦП оснастить.Ghool
14.09.2022 19:39+6если выкладывать на 100500 площадок - то другая, так сказать, сторона, начнёт выкладывать такие же списки но с другим содержимым и пользователи могут перепутать. Ну и главное - как понять, валидный ли это список?
единая точка хороший вариант потому что она довереннаяно да - есть проблема с отказоустойчивостью.
amarao
14.09.2022 14:04+1Насколько я понимаю, одна из задач - это осложнить снятие кандидатов, то есть предоставить информацию голосующим, но не предоставлять мешающим голосовать.
litalen
14.09.2022 14:15+1Так выкладка данных в этом году насколько я понимаю была буквально за несколько дней до, можно ведь и вообще одновременно с началом голосования. Да и не думаю, что если есть такие ресурсы на DDoS то нет таких же ресурсов на распределенный скрейпинг API (там вообще от него защиты может и не быть).
kartvladek
14.09.2022 16:22-13Зачем вообще это защищать если итоги голосования итак заранее известны? Смешно прям
iddqda
14.09.2022 17:49+11Немного оффтоп, но надеюсь вы доставите пожелание до команды, отвечающей за фичи
Пожелание следующее:
сделайте пожалуйста кошельки еще в паре блокчейнов в которых не такая конская комиссия как в BTC и ERC20
скажем TRON и BSC было бы весьма удобно
а то иногда возникает желание задонатить, скажем 10USDT, но комиссия в $6 это желание отбивает
BugM
14.09.2022 19:51+5Отдавать статичные джейсоны можно прямо с nginx. Плюс самый тупой балансировщик. Все остальное делать на клиенте. Серверная логика вообще не нужна. Десятков ядер хватит с запасом на 100к рпс. Пусть даже 100 ядер.
И зачем это все? Задачка «сделать нероняемо в принципе» решается девопсом средней руки за неделю. И месяц фронта чтобы интерфейс нарисовать.
anonymous
НЛО прилетело и опубликовало эту надпись здесь
litalen
Ну, емейлы сайт не собирает теперь вроде (наконец-то), а донаты и вовсе в крипте принимают, что на уровень повыше для обывателя чем "скачать и включить VPN".
NavalnyTeam Автор
Здесь стоит упомянуть, что по России более 20 млн адресных узлов и более 90 тысяч избирательных комиссий. Данные могут меняться: кандидатов могут снимать, вылезать ошибки и все это должно синхронизироваться в режиме реального времени между ботом, сайтом и приложением. Геокодинг (а он обязательно нужен, чтобы по адресу найти избирательный участок) - ресурсоёмкая операция. Загрузить весь классификатор адресов в браузер в виде JSON проблематично.
litalen
А зачем весь сразу? Дайте пользователю возможность выбрать свой населенный пункт из списка, далее можно даже округ/район из возможно следующего списка (возможно промежуточный json). И размер "финального" json с адресами для браузера будет уже вполне подъемным. Так и нагрузка на сервер упадет до практически нуля - только файлы отдавать, остальное клиент делает сам. Такое можно уже и в IPFS тот же поместить (там и файлы обновлять можно через IPNS, оставляя изначальную ссылку неизменной), а в него есть масса веб-гейтов в "обычном" интернете (от того же cloudflare например).
anonymous
НЛО прилетело и опубликовало эту надпись здесь