Обнаружение эффекта Slashdot в nginx

10

Есть ли способ, с помощью которого Nginx может уведомить меня, если количество обращений от реферера превышает порог?

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

Удастся ли это сделать в Nginx? Возможно без Луа? (так как мой продукт не скомпилирован lua)

Quintin Par
источник
4
Что такое "Слэшдот" ??
ewwhite
Я сделал что-то подобное, чтобы обнаружить ddos ​​на ngix. Я добился этого путем разбора журнала доступа. Я выполнил задание cron для анализа журнала доступа и подсчета уникальных IP-соединений в час.
Шестнадцатое
8
Вы хотите сказать, что хотите, чтобы nginx мог определить, были ли вы куплены Dice?
MDMarra
1
@Hex Это (и, возможно, несколько фрагментов из вашего сценария) послужит отличным ответом на этот вопрос :)
voretaq7
3
Вероятно, не нужно больше беспокоиться о том, чтобы получить Slashdotted. Ваш веб-сервер должен иметь возможность обрабатывать дополнительные 4 соединения в час. Возможно, стоит побеспокоиться о том, чтобы перекрасить, хотя ...
HopelessN00b

Ответы:

3

Наиболее эффективное решение может быть , чтобы написать демон , который будет и следить за поле.tail -faccess.log$http_referer

Однако быстрым и грязным решением было бы добавить дополнительный access_logфайл, чтобы регистрировать только $http_refererпеременную с пользовательским параметром log_format, и автоматически вращать журнал каждые X минут.

  • Это может быть выполнено с помощью стандартных сценариев logrotate, которым может потребоваться постепенный перезапуск nginx для повторного открытия файлов (например, стандартная процедура, посмотрите на / a / 15183322 в SO для простого времени - сценарий)…

  • Или, используя переменные внутри access_log, возможно, извлекая мелкую спецификацию $time_iso8601с помощью директивы mapили if(в зависимости от того, куда вы хотите поместить свою access_log).

Таким образом, с учетом вышесказанного у вас может быть 6 файлов журнала, каждый из которых охватывает период в 10 минут http_referer.Txx{0,1,2,3,4,5}x.log, например, получая первую цифру минуты, чтобы различать каждый файл.

Теперь все, что вам нужно сделать, это иметь простой сценарий оболочки, который может запускаться каждые 10 минут, catвсе вышеперечисленные файлы вместе, передавать его по sortконвейеру uniq -c, направлять в sort -rn, в head -16, и у вас есть список из 16 наиболее распространенных Refererвариантов. - свободно решать, если какие-либо комбинации чисел и полей превышает ваши критерии, и выполнить уведомление.

Впоследствии, после одного успешного уведомления, вы можете удалить все эти 6 файлов и, при последующих запусках, не выдавать никаких уведомлений, ЕСЛИ НЕ присутствуют все шесть файлов (и / или определенный другой номер, который вы считаете нужным).

CNST
источник
Это выглядит супер полезно. Я мог бы просить слишком много, но, как и в предыдущем ответе, вы не могли бы помочь со сценарием?
Quintin Par
@QuintinPar Это звучит внеклассно! ;-) Если хотите, меня можно нанять и проконсультировать; моя электронная почта cnst++@FreeBSD.org, также на Constantine.SU
cnst
Полностью понимаю. Большое спасибо за всю помощь до сих пор. Надеюсь, я когда-нибудь смогу себе позволить :-)
Quintin Par
1
@ QuintinPar, пожалуйста! Не беспокойтесь, это должен быть довольно простой скрипт с вышеприведенной спецификацией; просто вопрос тестирования, настройки и упаковки. :)
CNST
1
Вы супер герой!
Quintin Par
13

Я думаю, что это было бы гораздо лучше сделать с logtail и grep. Даже если это возможно сделать с помощью lua inline, вы не хотите, чтобы эти накладные расходы использовались при каждом запросе, и особенно вы не хотите, чтобы это было, когда вы получили Slashdotted.

Вот 5-секундная версия. Вставьте это в сценарий и поместите немного более читабельного текста вокруг него, и вы - золотой.

5 * * * * logtail -f /var/log/nginx/access_log -o /tmp/nginx-logtail.offset | grep -c "http://[^ ]slashdot.org"

Конечно, это полностью игнорирует reddit.com, facebook.com и все миллионы других сайтов, которые могут послать вам много трафика. Не говоря уже о 100 разных сайтах, каждый из которых отправляет по 20 посетителей. Вероятно, у вас должен быть простой старый порог трафика, который приводит к отправке вам электронного письма, независимо от реферала.

Ladadadada
источник
1
Проблема в том, чтобы быть активным. Мне нужно знать с любого сайта. Другой вопрос, куда мне поставить порог? Вы имели в виду дополнительный анализ логов? Также я не нашел - o в fourmilab.ch/webtools/logtail
Quintin Par
Порог будет зависеть от того, сколько трафика могут обрабатывать ваши серверы. Только вы можете установить это. Если вам нужно более быстрое уведомление, запускайте его каждые пять минут, а не каждый час, и делите порог на 12. -o Опция предназначена для файла смещения, чтобы он знал, с чего начать чтение в следующий раз.
Ладададада
@Ladadadada, я не согласен с тем, что накладные расходы были бы значительными, посмотрите мое решение - serverfault.com/a/870537/110020 - Я считаю, что накладные расходы будут весьма минимальными, если это будет реализовано должным образом, особенно (1), если ваш бэкэнд очень медленный, тогда эти издержки будут незначительными, или, (2), если ваш бэкэнд уже довольно быстро работает и / или кэшируется должным образом, тогда у вас должны быть небольшие проблемы с обработкой трафика, во-первых, и небольшая дополнительная нагрузка, выигранная ' не делать вмятину. В целом, звучит так, что у этого вопроса есть два варианта использования: (1) просто информирование и (2) автоматическое масштабирование.
CNST
4

Директива nginx limit_req_zone может основывать свои зоны на любой переменной, включая $ http_referrer.

http {
    limit_req_zone  $http_referrer  zone=one:10m   rate=1r/s;

    ...

    server {

        ...

        location /search/ {
            limit_req   zone=one  burst=5;
        }

Вы также захотите сделать что-то, чтобы ограничить количество состояния, требуемого на веб-сервере, поскольку заголовки реферера могут быть довольно длинными и разными, и вы можете увидеть бесконечное разнообразие. Вы можете использовать функцию nginx split_clients, чтобы установить переменную для всех запросов, основанную на хэше заголовка реферера. В приведенном ниже примере используется только 10 баксов, но вы можете сделать это с 1000 так же легко. Так что, если вы получили косую черту, люди, чей реферер хэшировал в тот же сегмент, что и URL-адрес slashdot, тоже будут заблокированы, но вы можете ограничить это до 0,1% посетителей, используя 1000 сегментов в split_clients.

Это будет выглядеть примерно так (полностью непроверено, но правильно):

http {

split_clients $http_referrer $refhash {
               10%               x01;
               10%               x02;
               10%               x03;
               10%               x04;
               10%               x05;
               10%               x06;
               10%               x07;
               10%               x08;
               10%               x09;
               *                 x10;
               }

limit_req_zone  $refhash  zone=one:10m   rate=1r/s;

...

server {

    ...

    location /search/ {
        limit_req   zone=one  burst=5;
    }
rmalayter
источник
Это интересный подход; однако, я полагаю, что вопрос заключается в автоматическом оповещении, когда происходит эффект Slashdot; Похоже, ваше решение решает случайную блокировку примерно 10% пользователей. Более того, я считаю, что ваши аргументы в пользу использования split_clientsмогут быть неверно проинформированы - они limit_reqоснованы на «утечке памяти», что означает, что общее состояние никогда не должно превышать размер указанной зоны.
CNST
2

Да, конечно, это возможно в NGINX!

Что вы могли бы сделать, это реализовать следующий DFA :

  1. Реализуйте ограничение скорости, основываясь на $http_refererвозможном использовании некоторого регулярного выражения через a mapдля нормализации значений. Когда лимит превышен, возникает внутренняя страница с ошибкой, которую можно перехватить с помощью error_pageобработчика по соответствующему вопросу , перейдя в новое внутреннее местоположение в качестве внутреннего перенаправления (невидимого для клиента).

  2. В указанном выше месте для превышенных пределов вы выполняете запрос оповещения, позволяя внешней логике выполнять уведомление; этот запрос впоследствии кэшируется, гарантируя, что вы получите только 1 уникальный запрос за данное временное окно.

  3. Перехватите код состояния HTTP предыдущего запроса (вернув код состояния ≥ 300 и используя proxy_intercept_errors onили, альтернативно, используя не встроенный по умолчанию auth_requestили add_after_bodyсделать «бесплатный» подзапрос), и завершите исходный запрос, как если бы предыдущий шаг не был вовлечен. Обратите внимание, что для этого нужно включить рекурсивную error_pageобработку.

Вот мой PoC и MVP, также по адресу https://github.com/cnst/StackOverflow.cnst.nginx.conf/blob/master/sf.432636.detecting-slashdot-effect-in-nginx.conf :

limit_req_zone $http_referer zone=slash:10m rate=1r/m;  # XXX: how many req/minute?
server {
    listen 2636;
    location / {
        limit_req zone=slash nodelay;
        #limit_req_status 429;  #nginx 1.3.15
        #error_page 429 = @dot;
        error_page 503 = @dot;
        proxy_pass http://localhost:2635;
        # an outright `return 200` has a higher precedence over the limit
    }
    recursive_error_pages on;
    location @dot {
        proxy_pass http://127.0.0.1:2637/?ref=$http_referer;
        # if you don't have `resolver`, no URI modification is allowed:
        #proxy_pass http://localhost:2637;
        proxy_intercept_errors on;
        error_page 429 = @slash;
    }
    location @slash {
        # XXX: placeholder for your content:
        return 200 "$uri: we're too fast!\n";
    }
}
server {
    listen 2635;
    # XXX: placeholder for your content:
    return 200 "$uri: going steady\n";
}
proxy_cache_path /tmp/nginx/slashdotted inactive=1h
        max_size=64m keys_zone=slashdotted:10m;
server {
    # we need to flip the 200 status into the one >=300, so that
    # we can then catch it through proxy_intercept_errors above
    listen 2637;
    error_page 429 @/.;
    return 429;
    location @/. {
        proxy_cache slashdotted;
        proxy_cache_valid 200 60s;  # XXX: how often to get notifications?
        proxy_pass http://localhost:2638;
    }
}
server {
    # IRL this would be an actual script, or
    # a proxy_pass redirect to an HTTP to SMS or SMTP gateway
    listen 2638;
    return 200 authorities_alerted\n;
}

Обратите внимание, что это работает как ожидалось:

% sh -c 'rm /tmp/slashdotted.nginx/*; mkdir /tmp/slashdotted.nginx; nginx -s reload; for i in 1 2 3; do curl -H "Referer: test" localhost:2636; sleep 2; done; tail /var/log/nginx/access.log'
/: going steady
/: we're too fast!
/: we're too fast!

127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.1" 200 16 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:49 +0200] "GET / HTTP/1.0" 200 16 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 200 20 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:51 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"

127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET / HTTP/1.1" 200 19 "test" "curl/7.26.0"
127.0.0.1 - - [26/Aug/2017:02:05:53 +0200] "GET /?ref=test HTTP/1.0" 429 20 "test" "curl/7.26.0"
%

Вы можете видеть, что первый запрос приводит к одному обращению к интерфейсу и одному бэкенду, как и ожидалось (мне пришлось добавить фиктивный бэкенд в расположение, которое имеет limit_req, поскольку a return 200будет иметь приоритет над ограничениями, настоящий бэкэнд не нужен для остальной обработки).

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

Третий запрос все еще превышает лимит, но мы уже отправили предупреждение, поэтому новое уведомление не отправляется.

Выполнено! Не забудьте раскошелиться на GitHub!

CNST
источник
Могут ли два условия ограничения скорости работать вместе? Я использую это прямо сейчас: serverfault.com/a/869793/26763
Quintin Par
@QuintinPar :-) Я думаю, это будет зависеть от того, как вы его используете - очевидная проблема будет заключаться в том, чтобы различать в одном месте, какой предел вводит условие; но если этот - a limit_req, а другой - a limit_conn, то просто используйте limit_req_status 429вышеупомянутое (требуется очень новый nginx), и я думаю, что вы должны быть золотым; возможны и другие варианты (один из которых наверняка сработает - это связывание nginx с / set_real_ip_from, но, в зависимости от того, что именно вы хотите сделать, могут быть более эффективные варианты).
17
@ QuintinPar, если что-то не хватает в моем ответе, дайте мне знать. Кстати, обратите внимание, что как только предел достигнут, и ваш скрипт должен вызываться, пока такой скрипт не будет должным образом кэширован nginx, тогда ваш контент может быть отложен; Например, вы можете захотеть реализовать скрипт асинхронно с чем-то вроде golang, или посмотреть опции тайм-аута для апстримов; также, возможно, захочет использовать proxy_cache_lock on, и, возможно, добавить некоторую обработку ошибок для того, что делать в случае сбоя сценария (например, используя, error_pageа также proxy_intercept_errorsснова). Я верю, что мой POC - хорошее начало. :)
CNST
Спасибо за попытку этого. Еще одна важная проблема для меня заключается в том, что я использую limit_req и limit_conn уже на уровне http, и это относится ко всем веб-сайтам, которые у меня есть. Я не могу переопределить это, это. Таким образом, это решение использует функциональность, предназначенную для чего-то другого. Есть ли другой подход к этому решению?
Quintin Par,
@QuintinPar Как насчет наличия вложенных экземпляров nginx, где каждый из них будет использовать один набор limit_req/ limit_conn? Например, просто поместите вышеуказанный конфиг перед вашим текущим интерфейсным сервером. Вы могли бы использовать set_real_ip_fromв восходящем nginx, чтобы гарантировать, что IP-адреса правильно учтены. Иначе, если он все еще не подходит, я думаю, что вы должны четко сформулировать свои точные ограничения и спецификацию - о каких уровнях трафика мы говорим? Как часто нужно запускать статистику (1 мин / 5 мин / 1 ч)? Что не так со старым logtailрешением?
CNST