Rilevamento dell'effetto Slashdot in nginx


10

C'è un modo in cui posso fare in modo che Nginx mi informi se gli hit di un referrer superano una soglia?

ad es. se il mio sito web è pubblicato su Slashdot e all'improvviso ho 2K hit in arrivo in un'ora, voglio essere avvisato quando supera 1K hit all'ora.

Sarà possibile farlo in Nginx? Forse senza lua? (dal momento che il mio prod non è compilato lua)


4
Che cos'è "Slashdot" ??
ewwhite,

Ho fatto qualcosa del genere per rilevare ddos ​​su ngix. L'ho raggiunto analizzando il registro di accesso. Ho fatto un lavoro cron per analizzare il registro degli accessi e contare connessioni IP univoche all'ora.
Hex,

8
Vuoi dire che nginx sarà in grado di rilevare se sei stato acquistato da Dice?
MDMarra,

1
@Hex That (e forse alcuni frammenti della tua sceneggiatura) farebbe un'ottima risposta a questa domanda :)
voretaq7,

3
Probabilmente non dovrai più preoccuparti di ottenere Slashdotted. Il tuo server web dovrebbe essere in grado di gestire altre 4 connessioni all'ora. Potrebbe voler preoccuparmi di ottenere Redditted, però ...
HopelessN00b,

Risposte:


3

La soluzione più efficiente potrebbe essere quella di scrivere un demone che sarebbe tail -fil access.loge tenere traccia del $http_referercampo.

Tuttavia, una soluzione rapida e sporca sarebbe quella di aggiungere un access_logfile aggiuntivo , registrare solo la $http_referervariabile con un personalizzato log_formate ruotare automaticamente il registro ogni X minuti.

  • Ciò può essere realizzato con l'aiuto degli script logrotate standard, che potrebbero richiedere riavvii graziosi di nginx per riaprire i file (ad esempio, la procedura standard, dare un'occhiata a / a / 15183322 su SO per un semplice periodo di tempo- script basato) ...

  • Oppure, usando le variabili all'interno access_log, possibilmente estraendo la specifica minuto $time_iso8601con l'aiuto della direttiva mapo if(a seconda di dove vuoi mettere la tua access_log).

Pertanto, con quanto sopra, potresti avere 6 file di registro, ognuno dei quali copre un periodo di 10 minuti http_referer.Txx{0,1,2,3,4,5}x.log, ad esempio ottenendo la prima cifra del minuto per differenziare ciascun file.

Ora, tutto quello che devi fare è avere un semplice script di shell che possa essere eseguito ogni 10 minuti, cattutti i file sopra elencati insieme, pipe a sort, pipe a uniq -c, a sort -rn, a head -16e hai un elenco delle 16 Referervarianti più comuni - libero di decidere se una qualsiasi combinazione di numeri e campi supera i tuoi criteri ed eseguire una notifica.

Successivamente, dopo una singola notifica riuscita, è possibile rimuovere tutti questi 6 file e, nelle successive esecuzioni, non inviare alcuna notifica A MENO CHE non siano presenti tutti e sei i file (e / o un certo altro numero che ritieni opportuno).


Questo sembra super utile. Potrei chiedere troppo, ma come la risposta precedente, ti dispiacerebbe aiutare con una sceneggiatura?
Quintin Par

@QuintinPar Sembra un programma extra! ;-) Se vuoi, sono disponibile per il noleggio e la consulenza; la mia email è cnst++@FreeBSD.org, anche a Constantine.SU
cnst

Totalmente capito. Grazie mille per tutto l'aiuto fino ad ora. Spero di potermi permettere un giorno :-)
Quintin Par

1
@QuintinPar sei il benvenuto! Non preoccuparti, dovrebbe essere uno script abbastanza semplice con le specifiche di cui sopra; solo una questione di test, configurazione e packaging, in pratica. :)
cnst

1
Sei un supereroe!
Quintin Par

13

Penso che sarebbe molto meglio farlo con logtail e grep. Anche se è possibile avere a che fare con lua inline, non vuoi quel sovraccarico per ogni richiesta e soprattutto non lo vuoi quando sei stato slashdotted.

Ecco una versione di 5 secondi. Inseriscilo in una sceneggiatura e metti un po 'di testo più leggibile intorno a te e sei d'oro.

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

Ovviamente, ciò ignora completamente reddit.com e facebook.com e tutti i milioni di altri siti che potrebbero inviarti molto traffico. Per non parlare di 100 siti diversi che ti inviano 20 visitatori ciascuno. Probabilmente dovresti avere solo una vecchia soglia di traffico che causa l'invio di un'e-mail, indipendentemente dal referrer.


1
Il problema è essere proattivi. Devo sapere da qualsiasi sito. Un'altra domanda è dove devo mettere la soglia? Intendevi analisi di log aggiuntive? Inoltre non ho trovato –o in fourmilab.ch/webtools/logtail
Quintin Par

La soglia dipenderà dalla quantità di traffico che i tuoi server possono gestire. Solo tu puoi impostarlo. Se si desidera una notifica più rapida, eseguirla ogni cinque minuti anziché ogni ora e dividere la soglia per 12. L' -o opzione è per un file offset in modo che sappia da dove iniziare a leggere la prossima volta.
Ladadadada,

@Ladadadada, non sono d'accordo sul fatto che il sovraccarico sarebbe sostanziale, vedi la mia soluzione - serverfault.com/a/870537/110020 - Credo che il sovraccarico sarebbe abbastanza minimo se questo fosse implementato correttamente, in particolare, (1), se il tuo backend è molto lento, quindi questo sovraccarico sarebbe trascurabile, oppure, (2), se il tuo back-end è già abbastanza scorrevole e / o correttamente memorizzato nella cache, allora dovresti avere piccoli problemi con la gestione del traffico e un piccolo carico extra vinto ' fare un'ammaccatura. Nel complesso, sembra che questa domanda abbia due casi d'uso, (1), appena informato e, (2), ridimensionamento automatico.
primo

4

La direttiva nginx limit_req_zone può basare le sue zone su qualsiasi variabile, incluso $ http_referrer.

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

    ...

    server {

        ...

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

Tuttavia, ti consigliamo di fare qualcosa per limitare la quantità di stato richiesta sul server Web, poiché le intestazioni dei referrer possono essere piuttosto lunghe e varie e potresti vedere una varietà infinita. È possibile utilizzare la funzione nginx split_clients per impostare una variabile per tutte le richieste basata sull'hash dell'intestazione del referrer. L'esempio seguente utilizza solo 10 dollari, ma puoi farlo con 1000 altrettanto facilmente. Quindi, se vieni sbattuto, le persone il cui referrer è entrato nello stesso bucket dell'URL slashdot verrebbero bloccate, ma potresti limitarlo allo 0,1% dei visitatori usando 1000 bucket in split_clients.

Sarebbe simile a questo (totalmente non testato, ma direzionalmente corretto):

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;
    }

Questo è un approccio interessante; tuttavia, credo che la domanda riguardi un avviso automatico quando si verifica l'effetto Slashdot; la tua soluzione sembra risolversi bloccando casualmente circa il 10% degli utenti. Inoltre, credo che il tuo ragionamento sull'uso split_clientspossa essere male informato - limit_reqsi basa su un "secchio che perde", il che significa che lo stato generale non dovrebbe mai superare la dimensione della zona specificata.
primo

2

Sì, ovviamente è possibile in NGINX!

Quello che potresti fare è implementare il seguente DFA :

  1. Implementare la limitazione della velocità, in base $http_referer, eventualmente usando un po 'di regex attraverso a mapper normalizzare i valori. Quando viene superato il limite, viene sollevata una pagina di errore interna, che è possibile rilevare attraverso un error_pagegestore come da una domanda correlata , andando in una nuova posizione interna come reindirizzamento interno (non visibile al client).

  2. Nella posizione sopra per limiti superati, si esegue una richiesta di avviso, lasciando che la logica esterna esegua la notifica; questa richiesta viene successivamente memorizzata nella cache, assicurando che otterrai solo 1 richiesta unica per una determinata finestra temporale.

  3. Cattura il codice di stato HTTP della richiesta precedente (restituendo un codice di stato ≥ 300 e utilizzando proxy_intercept_errors ono, in alternativa, utilizza il valore predefinito non predefinito auth_requesto add_after_bodyper effettuare una richiesta secondaria "gratuita") e completa la richiesta originale come se il passo precedente non era coinvolto. Si noti che è necessario abilitare la error_pagegestione ricorsiva affinché funzioni.

Ecco il mio PoC e un MVP, anche su 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;
}

Si noti che funziona come previsto:

% 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"
%

Puoi vedere che la prima richiesta si traduce in un hit front-end e in un back-end, come previsto (ho dovuto aggiungere un backend fittizio nella posizione che ha limit_req, poiché return 200avrebbe la precedenza sui limiti, non è necessario un vero backend per il resto del trattamento).

La seconda richiesta è al di sopra del limite, quindi, inviamo l'avviso (ricevendo 200) e lo memorizziamo nella cache, restituendo 429(ciò è necessario a causa della suddetta limitazione che le richieste inferiori a 300 non possono essere catturate), che viene successivamente catturato dal front-end , che ora è libero di fare ciò che vuole.

La terza richiesta supera ancora il limite, ma abbiamo già inviato l'avviso, quindi non viene inviato alcun nuovo avviso.

Fatto! Non dimenticare di rovesciarlo su GitHub!


Due condizioni di limitazione della velocità possono funzionare insieme? Sto usando questo adesso: serverfault.com/a/869793/26763
Quintin Par

@QuintinPar :-) Penso che dipenderà da come lo usi - il problema ovvio sarebbe distinguere in una singola posizione di quale limite ha introdotto la condizione; ma se questo è un limit_req, e l'altro è un limit_conn, usa semplicemente quanto limit_req_status 429sopra (richiede nginx molto nuovo), e penso che dovresti essere d'oro; potrebbero esserci altre opzioni (una per lavorare sicuramente è concatenare nginx con / set_real_ip_from, ma, a seconda di cosa esattamente si vuole fare, potrebbero esserci scelte più efficienti).
17-17

@QuintinPar se manca qualcosa nella mia risposta, fammi sapere. A proposito, nota che una volta raggiunto il limite e il tuo script deve essere chiamato, fino a quando tale script non viene correttamente memorizzato nella cache da nginx, il tuo contenuto potrebbe essere ritardato; ad esempio, potresti voler implementare lo script in modo asincrono con qualcosa di simile golango esaminare le opzioni di timeout per gli upstream; Inoltre, potrebbe voler utilizzare proxy_cache_lock onpure, e forse aggiungere un po 'la gestione degli errori per che cosa fare se lo script non riesce (ad esempio, utilizzando error_pagecosì come proxy_intercept_errorsancora una volta). Confido che il mio POC sia un buon inizio. :)
cnst

Grazie per aver provato questo. Un grosso problema per me è ancora, sto usando limit_req e limit_conn già a livello http e si applica a tutti i siti Web che ho. Non posso ignorarlo. Quindi questa soluzione sta usando una funzionalità pensata per qualcos'altro. Qualcos'altro approccio a questa soluzione?
Quintin Par,

@QuintinPar Che dire di avere istanze nginx nidificate, in cui ognuna utilizzerà un singolo set di limit_req/ limit_conn? Ad esempio, metti la configurazione sopra davanti al tuo attuale server front-end. È possibile utilizzare set_real_ip_fromin nginx upstream per assicurarsi che gli IP siano contabilizzati correttamente lungo la linea. Altrimenti, se ancora non si adatta, penso che devi articolare i tuoi vincoli esatti e le specifiche in modo più vivido - di quali livelli di traffico stiamo parlando? Con quale frequenza deve essere eseguita la stat (1min / 5min / 1h)? Cosa c'è che non va nella vecchia logtailsoluzione?
primo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.