-----------------------------------------------------------------------
-----------------------------------------------------------------------
こんにちは、酒井です。
「このサイトは日本国内だけに公開したい」という要件はよくあると思います。CloudFront でサイトを配信しているなら、海外からのアクセスを止める方法は 2 つあります。CloudFront の地理的制限と AWS WAF の地域一致ルールです。
先日、業務で CloudFront の地理的制限を AWS CDK(インフラの構成をプログラムのコードで書く仕組み)で設定する場面がありました。その内容がチームに共有されたときに、WAF でも同じことができると知りました。
同じことができるなら、どちらを選べばよいのでしょうか。ブロックされた人には何が見えて、ログには何が残るのでしょうか。
S3 と CloudFront だけの小さなサイトを作り、両方の方法を実際に切り替えて試してみました。
CloudFront の地理的制限は、ディストリビューション(CloudFront の配信設定の単位)に付ける設定です。国の一覧を許可リストか拒否リストとして指定します。
WAF の地域一致ルールは、Web ACL(WAF のルールをまとめたもの)に書くルールの 1 つです。リクエストの送信元の国を条件にして許可や拒否を決めます。Web ACL は CloudFront のディストリビューションに関連付けて使います。
両方を設定した場合に、リクエストがどの順番で通るかを図にしました。
AWS のドキュメントによると、地理的制限でブロックされたリクエストは WAF に渡されません。WAF に渡されるのは許可されたリクエストだけです。この順番は、後の「両方を設定するとどうなるか」で実際に確かめます。
検証では、日本からのアクセスを手元の PC から確かめました。海外からのアクセスは、米国(バージニア北部)リージョンの AWS CloudShell(ブラウザで使えるコマンドの実行環境)から確かめています。どちらも curl コマンドでサイトにアクセスしました。
これ以降に載せるコマンドの出力では、一部のヘッダーを省いています。
■ CloudFront の地理的制限で止める
ディストリビューションを作る CDK(TypeScript)のコードの抜粋です。日本だけを許可する指定は geoRestriction の 1 行で済みます。
日本からは 200 でページが返りました。海外から curl -si でアクセスした結果は次のとおりです。
ステータスコードは 403 で、CloudFront が用意しているエラーページが返りました。本文には「このディストリビューションは、あなたの国からのアクセスをブロックするよう設定されています」という意味の文が入っています。国が理由で止められたことが、アクセスした人にも伝わります。
ドキュメントによると、このページは CloudFront のカスタムエラーページの設定で差し替えられます。今回は試していません。
CloudFront の標準ログ(アクセスログ)は、リクエスト 1 件につき 1 行が S3 に保存されるログです。今回のリクエストは次のように記録されました。
|
送信元 |
sc-status |
x-edge-result-type |
x-edge-detailed-result-type |
|
日本 |
200 |
Miss |
Miss |
|
海外 |
403 |
Error |
ClientGeoBlocked |
sc-status は返したステータスコードです。x-edge-result-type は CloudFront がリクエストをどう処理したかの分類で、x-edge-detailed-result-type にはより詳しい分類が入ります。日本からの Miss は、キャッシュになかったので S3 から取得したという意味です。
海外からのリクエストは、x-edge-detailed-result-type に ClientGeoBlocked と出ています。地理的制限によるブロックだと、標準ログだけで見分けられました。
この点はドキュメントの説明が食い違っています。地理的制限のページには「標準ログだけでは、地域による拒否とほかの理由による 403 を区別できない」という趣旨の説明があります。一方で標準ログのリファレンスには ClientGeoBlocked という値が載っています。今回の結果はリファレンスのとおりでした。
標準ログは、リクエストの数分後には S3 に届いていました。ただしドキュメントには、通常は 1 時間以内に届くとあります。最大で 24 時間遅れることもあると書かれています。
2 つ目のやり方で考えどころになるのは一覧の書き方ではありません。Cognito 側の変化をどうやって DB に反映するかです。
Web ACL を作る CDK のコードの抜粋です。どのルールにも一致しないリクエストは許可します。そのうえで「国が JP ではない」リクエストを拒否するルールを 1 つ置きました。
CloudFront の地理的制限に比べてコードが長くなります。
CloudFront の地理的制限との違いは、コードの長さのほかにもう 1 つあります。CloudFront 用の Web ACL(scope が CLOUDFRONT)は、米国東部(バージニア北部)の us-east-1 リージョンに作る必要があります。サイトのリソースを東京リージョンに置いている場合は、スタック(CDK でまとめてデプロイする単位)を 2 つに分けることになります。
今回は S3 と CloudFront を東京のスタックに、Web ACL を us-east-1 のスタックに分けました。Web ACL の ARN(リソースを指す識別子)は、CDK の crossRegionReferences という設定でリージョンをまたいで渡しています。2 つのスタックを作る CDK のコードの抜粋です。
crossRegionReferences は、CDK のドキュメントで「現在は実験的な機能」とされています。今回の検証では、作成から削除まで問題なく動きました。
海外からアクセスした結果です。
CloudFront の地理的制限で止めたときと同じ 403 で、エラーページの作りも同じです。違うのは理由を書いた 1 文だけでした。CloudFront の地理的制限では国が理由だと書かれていましたが、今回は Request blocked. としか書かれていません。ヘッダーで違っていたのは content-length だけでした。
CloudFront の標準ログでは、WAF によるブロックは次のように記録されました。
|
送信元 |
sc-status |
x-edge-result-type |
x-edge-detailed-result-type |
|
海外 |
403 |
Error |
Error |
CloudFront の地理的制限で止めたときは ClientGeoBlocked と出ましたが、今回は Error としか出ていません。標準ログだけでは、WAF が止めたことは分かりませんでした。
止めた理由は WAF のログに残ります。WAF のログは CloudWatch Logs などに出力でき、今回は CloudWatch Logs に出しました。次は、ブロックされたリクエストの記録(JSON)から主な項目を抜き出したものです。IP アドレスは例の値に置き換えています。
どのルールで止めたか(terminatingRuleId)と、判定された国(country)が 1 件ごとに分かります。labels には国に加えて、国の中の地域(この例ではバージニア州)も入っていました。WAF のログは、リクエストの約 20 秒後には CloudWatch Logs に届いていました。
WAF のログを CloudWatch Logs に出すときは、ロググループの名前を aws-waf-logs- で始める必要があります。ロググループは Web ACL と同じリージョンに作ります。CloudFront 用の Web ACL なら us-east-1 です。
WAF のルールには、拒否のほかに Count というアクションがあります。ルールの条件に当てはまったリクエストを数えるだけで拒否はしません。今回のルールなら日本以外からのリクエストが数えられます。アクションが拒否なら止められていたリクエストです。CDK のコードでは、ルールの action を次のように変えます。
この状態で海外からアクセスすると、200 でページが返りました。WAF のログ(JSON)には次のように記録されています。
リクエストは許可されましたが、ルール block-outside-japan に一致したことが記録されています。同じ時間帯の日本からのリクエストでは、nonTerminatingMatchingRules は空でした。
本番のサイトにルールを入れる前に Count で動かしておけば、拒否に切り替えたときに止まるリクエストをログで確かめられます。AWS のドキュメントでも、本番のトラフィックに対してまず Count で試すことが勧められています。CloudFront の地理的制限のドキュメントには、これにあたるモードの説明は見当たりませんでした。
WAF では、拒否したときに返すステータスコードと本文をルールごとに指定できます。404 と短い文を返すようにしてみました。CDK のコードでは、Web ACL に本文を登録し、ルールのアクションからその本文を指定します。
海外からアクセスした結果です。
指定した 404 と本文だけが返りました。CloudFront のエラーページは付いていません。WAF のログでは、返したステータスコードが responseCodeSent という項目に 404 と記録されていました。
CloudFront の地理的制限と WAF のルールを両方有効にして、海外からアクセスしました。
返ってきたのは 403 です。エラーページに書かれたブロックの理由も、CloudFront の地理的制限だけのときと同じでした。標準ログにも ClientGeoBlocked と記録されています。
WAF のログには、このときの海外からのリクエストは記録されていませんでした。同じ時間帯の日本からのリクエストは記録されています。CloudFront の地理的制限でブロックされたリクエストは WAF に渡らないという、ドキュメントの説明どおりの結果です。
両方を設定すると、海外からのリクエストは WAF のログに残らなくなります。WAF のドキュメントにも、この点についての案内があります。地域とほかの条件を組み合わせてブロックしたい場合は、CloudFront の地理的制限ではなく WAF の地域一致ルールを使うよう書かれています。
ここまでに確かめたログの残り方を図にまとめました。
|
観点 |
CloudFront の地理的制限 |
WAF の地域一致ルール |
|
料金 |
追加料金なし |
Web ACL が月 5 ドル、ルールが月 1 ドル、100 万リクエストあたり 0.6 ドル |
|
制御の細かさ |
ディストリビューション全体に、国の許可か拒否だけ |
IP アドレスなど、ほかの条件と組み合わせられる |
|
事前に試せるか |
試すためのモードは見当たらない |
Count で、拒否せずに確かめられる |
|
ブロック時の応答 |
403 とエラーページ。国が理由だと書かれる |
403 とエラーページ。ステータスコードと本文を指定できる |
|
記録 |
標準ログに ClientGeoBlocked |
WAF のログにルール名と国。標準ログでは Error |
|
CDK のコード |
geoRestriction の 1 行 |
Web ACL の定義。us-east-1 に作る |
|
設定を変えるデプロイ |
ディストリビューションの更新(約 52〜139 秒) |
Web ACL の更新(約 15 秒) |
表の「料金」と「制御の細かさ」は、ドキュメントで確かめた内容です。「事前に試せるか」の CloudFront の地理的制限の欄も同じです。ほかは今回試した結果です。
設定を切り替えたときのデプロイの時間は次のとおりでした。CDK が表示したスタックごとのデプロイ時間で、どれも 1 回ずつの計測です。
|
切り替えた内容 |
更新されたもの |
かかった時間 |
|
地理的制限を外して Web ACL を関連付ける |
ディストリビューション |
約 139 秒 |
|
ルールのアクションを拒否から Count に変える |
Web ACL |
約 15 秒 |
|
ルールのアクションをカスタムレスポンスに変える |
Web ACL |
約 15 秒 |
|
Web ACL を付けたまま地理的制限を足す |
ディストリビューション |
約 52 秒 |
Web ACL のルールを変えるだけなら 15 秒ほどで終わりました。CloudFront の地理的制限はディストリビューションの設定なので、変えるたびにディストリビューションの更新が入ります。
ここで比べたのはデプロイが終わるまでの時間です。WAF のドキュメントには、変更が行き渡るまでに数秒から数分かかるとあります。デプロイが終わって 1 分ほど後に確かめると、新しい設定が効いていました。
CloudFront の地理的制限に追加料金はかかりません。WAF の料金は Web ACL が月 5 ドル、ルールが月 1 ドルで、リクエストには 100 万件あたり 0.6 ドルがかかります。これは従量課金の場合の金額で、月額は時間割りで計算されます。検証のように短い時間だけ置くなら、月額はその時間分で済みます。今回の検証で Web ACL を置いていたのは約 30 分でした。すでに Web ACL を使っているサイトに地域一致ルールを 1 つ足す場合は、ルールの月額 1 ドルが増えます。
CloudFront には、WAF を含めて月額が決まっている定額プランもあります。定額プランを使っている場合は、ここに書いた料金の比較は当てはまりません。
ブロックされたリクエストにかかる CloudFront の料金も調べました。WAF がブロックしたリクエストには、2024 年 10 月 25 日から CloudFront のリクエスト料金とデータ転送料金がかからなくなっています。ただし WAF の料金は、ブロックしたリクエストにもかかります。地理的制限でブロックされたリクエストについては、料金がかかるかどうかを書いた公式の説明を見つけられませんでした。
国の判定のしかたにも違いがあります。CloudFront のドキュメントには、最近のテストでは国の判定の精度が全体で 99.8% だったと書かれています。国を判定できない場合、CloudFront はコンテンツを配信します。
WAF は MaxMind 社の GeoIP データベースを使っています。国レベルの精度は非常に高いと書かれていますが、数字は載っていません。国コードを取得できない場合、WAF がリクエストに付けるラベルの国コードは XX になります。
今回のように「JP ではない」を拒否するルールでは、XX は JP ではないので拒否されると考えられます。そうだとすると、国を判定できないリクエストを CloudFront の地理的制限は通し、WAF のルールは止めることになります。この動きは今回試せていません。
国単位でサイト全体を止めるだけなら、CloudFront の地理的制限で足りると思います。追加料金がなく、CDK のコードは 1 行です。標準ログで地理的制限によるブロックだと見分けられることも確かめました。
WAF の地域一致ルールが向いているのは、次のような場合です。
すでに Web ACL を使っているサイトなら、ルールを 1 つ足すだけで済みます。Web ACL をまだ使っていないサイトでは、Web ACL の月額と us-east-1 にスタックを用意する作業が増えます。国で止めるためだけに WAF を入れるかどうかは、上の 4 つが必要かどうかで決めるとよさそうです。
国を条件にしたルールを WAF にも書く場合は、CloudFront の地理的制限と併用しないほうがよさそうです。地理的制限が先に効くので、海外からのリクエストは WAF に届かず、WAF のログにも残りません。ほかの目的で Web ACL を使っているサイトに地理的制限を足すぶんには、この問題は起きません。
海外からのアクセスを止める 2 つの方法を、同じサイトで切り替えて比べました。
CloudFront の地理的制限は CDK のコード 1 行で設定でき、追加料金もかかりません。標準ログには ClientGeoBlocked と記録されるので、地理的制限によるブロックだと見分けられました。
WAF の地域一致ルールには、Web ACL の月額と us-east-1 のスタックが要ります。その代わりに Count で事前に試せて、返すステータスコードと本文も指定できます。止めたルールと国はログに残りました。
両方を設定すると CloudFront の地理的制限が先に効き、海外からのリクエストは WAF のログに残りませんでした。
国単位でサイト全体を止めるだけなら CloudFront の地理的制限で足ります。ほかの条件との組み合わせや事前の確認が必要なら WAF が向いています。海外からのアクセスを止める方法を選ぶときの参考になればうれしいです。