การตรวจจับ Slashdot effect ใน nginx


10

มีวิธีที่ฉันสามารถกำหนดให้ Nginx แจ้งเตือนฉันได้หรือไม่หากการเข้าชมจากผู้อ้างอิงเกินเกณฑ์หรือไม่

เช่นถ้าเว็บไซต์ของฉันให้ความสำคัญกับ Slashdot และในทันใดฉันก็มีเพลงฮิต 2K มาในอีกหนึ่งชั่วโมงฉันอยากได้รับการแจ้งเตือนเมื่อไปเกิน 1K ต่อชั่วโมง

เป็นไปได้ไหมที่จะทำสิ่งนี้ใน Nginx? อาจเป็นไปได้โดยไม่ต้อง lua? (เนื่องจากผลิตภัณฑ์ของฉันไม่ได้รวบรวม lua)


4
"Slashdot" คืออะไร?
ewwhite

ฉันทำสิ่งนี้เพื่อตรวจจับ ddos ​​บน ngix ฉันทำได้โดยแยกวิเคราะห์บันทึกการเข้าถึง ฉันทำงาน cron เพื่อแยกวิเคราะห์บันทึกการเข้าถึงและนับการเชื่อมต่อ ip ที่ไม่ซ้ำกันต่อชั่วโมง
Hex

8
คุณหมายถึงคุณต้องการให้ nginx สามารถตรวจสอบได้ว่าลูกเต๋าของคุณถูกซื้อหรือไม่
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และคุณมีรายการของRefererรูปแบบที่พบมากที่สุด 16 แบบ - ฟรีในการตัดสินใจว่าการรวมกันของตัวเลขและเขตข้อมูลใด ๆ เกินกว่าเกณฑ์ของคุณและทำการแจ้งเตือน

ต่อจากนั้นหลังจากการแจ้งเตือนที่ประสบความสำเร็จเพียงครั้งเดียวคุณสามารถลบไฟล์ทั้ง 6 ไฟล์เหล่านี้และในการรันครั้งต่อไปไม่ออกการแจ้งเตือนใด ๆ เว้นแต่ไฟล์ทั้งหกนี้จะปรากฏ (และ / หรือหมายเลขอื่นตามที่คุณเห็นสมควร)


มันดูมีประโยชน์มาก ฉันอาจจะขอมากเกินไป แต่ชอบคำตอบก่อนหน้านี้คุณจะช่วยด้วยสคริปต์หรือไม่
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 คน คุณอาจจะเพียงแค่มีธรรมดาเก่าจราจรเกณฑ์ที่เป็นสาเหตุของอีเมลไปที่ถูกส่งถึงคุณโดยไม่คำนึงถึงอ้างอิง


1
ปัญหาคือการเป็นเชิงรุก ฉันต้องการรู้จากเว็บไซต์ใด ๆ คำถามอื่นอยู่ที่ฉันจะใส่เกณฑ์? คุณหมายถึงการแยกวิเคราะห์บันทึกเพิ่มเติมหรือไม่ นอกจากนี้ฉันไม่พบ –o ในfourmilab.ch/webtools/logtail
Quintin Par

เกณฑ์จะขึ้นอยู่กับปริมาณการรับส่งข้อมูลเซิร์ฟเวอร์ของคุณสามารถจัดการได้ มีเพียงคุณเท่านั้นที่สามารถตั้งค่าได้ หากคุณต้องการแจ้งเตือนเร็วรันทุกห้านาทีแทนทุกชั่วโมงและแบ่งเกณฑ์โดย 12 -o ตัวเลือกสำหรับไฟล์ชดเชยจึงรู้ว่าจะเริ่มต้นการอ่านในครั้งต่อไป
Ladadadada

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

คุณจะต้องทำบางสิ่งบางอย่างเพื่อ จำกัด จำนวนสถานะที่ต้องการบนเว็บเซิร์ฟเวอร์ด้วยเนื่องจากส่วนหัวของผู้อ้างอิงอาจมีความยาวและหลากหลายและคุณอาจเห็นตัวแปร infinte คุณสามารถใช้คุณสมบัติ nginx split_clientsเพื่อตั้งค่าตัวแปรสำหรับคำขอทั้งหมดที่ยึดตามแฮชของส่วนหัวผู้อ้างอิง ตัวอย่างด้านล่างใช้เพียง 10 buckes แต่คุณสามารถทำได้ด้วย 1,000 เพียงอย่างง่ายดาย ดังนั้นหากคุณได้รับ slashdotted ผู้ที่มีผู้อ้างอิงเกิดการแฮชเข้าไปในที่เก็บข้อมูลเดียวกันกับ URL ของ slashdot ก็จะถูกบล็อกด้วยเช่นกัน แต่คุณสามารถ จำกัด ได้เพียง 0.1% ของผู้เข้าชมโดยใช้ถัง 1000 ถัง

มันจะมีลักษณะเช่นนี้ (ยังไม่ทดลองทั้งหมด แต่ถูกต้องตามทิศทาง):

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 effect โซลูชันของคุณดูเหมือนจะแก้ไขได้โดยการสุ่มบล็อกผู้ใช้ 10% ยิ่งกว่านั้นฉันเชื่อว่าเหตุผลของคุณสำหรับการใช้งานsplit_clientsอาจจะผิดไปจากlimit_reqนี้- ขึ้นอยู่กับ "ถังที่รั่ว" ซึ่งหมายความว่าสถานะโดยรวมไม่ควรเกินขนาดของโซนที่ระบุ
cnst

2

ใช่แน่นอนมันเป็นไปได้ใน NGINX!

สิ่งที่คุณสามารถทำได้คือใช้DFAต่อไปนี้:

  1. ใช้การ จำกัด อัตราการใช้$http_refererอาจขึ้นกับการใช้ regex ผ่าน 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"
%

คุณสามารถเห็นได้ว่าคำขอแรกส่งผลให้ front-end หนึ่งและแบ็กเอนด์ตีหนึ่งตามที่คาดไว้ (ฉันต้องเพิ่มแบ็กเอนด์ดัมมี่ไปยังตำแหน่งที่มีlimit_reqเนื่องจากreturn 200จะมีความสำคัญเหนือกว่าขีด จำกัด แบ็กเอนด์จริงไม่จำเป็น สำหรับส่วนที่เหลือของการจัดการ)

คำขอที่สองอยู่เหนือขีด จำกัด ดังนั้นเราจึงส่งการแจ้งเตือน (รับ200) และแคชส่งคืน429(จำเป็นเนื่องจากข้อ จำกัด ที่กล่าวมาข้างต้นซึ่งไม่สามารถจับการร้องขอที่ต่ำกว่า 300) ซึ่งจะถูกดักจับโดย front-end ซึ่งฟรีตอนนี้สามารถทำอะไรก็ได้ที่ต้องการ

คำขอที่สามยังคงเกินขีด จำกัด แต่เราได้ส่งการแจ้งเตือนไปแล้วดังนั้นจึงไม่มีการส่งการแจ้งเตือนใหม่

ทำ! อย่าลืมแยกไว้บน GitHub!


เงื่อนไขการ จำกัด อัตราสองอัตราสามารถทำงานร่วมกันได้หรือไม่ ฉันกำลังใช้งานอยู่ตอนนี้: serverfault.com/a/869793/26763
Quintin Par

@QuintinPar :-) ฉันคิดว่ามันจะขึ้นอยู่กับวิธีการใช้งานของคุณ - ปัญหาที่เห็นได้ชัดคือการแยกแยะความแตกต่างในสถานที่เดียวที่มีข้อ จำกัด ในการนำเสนอเงื่อนไข แต่ถ้าอันนี้คือ a limit_req, และอีกอันคือ a limit_conn, ให้ใช้limit_req_status 429ข้างต้น (ต้องการ nginx ใหม่มาก), และฉันคิดว่าคุณควรจะเป็นสีทอง; อาจมีตัวเลือกอื่น ๆ (ตัวเลือกหนึ่งที่ใช้งานได้อย่างแน่นอนคือการโยง nginx w / set_real_ip_fromแต่ขึ้นอยู่กับสิ่งที่คุณต้องการจะมีตัวเลือกที่มีประสิทธิภาพมากขึ้น)
cnst

@QuintinPar หากมีสิ่งใดที่ขาดหายไปจากคำตอบของฉันแจ้งให้เราทราบ BTW โปรดทราบว่าเมื่อถึงขีด จำกัด และสคริปต์ของคุณจะถูกเรียกใช้จนกว่าสคริปต์ดังกล่าวจะถูกแคชอย่างเหมาะสมโดย nginx ดังนั้นเนื้อหาของคุณอาจล่าช้า เช่นคุณอาจต้องการใช้สคริปต์แบบอะซิงโครนัสกับสิ่งที่ชอบgolangหรือดูตัวเลือกการหมดเวลาสำหรับ upstreams; ยังอาจต้องการใช้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ใน upstream nginx เพื่อให้แน่ใจว่า IP นั้นถูกต้องตามบรรทัด มิฉะนั้นถ้ามันยังไม่พอดีฉันคิดว่าคุณต้องพูดถึงข้อ จำกัด ที่แน่นอนของคุณและสเป็คที่ชัดเจนมากขึ้น - เรากำลังพูดถึงระดับการจราจรอะไร Stat ต้องรันบ่อยแค่ไหน (1 นาที / 5 นาที / 1 ชั่วโมง) เกิดอะไรขึ้นกับlogtailโซลูชันเก่า
cnst
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.