Продовжуємо серію про відмовостійкий DNS. У частині 3 ми поставили dnsdist спереду - з балансуванням і failover. Але є ще одна загроза, з якої, власне, усе й починалось: резолвери (чи зловмисники) заливають сервер запитами, pdns не встигає, і страждають усі. Авторитативний pdns сам погано вміє обмежувати - а от dnsdist для цього й створений.
Додаємо два шари захисту: ліміт запитів на один IP (з трюком, що форсує TCP) і динамічні блоки, які самі банять набридливий IP на час.
Що потрібно зробити?#
Налаштувати в dnsdist захист від флуду, а тоді - перевірити його наживо: з сусідньої машини залити сервер потоком запитів (dnsperf) і побачити, що потік відсікається, абʼюзер потрапляє в тимчасовий бан, а легітимний клієнт з іншого IP працює як ні в чому не бувало. Двопанельний запис: ліворуч ns1 (наш DNS, якого захищаємо), праворуч ns2 (грає резолвер, що заливає).
З чим працюю?#
Дві ноди з попередніх частин. Тепер ns1 - це «захисник» (dnsdist + pdns), а ns2 зіграє роль резолвера, що заливає ns1 запитами. Генеруватимемо навантаження через dnsperf:
| |
Далі те саме, що в записі, тільки вже можна копіювати.
Спершу - без захисту#
Заливаємо ns1 потоком 800 запитів/с протягом 8 секунд:
| |
| |
Усі 6400 дійшли. dnsdist (з кешем пакетів із частини 3) це тягне, але нічого не фільтрує - а в реального авторитативного сервера за таким флудом може стояти значно важчий бекенд.
Захист: ліміт на IP + динамічні блоки#
Додаємо в /etc/dnsdist/dnsdist.conf два правила:
| |
Два різні підходи:
MaxQPSIPRule(100)+TCAction()- миттєвий запобіжник. Щойно один IP переступає 100 qps, dnsdist відповідає йому з прапором TC=1 («відповідь не вмістилась, спитай по TCP»). Чесний резолвер слухняно перепитає по TCP - це дешево. А ось спуфлений UDP-флуд (із підробленою адресою джерела) TCP-рукостискання не подужає й відсіється сам. Класичний трюк проти amplification.dynBlockRulesGroup+setQueryRate- розумніший шар. dnsdist веде облік запитів на кожен IP і вmaintenance()(викликається раз на секунду) тимчасово банить тих, хто перевищив поріг. Тут: понад 20 qps за 5-секундне вікно - бан на 20 секунд зDrop(мовчазне відкидання). За 20 секунд бан спадає сам.
Пороги (100, 20/5/20) - для демо занижені, щоб флуд спрацював одразу. Під свій трафік підбирайте вищі: подивіться звичайний qps на один IP і беріть із запасом.
| |
Той самий флуд - із захистом#
| |
| |
Картина інша: dnsperf зумів видавити лише ~110 qps замість 800, а всього «долетіло» 886 відповідей замість 6400. Потік до pdns обрізано в рази - бо щойно ns2 переступив поріг, його запити почали відкидатись, і сам клієнт від браку відповідей сповільнився.
Кого забанило#
Дивимось активні блоки з консолі:
| |
| |
IP ns2 - у бані (Drop), ще на 6 секунд. А тепер головне - легітимний клієнт з іншого IP:
| |
Працює без затримки. Блок - точковий: карається лише той IP, що заливав, решта світу сервер бачить нормальним. І бан тимчасовий - за 20 секунд спаде сам, без жодного ручного втручання.
Граблі#
dnsperf- closed-loop. Його клієнти чекають на відповідь, перш ніж слати наступний запит. Тому коли dnsdist починає відкидати запити, падає й сама швидкістьdnsperf(звідси «1086 надіслано» замість 6400) - це нормально й добре показує ефект.maintenance()- це callback dnsdist, який викликається раз на секунду. Саме в ньому треба викликатиdbr:apply(), інакше динамічні блоки не застосовуються.DropvsTruncate.DNSAction.Dropмовчки відкидає (абʼюзер не отримує нічого). Можна натомістьDNSAction.Truncate- тоді навіть забаненим шлемо TC=1 (хай ломляться по TCP, якщо це чесний клієнт за NAT із сусідом-флудером). Вибір залежить від того, кого ви очікуєте по той бік.- Пороги не з голови. Занизькі - заб’єте легітимні сплески (наприклад, великий резолвер за CGNAT). Зависокі - флуд пролізе. Міряйте свій трафік.
Що маємо на цей момент#
Відмовостійкий authoritative DNS уже складається з чотирьох кроків:
- PowerDNS + MariaDB + API - свій сервер, зони в базі, керування через REST.
- Реплікація MariaDB - другий сервер на репліці, дані синхронні по приватній мережі.
- dnsdist: балансування і failover - вхід витримує падіння pdns чи цілої ноди.
- dnsdist проти флуду (ця частина) - вхід витримує й UDP-перевантаження.
Дані надійні (реплікація), вхід відмовостійкий (dnsdist + два NS) і захищений від флуду запитів. Той самий підхід - «логіку синхронізації віддаємо базі, а вхід - спеціалізованому балансувальнику» - масштабується і на три, і на пʼять серверів.
Хардинг на цьому не закінчується. Тут ми відсікали UDP-флуд (багато запитів), а є ще атаки по TCP - коли вичерпують не запити, а зʼєднання (slowloris, лавина конектів): для них у dnsdist свої важелі - ліміти одночасних зʼєднань на клієнта, запитів на конект і його тривалості. Плюс зону можна підписати DNSSEC (бекенд у нас уже з gmysql-dnssec=yes). Теми для наступних частин.



