セキュリティデータのエンリッチ化と Fastly エッジクラウドプラットフォームでグリンチボットをペナルティボックスに追加
以前のブログ記事では、Fastly のエッジクラウドプラットフォームからオリジンに送信する情報でリクエストをエンリッチ化する方法についてご説明しました。セントラルクラウドやオリジンサーバーには多くの機能と一元化された豊富な情報がありますが、これは、それらの情報を活用して可視性を高める方法のほんの一例に過ぎません。さらに、オリジンのセキュリティ判断を Fastly エッジで共有できるようになったら、どんなに素晴らしいでしょう。
このブログ記事では、オリジンのレスポンスから得られる情報を使って、不正な IP アドレスをペナルティボックスに追加する方法についてご説明します。Fastly は、エッジで実現可能なセキュリティソリューションを推進してきました。今回ご紹介するのはその一例です。では早速、始めましょう。
「グリンチボット」が暗躍する季節がやってきました。この時期、消費者が購入しようとする前に、さまざまなツールが在庫確認を行い、商品を買い占めしようとします。商品を購入したいエンドユーザーにとって、「現在、在庫はありません」と表示されることほど不快なことはありません。同時に、「グリンチボット」ツールは、常に在庫を確認するために大量のリクエストを送信することで、ビジネスに悪影響を及ぼす可能性もあります。多くの場合、これらのリクエストはオリジンに送信され、そこには、長期間にわたって一元化された在庫の記録が存在します。
このようなオリジントラフィックの問題に対処するには、エッジでインベントリをキャッシュし、インベントリの更新が必要なときだけキャッシュをパージするなど、いくつかの方法があります。このようなトラフィックをブロックするというのも一つのオプションです。トラフィックをブロックするには、オリジンが特定の IP アドレスの有害な動作を観測すると、Fastly の次世代 WAF を使って「406」のようなカスタムレスポンスコードを返し、指定された期間、Fastly エッジでその IP アドレスがブロックされるように設定します。
エッジレート制限とペナルティボックス
エッジレート制限は、現在、限定提供版でのご利用が可能です。この記事の残りの部分で使用するコンセプトについては、Developer Hub をご覧ください。
では、その仕組みについて見てみましょう。エッジは406または206のレスポンス (カスタマイズ可能) を受信すると、client.ip
をペナルティボックスに追加します。その後、エッジノードがその IP アドレスからリクエストを受信すると、その IP アドレスはエッジでブロックされ、429レスポンスが返されます。client.ip
がペナルティボックスに追加され、エッジノードがリクエストに対してアクションを開始するまでの間、短い遅延が発生することが予想されます。これらの動作の詳細については、レート制限に関するドキュメントをご覧ください。
ネイティブのエッジレート制限機能を使ってリクエストをカウントすることもできますが、ブロックなどのアクションを起こす前に、クライアントのリクエストをより正確にカウントしたい場合も多々あります。これは、オリジンサーバーに Fastly の次世代 WAF をデプロイすることで、簡単に実現できます。また、既存のオリジンロジックを使ってカウントを実行し、ブロックのステータスコードを返すこともできます。
この方法では、ratelimit.penaltybox_add とratelimit.penaltybox_has の関数が使用されます。問題のある IP アドレスは、ratelimit.penaltybox_add により、ペナルティボックスに追加されます。後続のリクエストでは、ratelimit.penaltybox_has により、ペナルティボックスにエントリーがあるか確認されます。エッジノードによって、ペナルティボックスにエントリーがあることが検出されると、リクエストはブロックされます。
以下にスニペット全体を示します。このスニペットは、init の位置に配置されました。このスニペットを本番環境に実装する前に、アプリケーションが正常に機能するかをテストし、確認する必要があります。
# Snippet rate-limiter-v1-origin_waf_response-init-init : 100
# Begin rate-limiter Fastly Edge Rate Limiting
penaltybox rl_origin_waf_response_pb {}
ratecounter rl_origin_waf_response_rc {}
table rl_origin_waf_response_methods {
"GET": "true",
"PUT": "true",
"TRACE": "true",
"POST": "true",
"HEAD": "true",
"DELETE": "true",
"PATCH": "true",
"OPTIONS": "true",
}
# Start rate-limiter Fastly Edge Rate Limiting
sub vcl_recv {
# call rl_origin_waf_response_process;
if (req.restarts == 0 && fastly.ff.visits_this_service == 0
&& table.contains(rl_origin_waf_response_methods, req.method)
) {
if (ratelimit.penaltybox_has(rl_origin_waf_response_pb, client.ip)) {
error 829 "Rate limiter: Too many requests for origin_waf_response";
}
}
}
# End rate-limiter Fastly Edge Rate Limiting
# Start check backend response status code
sub vcl_fetch {
# perform check based on the origin response. 206 status makes for easier testing and reporting
if (beresp.status == 406 || beresp.status == 206) {
log "406 or 206 response";
ratelimit.penaltybox_add(rl_origin_waf_response_pb, client.ip, 10m);
}
}
# End check backend response status code
# Start useful troubleshooting based on the response
sub vcl_deliver {
if (req.http.fastly-debug == "1"){
set resp.http.X-ERL-PenaltyBox-has = ratelimit.penaltybox_has(rl_origin_waf_response_pb, client.ip);
}
}
# End useful troubleshooting based on the response
sub vcl_error {
# Snippet rate-limiter-v1-origin_waf_response-error-error : 100
# Begin rate-limiter Fastly Edge Rate Limiting - default edge rate limiting error - origin_waf_response
if (obj.status == 829 && obj.response == "Rate limiter: Too many requests for origin_waf_response") {
set obj.status = 429;
set obj.response = "Too Many Requests";
set obj.http.Content-Type = "text/html";
synthetic.base64 "PGh0bWw+Cgk8aGVhZD4KCQk8dGl0bGU+VG9vIE1hbnkgUmVxdWVzdHM8L3RpdGxlPgoJPC9oZWFkPgoJPGJvZHk+CgkJPHA+VG9vIE1hbnkgUmVxdWVzdHMgdG8gdGhlIHNpdGUgLSBGYXN0bHkgRWRnZSBSYXRlIExpbWl0aW5nPC9wPgoJPC9ib2R5Pgo8L2h0bWw+Cg==";
return(deliver);
}
# End rate-limiter Fastly Edge Rate Limiting - default edge rate limiting error - origin_waf_response
}
デモを見る
以下の例では、私のお気に入りの負荷生成ツール Siege を使用しています。以下のコマンドは、206レスポンスが Fastly エッジに返される例です。この例では、ペナルティボックスを実装すると、ペナルティボックスに IP アドレスが追加されます。ドメインでコマンドを更新し、アカウントでエッジレート制限機能を有効にする必要があります。前述のように、ノードがペナルティボックスにエントリーがあるかどうかの確認を開始すると、多くのリクエストがオリジンに到達することが予想されるためです。
! siege https://[yourdomain.foo.bar]/206 -t 15s
** SIEGE 4.1.1
** Preparing 25 concurrent users for battle.
The server is now under siege...
HTTP/1.1 206 0.17 secs: 19 bytes ==> GET /206
HTTP/1.1 206 0.17 secs: 19 bytes ==> GET /206
HTTP/1.1 206 0.17 secs: 19 bytes ==> GET /206
#### Removed for brevity ####
HTTP/1.1 429 0.09 secs: 151 bytes ==> GET /206
HTTP/1.1 429 0.08 secs: 151 bytes ==> GET /206
HTTP/1.1 429 0.09 secs: 151 bytes ==> GET /206
HTTP/1.1 429 0.08 secs: 151 bytes ==> GET /206
Lifting the server siege...
Transactions: 3797 hits
Availability: 100.00 %
Elapsed time: 14.58 secs
Data transferred: 0.50 MB
Response time: 0.09 secs
Transaction rate: 260.43 trans/sec
Throughput: 0.03 MB/sec
Concurrency: 22.65
Successful transactions: 386
Failed transactions: 0
Longest transaction: 0.54
Shortest transaction: 0.06
実際に試してみましょう
グリンチボットはユースケースの一つに過ぎず、ペナルティボックスはあらゆるタイプの任意の識別子にアクセスできます。つまり、以下を含むさまざまなケースで、今回ご紹介した方法を応用することが可能です。
収益を生み出していない不審な ASN から大量のリクエストを受信している。
漏洩したクレデンシャルがログインエンドポイントで大量に使用されている。
大量のリクエストによって、400番台または500番台のレスポンスコードが発生している。
これは、Fastly とエンリッチ化されたリクエストの活用方法の一例に過ぎません。他にもアイディアがありましたら、ツイートして教えてください!今後もエッジで実現可能なセキュリティソリューションの例についてご紹介していきますので、ぜひご期待ください。