GoogleやBingの検索サイトなど、サイトの内容を取得するために大量に集中的にアクセスするものがたくさんあります。
それをbot(ボット)と言います。
中にはF5アタックじゃないか? 思うくらい迷惑なものもある。
今回は Webサーバー・nginxの設定で制限をかけます。limit_reqを使います。
ボットは検索サイトだけではありません。SEOのツールを提供しているサービス、広告・マーケティング会社など、サイトの内容を解析しようとボットだらけの世界です。
ある程度のサイトに成長すると、正規のアクセスよりもボットのほうがログに記録されるほど。
今のWebサイトはこんな状態なので、アクセス制限の設定は必須です。
悲しいかな、あまりにもアクセス数が少ないサイトはボットにも相手にされません。
ボットが大量に来ることがサイトの成長の証でもあるところが複雑。
limit_reqの条件分岐は簡単にはいかない
話を戻して、nginxのアクセス制限にはngx_http_limit_req_moduleを使います。
アクセス制限は、ユーザーエージェントなどクライアントから受信するデータを使って条件を絞ります。
しかし、ifやlimit_reqなどのディレクティブには使用制限があるのでかんたんに条件分岐ができません。
httpコンテキストではifが使えない
アクセス制限を行うには、まずhttpコンテキストでlimit_req_zoneを定義します。
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
}
ここでまず考えるのは、limit_req_zoneの定義を条件に合致したときだけにすること。たとえばこんな感じで。
http {
if ( $http_user_agent ~* (Googlebot|bingbot) ){
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
}
}
ただこれは文法エラーです。こんなメッセージが出ます。
nginx -t
nginx: [emerg] "if" directive is not allowed here in /etc/nginx/nginx.conf:46
nginx: configuration file /etc/nginx/nginx.conf test failed
if文はserver, locationコンテキストでしか使えないから。limit_req_zoneを条件分岐するにはある工夫が必要です。(後述)
(if文のことを『ifディレクティブ』という。)
nginxの設定で使う文法では、ifやlimit_reqなどの命令定型のことをディレクティブという。
また、http, server, locationなどの適用範囲をあらわす定型のことをコンテキストという。
limit_reqはifで使えない。
そこで、limit_req_zoneは条件をつけずに定義しておいて、じっさいに制限を行うlimit_reqで条件を付けちゃおうと考えます。
limit_req_zoneは、キャッシュのサイズとアクセス頻度、キャッシュ名に使うキーを定義しているだけでアクセス制限をしているわけではない。
じっさいの制限は、server, locationコンテキストで定義するlimit_reqで行う。
たとえばこんな感じ。
server {
listen 443 ssl http2;
server_name example.com;
limit_req zone=one burst=100 nodelay;
if ( $http_user_agent ~* (Googlebot|bingbot) ){
limit_req zone=one burst=5 nodelay;
}
}
ただ今度はこんな文法エラーが発生します。
nginx -t
nginx: [emerg] "limit_req" directive is not allowed here in /etc/nginx/conf.d/example.com.conf:8
nginx: configuration file /etc/nginx/nginx.conf test failed
limit_reqは if の中では使えません。
limit_reqの条件分岐には工夫が必要
それならどうすりゃいいんだ? になってしまうんですが、方法はあります。ポイントは2つ。
- if の代わりに map を使う
- limit_req_zoneの空のキーを使う
mapは、変数値に影響される変数をセットするもので、リテラル値を代入することもできます。
map $http_user_agent $is_bot {
default 0;
~* (Googlebot|bingbot) 1;
}
クライアントからの受信データ、ユーザーエージェントを見て、GoogleとBingのボットだったら1をそれ以外は0を$is_botに代入します。
これでさっき使えなかった if とまったく同じ条件ができました。
そしてもう一つは、limit_req_zoneはキーを空で指定すると、アクセス制限は無効になります。
nginxのドキュメントではサラッと触れてるだけのものですが、これが使える。
このポイントを踏まえてアクセス制限を条件で振り分けるとこうなります。
http {
# アクセス制御振り分けの作り直し
# if ( $http_user_agent ~* (Googlebot|bingbot) ){
# limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
# }
map $http_user_agent $limit_req_key {
default '';
~* (Googlebot|bingbot) $binary_remote_addr;
}
limit_req_zone $limit_req_key zone=one:10m rate=1r/s;
}
httpコンテキストで振り分けは終わってるので、serverコンテキストではlimit_reqを定義するだけでOK。
server {
listen 443 ssl http2;
server_name example.com;
limit_req zone=one burst=5 nodelay;
}
serverコンテキストでmapを使ってlimit_reqを分けれそうですが、残念ながらmapはhttpコンテキストでしか使えません。
アクセス制御条件分岐のサンプル
最後に応用編としてlimit_reqのアクセス制御のサンプルをご紹介します。
http {
map $http_user_agent $is_bot {
default 0;
~*(Googlebot|bingbot|PetalBot) 1;
}
map $is_bot $limit_bot {
default '';
1 $binary_remote_addr;
}
map $is_bot $limit_none_bot_tmp {
default '';
0 $binary_remote_addr$http_referer;
}
# --- 除外設定 ---
map $http_referer $limit_none_bot {
default $limit_none_bot_tmp;
~*example.com '';
}
limit_req_zone $limit_bot zone=bot:10m rate=5r/s;
limit_req_zone $limit_none_bot zone=nonebot:10m rate=10r/s;
}
botとbotじゃないアクセスの2つのキーを用意して、それぞれlimit_req_zoneを定義しました。
ボットの集中アクセスはWebサーバーが落ちそうなくらい負荷がかかることがあるので、制限値を厳しくしています。
通常のアクセスはF5アタックなどの悪質な集中アクセス用。サーバーが落ちず、かつサイト表示に影響がない値を設定。
DoS/DDoS攻撃
DoS攻撃(Denial Of Service Attack)
短時間に大量のアクセスを行ってサーバーに高負荷を与え、アプリケーションが正常に動かなくするもの。
WebサーバーのHTTP/HTTPSだけでなく、外からアクセスのリクエストを送信するプロトコル全般で行われる。
Webサーバーへの攻撃はF5アタックが有名。
(ブラウザでF5を押すと再表示が行われる。その機能を使って大量のアクセスを行う。)
サーバーごとパンクさせることもある。
DDoS攻撃(Distributed Denial Of Service Attack)
ネットワーク上にある別のコンピュータを経由して行うDoS攻撃。
攻撃者はまず無関係のコンピュータを乗っ取り、そこから攻撃するので特定されづらい。
また、複数のコンピュータから一気に畳み掛けることもできるので、DoS攻撃よりも破壊力がある。
Distributedは『分散』という意味。
これらの攻撃を防ぐには1秒間あたりのアクセス数を制限するなど行うが、正常な集中アクセスを処理しながらの調整がむずかしい。
また、通常アクセスでは、サイト内の画像やCSS, JavaScriptの要求があって、これをすべて通さないとサイト表示が壊れてしまいます。
$http_refererを見て、自サイトからのアクセスは制限を無効にしました。
キーにIPアドレスとリファラーを利用していることろがポイント。
server {
listen 443 ssl http2;
server_name example.com;
limit_req zone=bot burst=5 nodelay;
limit_req zone=nonebot burst=20 nodelay;
}
serverコンテキストのlimit_reqでも、ボットは厳し目に、通常アクセスは画像などの内部要求に耐えられるように制限をゆるめています。
limit_req_zoneとlimit_reqの定義の意味。
limit_req_zone の rate=10r/sは、『1秒間に10アクセスを許可する』という意味ではありません。
『1秒間に10アクセス相当のアクセスを許可する』という意味。
つまり、0.1秒に1回のアクセスを許可するということ。
1秒間に2アクセスしかなくても、その間隔が0.1秒未満であれば、2番目のアクセスははじかれます。
また、limit_req の burst は、タイヤが破裂する意味の『バースト』と同じ単語ですが、『破裂する。爆発する』から転じて、集中して何かが起きることの意味もあります。
ここでは、集中アクセス数の制限を定義します。
limit_reqでは通常、集中アクセスがリクエストされると、limit_req_zoneのゾーンで指定したキャッシュへリクエストを保存します。
(limit_req_zoneのzone名の後ろのコロン(:)以降は、キャッシュのMaxサイズ。)
そして、そのキャッシュからリクエストを取って、limit_req_zone の rate の間隔に制限されてる集中アクセスでもrate の間隔で遅延させて処理しようとします。
burst は、その遅延させてでも処理するアクセス数のことでもある。
(それ以上ははじかれる。)
burst が未指定だと遅延は発生せず、limit_req_zone の rate の制限に従って即座にはじきます。
また、limit_req の nodelay は遅延を発生させないという意味。
これが定義されると、burst のアクセス以下の集中アクセスであれば、rate に関係なく即座に処理します。
(burst を超えると残りはrateの制限に従う。)
burst がなければ遅延はないだの、burst は遅延させるのに nodelay は遅延させないだのややこしいですが、rate, burst, nodelay は複合的に条件がからんでアクセス制限します。
rate | 制限の一番強い条件。 遅延処理の間隔でも使われる。 |
burst | rateがNGのときだけ使われる。 遅延処理の実行アクセス数。 |
nodelay | burst指定時の即時処理指定。 burstがないと無効。 |
limit_req_zone rate | limit_req burst | limit_req nodelay | |
---|---|---|---|
✕ | ○ | - | rateで制限されてもburst分だけは遅延 させて処理する。 遅延間隔はrateに依存。 burstを超える分ははじかれる。 |
○ | ○ | - | rateの制限内なので遅延は起きない。 rateがOKなのでburstに意味はなく、全 アクセスを即座に処理する。 |
✕ | ○ | ○ | rateで制限されてもburst分だけは即座 に処理する。 burstを超えたものははじかれる。 |
○ | - | - | rateの制限内なので即座に処理する。 |
✕ | - | - | 遅延処理はしない。 rateに制限されたものは即座にはじかれ る。 (はじく条件はrateだけになる。) |
burst を指定するときは一緒に nodelay も指定するのが一般的で推奨されています。
即座に処理できるものは遅延させる必要はないでしょ? ということ。
また、nodelay だけ指定しても意味はない。burst があって効力がある。