Виявлення ефекту Slashdot в nginx


10

Чи є спосіб я змусити Nginx повідомити мене, якщо звернення від реферала перевищують поріг?

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

Чи вдасться це зробити в Nginx? Можливо, без луа? (оскільки мій продюсер не зібраний у луа)


4
Що таке "Slashdot" ??
ewwhite

Я щось подібне зробив, щоб виявити ddos ​​на ngix. Я досяг цього, аналізуючи журнал доступу. Я зробив cron завдання, щоб проаналізувати журнал доступу та підрахувати унікальні IP-з'єднання на годину.
Hex

8
Ви маєте на увазі, що хочете, щоб nginx міг виявити, чи купував вас Dice?
MDMarra

1
@Hex Це (і, можливо, кілька фрагментів вашого сценарію) відмінно відповість на це питання :)
voretaq7

3
Напевно, більше не потрібно турбуватися про те, щоб отримати Slashdotted. Ваш веб-сервер повинен мати можливість обробляти додаткові 4 з'єднання на годину. Можливо, хочете потурбуватися про те, щоб отримати Redditted ...
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 файлів і, в подальшому виконанні, не видавати жодного повідомлення, БЕЗКОШТОВНО, щоб усі шість файлів були присутніми (та / або певним іншим номером, як вважаєте за потрібне).


Це виглядає супер корисно. Я можу просити занадто багато, але, як попередня відповідь, ви б не могли допомогти зі сценарієм?
Квінтін Пар

@QuintinPar Це звучить позаурочний план! ;-) Якщо ви хочете, я доступний для прокату та консультацій; мій електронний лист cnst++@FreeBSD.org, також на Constantine.SU
cnst

Повністю розумію. Дякую за всю допомогу до цих пір. Сподіваюся, я можу собі дозволити собі якийсь день :-)
Квінтін,

1
@QuintinPar ласкаво просимо! Не хвилюйтесь, це повинен бути досить простий сценарій з вищевказаною специфікацією; лише питання тестування, налаштування та упаковки. :)
cnst

1
Ви супер герой!
Квінтін,

13

Я думаю, що це було б набагато краще зробити з локтя і греп. Навіть якщо це можливо зробити з 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 відвідувачів на кожен. Ви, ймовірно, повинні просто мати звичайний старий поріг трафіку, який призводить до того, що вам буде надіслано електронний лист незалежно від рефератора.


1
Проблема - бути ініціативною. Мені потрібно знати з будь-якого сайту. Ще одне питання - куди мені поставити поріг? Ви мали на увазі додатковий аналіз журналу? Також я не знайшов –о в fourmilab.ch/webtools/logtail
Квінтін,

Поріг буде залежати від того, скільки трафіку можуть обробляти ваш сервер. Тільки ви можете це встановити. Якщо ви хочете швидшого повідомлення, запустіть його кожні п’ять хвилин замість кожної години та розділіть поріг на 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 так само легко. Тож, якщо ви отримали slashdotted, люди, у яких референт трапився хеш-пам'ять у те саме відро, що і 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;
    }

Це цікавий підхід; однак я вважаю, що питання полягає в автоматичному оповіщенні, коли відбувається ефект 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, тому return 200що перевага матиме обмеження, справжній бекенд не потрібен для решти обробки.

Другий запит знаходиться вище межі, тому ми надсилаємо сповіщення (отримання 200) та кешуємо його, повертаючи 429(це необхідно через вищезазначене обмеження, що запити нижче 300 не можуть бути спіймані), яке згодом перехоплюється передньою частиною , який зараз вільний робити все, що хоче.

Третій запит все ще перевищує ліміт, але ми вже надіслали попередження, тож нове попередження не надсилається.

Готово! Не забудьте розщедритися на GitHub!


Чи можуть дві умови обмеження швидкості працювати разом? Я зараз користуюся цим: serverfault.com/a/869793/26763
Quintin,

@QuintinPar :-) Я думаю, це буде залежати від того, як ти його використовуєш - очевидною проблемою було б розрізнити в одному місці, за якою межею введена умова; але якщо цей - a limit_req, а інший - a limit_conn, тоді просто використовуйте limit_req_status 429вищезазначене (вимагає зовсім нового nginx), і я думаю, ви повинні бути золотистими; Можливо, існують й інші варіанти (один з яких точно працювати - це прив’язання nginx w / set_real_ip_from, але, залежно від того, що саме ви хочете зробити, можуть бути більш ефективні варіанти).
cnst

@QuintinPar якщо у моїй відповіді щось не вистачає, дайте мені знати. BTW, зауважте, що як тільки буде досягнуто обмеження, і ваш скрипт повинен бути викликаний, поки такий скрипт не буде кешований належним чином nginx, то ваш вміст може затриматись; наприклад, ви можете захотіти реалізувати скрипт асинхронно з чимось на зразок golang, або переглянути параметри очікування для вихідних потоків; також, можливо, хочеться використовувати proxy_cache_lock onі, можливо, додати деяку обробку помилок для того, що робити, якщо сценарій не працює (наприклад, використовуючи error_page, а також proxy_intercept_errorsзнову). Я вірю, що мій POC є гарним початком. :)
cnst

Дякуємо за спробу цього. Одним з головних проблем для мене все ще є те, що я використовую limit_req та limit_conn вже на рівні http, і це стосується всіх веб-сайтів, які у мене є. Я не можу це перемогти. Таким рішенням є використання функціоналу, призначеного для чогось іншого. Ще якийсь підхід до цього рішення?
Квінтін Пар

@QuintinPar Що з вбудованими екземплярами nginx, де кожен буде використовувати один набір limit_req/ limit_conn? Наприклад, просто поставте вищезгадану конфігурацію перед вашим поточним сервером передового доступу. Ви можете використовувати set_real_ip_fromв nginx вище за течією, щоб переконатися, що IP-адреси обліковуються правильно за лінією. В іншому випадку, якщо це все ще не підходить, я думаю, що ви повинні чіткіше чітко сформулювати свої точні обмеження та характеристики - про які рівні трафіку ми говоримо? Як часто потрібно запускати статистику (1 хв / 5 хв / 1 год)? Що не так із старим logtailрішенням?
cnst
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.