Какие подходы и инструменты в Go можно использовать для базовой защиты веб-сервера от DDoS-атак?

Ответ

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

1. Ограничение частоты запросов (Rate Limiting)

Это основной механизм для борьбы с флудом. Он ограничивает количество запросов, которое клиент может сделать за определенный промежуток времени. Для этого удобно использовать пакет golang.org/x/time/rate.

import "golang.org/x/time/rate"

// 10 запросов в секунду с возможностью "всплеска" до 20
limiter := rate.NewLimiter(10, 20)

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if !limiter.Allow() {
        http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
        return
    }
    // ... обработка запроса
})

Для большей эффективности лимитер нужно создавать на основе IP-адреса клиента.

2. Установка таймаутов

Таймауты защищают от атак типа "Slowloris", когда злоумышленник устанавливает множество медленных соединений, исчерпывая ресурсы сервера. В http.Server важно настраивать:

  • ReadTimeout: Максимальное время на чтение всего запроса, включая тело.
  • WriteTimeout: Максимальное время на запись ответа.
  • IdleTimeout: Время, в течение которого открытое keep-alive соединение может простаивать.
srv := &http.Server{
    Addr:         ":8080",
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  60 * time.Second,
}

3. Ограничение количества одновременных соединений

Можно ограничить общее число активных соединений с помощью семафора (реализованного на буферизованном канале), чтобы предотвратить исчерпание файловых дескрипторов.

// Ограничиваем до 100 одновременных обработок
var connLimiter = make(chan struct{}, 100)

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    connLimiter <- struct{}{}
    defer func() { <-connLimiter }()

    // ... обработка запроса
})

Внешние уровни защиты

Важно помнить, что защита на уровне приложения — это лишь один из рубежей. Для надежной защиты необходимы:

  • Reverse Proxy (Nginx, HAProxy): Могут выполнять rate limiting и кэширование до того, как запрос дойдет до Go-приложения.
  • Firewall (iptables, nftables): Блокировка трафика с подозрительных IP на уровне ОС.
  • Специализированные сервисы (Cloudflare, Akamai): Предоставляют глобальную сеть для фильтрации вредоносного трафика еще до того, как он достигнет вашей инфраструктуры. Это самый эффективный метод против масштабных DDoS-атак.

Ответ 18+ 🔞

Ну слушай, вот сидишь ты, пишешь своё приложение на Go, всё летает, красота. А потом — бац! — какой-то пидарас шерстяной с ботнетом из тостеров решает тебе жизнь устроить, и начинается этот пиздец под названием DDoS.

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

1. Rate Limiting, или «Не гони, мудак!»

Самая простая и действенная штука — ограничить, сколько запросов один клиент может слать за время. Представь, как к тебе в гости приходит один знакомый и начинает со скоростью пулемёта рассказывать анекдоты. Сначала смешно, а через минуту уже хочется вилкой ему в глаз. Вот лимитер — это ты, который говорит: «Бля, давай по одному в секунду, а то я с ума сойду».

В Go для этого есть отличный пакет golang.org/x/time/rate. Берёшь и настраиваешь, сколько «анекдотов в секунду» ты готов слушать.

import "golang.org/x/time/rate"

// Допустим, 10 запросов в секунду, с возможным «запасом» в 20
limiter := rate.NewLimiter(10, 20)

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if !limiter.Allow() {
        http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
        return
    }
    // ... а вот тут уже обрабатываем, если клиент не слишком распизделся
})

Важный момент, чувак: этот лимитер нужно привязывать к IP-адресу клиента, а не делать один на всех. Иначе один еблан заспамит, а нормальные пользователи будут страдать. Это как если бы в баре один алкаш выпил всё пиво, а остальным бы сказали: «Извините, лимит исчерпан».

2. Таймауты, или «Давай быстрее, я жду!»

Есть такая хитрая жопа атака — Slowloris. Злоумышленник открывает кучу соединений и начинает оооочень медленно шептать тебе на ухо свой запрос, по байту в минуту. Сервер ждёт-ждёт, а соединения все заняты, новые принять не может — и накрылся медным тазом.

Чтобы такого не было, нужно ставить жёсткие таймауты в самом http.Server. Сказать ему: «Слушай, если клиент за 5 секунд не прислал запрос — посылай его нахуй и освобождай сокет».

srv := &http.Server{
    Addr:         ":8080",
    ReadTimeout:  5 * time.Second,   // Время на прочтение всего запроса
    WriteTimeout: 10 * time.Second,  // Время на отправку ответа
    IdleTimeout:  60 * time.Second,  // Время, которое пустое соединение может висеть
}

Вот так. Поставил — и уже спокойнее спишь.

3. Ограничение одновременных соединений, или «Мест нет!»

Представь свою программу как маленький бар. У тебя есть, допустим, 100 стульев. Если приходит 101-й посетитель, он просто ждёт у входа, пока кто-то не уйдёт. Так и тут: можно ограничить общее число горутин, которые одновременно обрабатывают запросы. Реализуется это проще пареной репы — на буферизованном канале.

// Делаем «бар на 100 мест»
var connLimiter = make(chan struct{}, 100)

http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Пытаемся занять место
    connLimiter <- struct{}{}
    // Не забываем освободить, когда уходим!
    defer func() { <-connLimiter }()

    // ... а вот тут уже сама обработка запроса
})

Если все 100 мест заняты, 101-й запрос будет ждать в этой строчке connLimiter <- struct{}{}, пока не освободится слот. Это не даст твоему серверу сожрать всю память и CPU, когда на него навалятся.

Но это всё, блядь, цветочки!

Запомни главное: защита в самом приложении — это как поставить хороший замок на дверь квартиры. От вора-одиночки спасёт, но если приедет танк или орда мародёров — пиши пропало.

Поэтому для серьёзной защиты нужны внешние рубежи:

  • Reverse Proxy (Nginx, HAProxy): Это такой здоровенный вышибала перед твоим баром. Он может сам отсеивать спам-запросы, кешировать статику и вообще принимать первый удар на себя.
  • Фаервол (iptables): Это уже уровень операционки. Можно тупо дропать пакеты с IP-адресов, которые ведут себя как последние мудя.
  • Специализированные сервисы (Cloudflare и прочие): Это, блядь, уже целая армия. Они стоят между тобой и всем интернетом, фильтруют трафик через свои дата-центры по всему миру и пропускают к тебе только чистых, проверенных ребят. От большого DDoS — это часто единственное спасение.

Короче, суть в чём: в своём коде делай лимитеры и таймауты — это обязательно. Но не питай иллюзий, что этого хватит на всё. Самые жирные атаки отбиваются на уровне инфраструктуры, до твоего кода дело даже не должно дойти. Вот так вот, ебать.