nginx에서 Slashdot 효과 감지


10

리퍼러의 적중이 임계 값을 초과하면 Nginx가 알려주도록하는 방법이 있습니까?

예를 들어 내 웹 사이트가 Slashdot에 등장하고 갑자기 1 시간에 2K 조회가 발생하면 1K 조회가 1 시간을 초과하면 알림을 받고 싶습니다.

Nginx에서 이것을 할 수 있습니까? 루아가 없을까요? (내 제품은 루아 컴파일되지 않았으므로)


4
"Slashdot"는 무엇입니까?
ewwhite

ngix에서 ddos를 감지하기 위해 이와 같은 작업을 수행했습니다. 액세스 로그를 구문 분석하여 달성했습니다. Cron 작업을 수행하여 액세스 로그를 구문 분석하고 시간당 고유 IP 연결을 계산했습니다.
Hex

8
당신은 nginx가 당신이 주사위로 구입했는지 감지 할 수 있기를 원합니까?
MDMarra

1
@ Hex That (그리고 아마도 당신의 스크립트에서 발췌 한 몇개의 스 니펫)은이 질문에 대한 훌륭한 답변을 만들 것입니다 :)
voretaq7

3
아마도 더 이상 슬래시 도트에 대해 걱정할 필요가 없습니다. 웹 서버는 시간당 4 개의 추가 연결을 처리 할 수 ​​있어야합니다. 그래도 Redditted를 얻는 것에 대해 걱정하고 싶을 수도 있습니다 ...
HopelessN00b

답변:


3

가장 효율적인 솔루션은 것 데몬 쓸 수 있습니다 의 트랙을 유지하고, 필드.tail -faccess.log$http_referer

그러나 빠르고 더러운 해결책은 추가 access_log파일 을 추가 $http_referer하고 custom으로 변수 만 log_format기록하고 X 분마다 자동으로 로그를 회전하는 것입니다.

  • 이는 표준 logrotate 스크립트를 사용하여 수행 할 수 있습니다. 파일을 다시 열려면 nginx를 정상적으로 다시 시작해야 할 수 있습니다 (예 : 표준 절차 는 간단한 시간 동안 SO 에서 / a / 15183322를 살펴보십시오) 기반 스크립트)…

  • 또는 내의 변수를 사용 하여 또는 지시문 access_log$time_iso8601사용 하여 분 사양을 가져올 수도 있습니다 (을 넣을 위치에 따라 다름 ).mapifaccess_log

따라서 위와 같이 http_referer.Txx{0,1,2,3,4,5}x.log각 파일을 구별하기 위해 분의 첫 번째 숫자를 가져 와서 각각 10 분의 기간을 포함하는 6 개의 로그 파일이있을 수 있습니다 .

이제 10 분마다 실행되는 간단한 셸 스크립트 cat, 위의 모든 파일을 함께 파이프 sort, 파이프, 파이프, to uniq -c, to sort -rn, to head -16및 가장 일반적인 16 가지 Referer변형 목록이 있습니다. — 숫자와 필드의 조합이 기준을 초과하는지 여부를 자유롭게 결정하고 알림을 수행합니다.

그 후, 하나의 성공적인 알림 후에는이 6 개의 파일을 모두 제거 할 수 있으며 이후 실행에서 6 개의 파일이 모두 존재하는 경우 (및 / 또는 적합한 다른 번호)가 없으면 알림을 발행 할 수 없습니다.


이것은 매우 유용하게 보입니다. 너무 많이 요구할 수도 있지만 이전 답변과 마찬가지로 스크립트를 도와 주시겠습니까?
Quintin Par

@QuintinPar 여분의 커리큘럼이 들립니다! ;-) 원하는 경우, 고용 및 컨설팅이 가능합니다. 제 이메일은 cnst++@FreeBSD.org이고 Constantine.SU
cnst

완전히 이해하십시오. 지금까지 모든 도움에 감사드립니다. 내가 당신에게 언젠가 여유가 있기를 바랍니다 :-)
Quintin Par

1
@QuintinPar 천만에요! 걱정하지 마십시오. 위의 스펙을 가진 매우 간단한 스크립트 여야합니다. 기본적으로 테스트, 구성 및 패키징 문제입니다. :)
cnst September

1
당신은 슈퍼 영웅입니다!
Quintin Par

13

나는 이것이 logtail과 grep으로 훨씬 더 좋을 것이라고 생각합니다. 루아 인라인으로 할 수 있더라도 모든 요청에 ​​대해 오버 헤드를 원하지 않으며 특히 슬래시 도트 (Slashdotted)되었을 때 원하지 않습니다.

5 초 버전입니다. 스크립트에 넣고 더 읽기 쉬운 텍스트를 넣으면 황금색입니다.

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

물론 reddit.com과 facebook.com 및 많은 트래픽을 보낼 수있는 수백만 개의 다른 사이트를 완전히 무시합니다. 각기 20 명의 방문자를 보내는 100 개의 서로 다른 사이트는 말할 것도 없습니다. 리퍼러에 관계없이 전자 메일이 전송되도록하는 오래된 오래된 트래픽 임계 값이 있어야합니다.


1
문제는 능동적입니다. 모든 사이트에서 알아야합니다. 또 다른 질문은 임계 값을 어디에 두어야합니까? 추가 로그 파싱을 의미 했습니까? 또한 나는 fourmilab.ch/webtools/logtail
Quintin Par

임계 값은 서버에서 처리 할 수있는 트래픽 양에 따라 다릅니다. 당신 만이 그것을 설정할 수 있습니다. 더 빠른 알림을 원하면 매시간 대신 5 분마다 실행하고 임계 값을 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;
        }

리퍼러 헤더가 상당히 길고 다양 할 수 있으며 무한 바리에이션이 나타날 수 있으므로 웹 서버에 필요한 상태의 양을 제한하기 위해 무언가를 수행하려고 할 것입니다. nginx split_clients 기능을 사용 하여 리퍼러 헤더의 해시를 기반으로하는 모든 요청에 ​​대한 변수를 설정할 수 있습니다 . 아래 예제는 10 버크만 사용하지만 1000으로 쉽게 수행 할 수 있습니다. 따라서 슬래시 도트 (slashdotted)가 발생하면 참조자가 슬래시 도트 URL과 동일한 버킷으로 해시 한 사람들도 차단되지만 split_clients에서 1000 개의 버킷을 사용하여 방문자의 0.1 %로 제한 할 수 있습니다.

다음과 같이 보일 것입니다 (완전히 테스트되지 않았지만 방향이 올바른 것입니다).

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통해 map값을 정규화 하기 위해 a 를 통해 일부 정규 표현식을 사용하여 속도 제한을 구현 하십시오. 한도를 초과하면 내부 오류 페이지가 발생 하여 관련 질문에 따라error_page 핸들러를 통해 내부 리디렉션 (클라이언트에게 보이지 않음)으로 새 내부 위치로 이동할 수 있습니다.

  2. 한계를 초과 한 위의 위치에서 경고 요청을 수행하여 외부 논리가 알림을 수행하게합니다. 이 요청은 이후에 캐시되므로 지정된 시간 창마다 하나의 고유 한 요청 만받을 수 있습니다.

  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실제 백엔드는 필요하지 않습니다) 나머지 취급에 대해서는).

두 번째 요청이 한도를 초과하므로 경고를 보내고 (getting 200)이를 캐시하여 반환합니다 429(이는 300 이하의 요청을 잡을 수없는 위에서 언급 한 제한으로 인해 필요함). , 원하는대로 무엇이든 자유롭게 할 수 있습니다.

세 번째 요청은 여전히 ​​한도를 초과하지만 이미 경고를 보냈으므로 새 경고가 전송되지 않습니다.

끝난! GitHub에서 포크하는 것을 잊지 마십시오!


두 개의 속도 제한 조건이 함께 작동 할 수 있습니까? 나는 이것을 지금 사용하고 있습니다 : serverfault.com/a/869793/26763
Quintin Par

@QuintinPar :-) 나는 그것이 당신이 그것을 어떻게 사용 하느냐에 달려 있다고 생각합니다. 명백한 문제는 조건을 도입 한 한 위치를 구별하는 것입니다; 그러나이 하나가 limit_req이고 다른 하나가 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

시도해 주셔서 감사합니다. 나에게 가장 큰 문제는 여전히 http 수준에서 limit_req 및 limit_conn을 사용하고 있으며 내가 가진 모든 웹 사이트에 적용된다는 것입니다. 나는 이것을 무시할 수 없다. 따라서이 솔루션은 다른 용도의 기능을 사용하고 있습니다. 이 솔루션에 대한 다른 접근 방법이 있습니까?
Quintin Par

@QuintinPar 중첩 된 nginx 인스턴스를 갖는 것은 어떻습니까? 각 인스턴스는 단일 세트의 limit_req/를 사용 limit_conn합니까? 예를 들어, 위의 구성을 현재 프런트 엔드 서버 앞에 두십시오. 당신이 사용할 수있는 set_real_ip_fromIP를 올바르게 선 아래로 회계 처리하기 위해 상류의 nginx에. 그렇지 않은 경우에도 여전히 맞지 않으면 정확한 제약 조건과 사양을보다 생생하게 표현해야한다고 생각합니다. 어떤 트래픽 수준에 대해 이야기하고 있습니까? 통계를 얼마나 자주 실행해야합니까 (1 분 / 5 분 / 1 시간)? 이전 logtail솔루션 의 문제점은 무엇입니까 ?
cnst
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.