WAFの評価を本番環境で行う、CloudFront2段構成のノウハウ

※画像はCopilotで作成

 

インフラグループ 宮本です。

担当プロダクトでは、サービスの保護のためAWS WAFを使用しています。このたび使用しているWAFのACLを刷新し、新しいACLへの移管を行いました。

この際、新しいWAF ACLのエージングは本番環境で行う必要がある一方で、本番環境を止めたくない、期間中もサービスの保護レベルを下げたくない、という要求があり、CloudFrontの2段構成(CloudFrontのOriginに別のCloudFront Distributionを配置する構成)という、特殊な構成をとりました。

AWSのCloudFrontを採用している本番環境にてWAF ACLの刷新を検討されている方には、同様の要求と、それに伴う課題が発生するのではないかと考え、我々の取り組みをご紹介します。

※ 本文中にViewerという単語が登場しますが、すべてエンドユーザーもしくはWebブラウザ(1段目のCloudFrontのViewer)を指すものとします。ご了承ください。

想定読者

  • CloudFront を使用する環境で、WAF のACLそのものを刷新したいインフラ担当者
  • 既存ACLに新しいルールを追加したいだけのケースは対象外

この記事を読むことで、以下のような内容がご理解いただけます

  • CloudFront採用環境でWAF ACLを刷新する際に発生する要求
  • CloudFront2段構成による解決方法
  • CloudFront2段構成を作る上で考慮すべきポイント

 早速ひとつ目のポイントから行ってみましょう!

CloudFront採用環境でWAF ACLを刷新する際に発生する要求

要求事項①:サービスの保護レベルの低下を招かないこと

ひとつ目は、WAFエージング期間中のサービス保護レベルが、"BLOCK"から"COUNT"に下がらないことです。

一般にWAFのエージング、つまり過検知や誤検知のチューニングは本番環境で実施します。この理由は、検証環境で想定される全てのリクエストを再現することが難しいためです。加えて、検証するWAFルールは"COUNT"モードで実施し、過検知や誤検知による"BLOCK"が発生しない状態で行うのが安全です。

担当プロダクトのようにCloudFrontを使用している環境で、ACLを刷新しエージングを実施すると、本要求事項を満たすための課題が発見されました。それはCloudFrontに適用できるAWS WAFのACLはひとつだけという制約です。

ACLを丸ごと刷新する場合、既存の"BLOCK"モードで稼働するACLと、"COUNT"モードで稼働する新しいACLの両方を一度にアタッチすることができません。新しいACLのエージングを通常の構成で行おうとすると、"COUNT"モードのACLのみアタッチされることになり、サービスの保護レベルが下がります。

補足

既存のACLに新しいルールを追加する場合は、当該ルールを"COUNT"モードで、既存ルールよりも高いプライオリティで追加することで、既存のルールによる"BLOCK"を維持しつつ、エージングを行うことが可能です。

一方で既存の、使用中のACLに手を加えられない状況が存在します。例えば、元々AWS WAF ACLのルールのマネージメントを外部の運用サービスに委託していて、切り替えを検討する場合です。外部の運用サービスでルールが管理されるACLに対し、検証用のルールを独自で追加することは難しいため、新しいACLを作成して切り替える必要があります。

要求事項②:サービス影響の低減

ふたつ目の要求は言うまでもないことですが、機会損失を防ぐため、本番環境のサービス影響を抑えて対応することです。

要求事項①を解決するためには、通常の構成における通信経路上に、"COUNT"モードのACLを差し込む、構成変更の必要性が明らかでした。このため、構成変更の開始と終了、期間中の各種リリースやメンテナンスなどのタイミングで、構成変更によるサービス影響を抑制する変更方法が課題となりました。

CloudFront2段構成による解決方法

構成:CloudFront2段構成

図1. 構成概要

既存のCloudFrontにアタッチするWAF ACLと、そのオリジンとなるCloudFront Distributionを新規作成します。新しいWAF ACLは"COUNT"モードで動作します。詳細は後述しますが、CloudFrontは2段目を新設し、新しいWAF ACLは1段目のCloudFrontにアタッチするものを新設するのがポイントです。

Origin側にアクセス元の制御が入っている場合は、新しいDistributionからのアクセスを許可するように、併せて修正が必要です。S3 Bucketを暗号化している場合は、KMSキーの制御ポリシーなども確認する必要があります。

Tips 1 AWSの推奨について

AWSとしてはCloudFrontのOriginにCloudFrontのDistributionを配置する多段構成は推奨しておらず、ネット上で検索すると、3段以上の構成はNGとしてサポートから回答された方もいらっしゃるようです。

あるディストリビューションを別のディストリビューションの前に配置することはお勧めしません。

HTTP 403 ステータスコード (Permission Denied) - Amazon CloudFront

あくまで今回のように、他に手段が無い場合に採用される構成であるということでご容赦ください。元々の構成で既に、OriginにCloudFrontが登録されている場合は3段構成になってしまうため、当該のCloudFrontは新規作成したCloudFrontと同じ2段目に配置し、既存のWAF ACLを直接アタッチするようにしてください。

Tips 2 レイテンシーについて

2段構成にするとレイテンシーへの影響が懸念されます。担当プロダクトにおいても最大50ms程度の増加を想定していました。しかし実際に移行した後1週間程計測したところ、意外なほどレイテンシーの平均値に目立った増加は見られず、10ms程度増えたかどうかでした。増分の幅や、この増分がエラーレートの上昇に結びつくかはアプリケーション次第と思われますが、ひとつの参考になればと思います。

移行ステップ

0. 移行前の状態

図2. 移行前

1. 事前準備

図3. 事前準備

・2段目のCloudFrontと新しいWAF ACLを構築

・新しいCloudFrontがOriginにアクセス可能な状態を作っておく

・新しいCloudFrontに古いACLを紐づけておく。この時点では古いWAFが新旧どちらのCloudFrontにも紐づいている状態

2. 2段構成開始

図4. 2段構成開始

・2段構成に切り替える

・1段目のCloudFrontのOriginを全て2段目に向ける

・1段目のWAFを新しいWAFに差し替える

3. 2段構成の終了

図5. 2段構成の終了

・エージングが終了した後、新しいWAFをBLOCKモードで動作させる

・1段目のCloudFrontのOriginを元に戻す

・2段目のCloudFrontや古いWAF ACL、Originの制御などを元に戻す

CloudFront2段構成を作る上で考慮すべきポイント

ここからは、2段構成を作る上で考慮すべきポイントを3点ご紹介します。

①1段目ではなく、2段目のCloudFront Distributionを作る

②2段目でHost ヘッダーを書き換える

③ClientIpベースのWAF ACLルールの修正

①1段目ではなく、2段目のCloudFront Distributionを作る

ここまで読まれて、1段目のCloudFront Distributionの前に来るCloudFront Distributionを作成したほうが、変更箇所が少なくなって楽なのではないか、と思われた方もいらっしゃるかと思います。しかしながら、2段目を作る方式を採らない場合、サービス断が発生する場合があります。代替ドメイン名を設定している場合です。

CloudFrontを使用するとき、本番環境では代替ドメイン名を設定されていることが多いのではないかと思います。(※代替ドメイン名とは、CloudFrontのデフォルトの、xxxx.cloudfront.netというドメインではなく、例えばcf.persol-career.jpのようなドメインでDistributionにアクセスできるようにする機能です)代替ドメイン名として設定できるドメイン名は、全世界のCloudFront Distributionに対して一意である必要があります。これについて、ドキュメントには以下のような記載があります。

重複する代替ドメイン名
同じ代替ドメイン名が別の CloudFront ディストリビューションに既に存在する場合は、AWS アカウントが他のディストリビューションを所有しているとしても、その代替ドメイン名を CloudFront ディストリビューションに追加することはできません。

代替ドメイン名 (CNAME) を追加することによって、カスタム URL を使用する - Amazon CloudFront

つまりは、1段目より前のDistributionを作成した場合、移行ステップにおける"2. 2段構成開始"において、代替ドメイン設定を一度削除してから新しいDistributionに再度代替ドメインを設定する必要があり、この作業の間はサービス断となります。これでは要求事項の②を満たせないため、よりサービス影響のない移行方式として2段目のDistributionを作成することをお勧めします。

Tips 3 Terraform管理の場合

Terraform管理下のCloudFront Distributionにおいては、代替ドメインの削除を行おうとすると、Distributionリソース自体の再作成が発生します。ご注意ください。

②2段目でHost ヘッダーを書き換える

2段構成をとる際、Viewer RequestのHost ヘッダーをそのままOriginに渡すことができません。このため、OriginにあるアプリケーションにHost ヘッダーの値を渡すには、少し工夫が必要です。

先にCloudFrontの挙動について触れると、リクエストがOriginに転送される際、リクエストのHost ヘッダーはOriginのドメイン名に置き換わります。(図6参照)一方で、Host ヘッダーにViewerのアクセスしたドメイン名(例:cf.persol-career.jp)を格納した状態で、リクエストをOriginに転送したいケースも存在します。このため、CloudFrontのCache Behaviorには、Viewer Requestに含まれるHost ヘッダーをそのままOriginに転送する設定が可能です。例えば、Cache BehaviorのOrigin Request Policyを、Host ヘッダーを転送するAllviewerに設定すると、Viewerのアクセスしたドメイン名をHost ヘッダーに格納し、アプリケーションに転送可能です。

図6. Originドメイン名への置き換え

通常のCloudFront1段の構成であればこの設定を使って問題ないのですが、2段構成の際はループを発生させてしまいます。(図7参照)CloudFront ではHost ヘッダーをもとにリクエストするDistributionを決定します。(図8参照)このため、1段目でHost ヘッダーを維持したまま2段目に転送しようとすると、CloudFrontは再度、1段目のDistributionにリクエストを転送しようとしてループが発生し、最終的には403エラーとなります

図7において1段目のCloudFrontを出たリクエストは、OriginのURL、つまり2段目のCloudFrontのドメイン名を名前解決した結果、図8でいうところのViewer facing Endpointに到達します。その後Host ヘッダーをもって、再度1段目のDistributionに配信され、結果として図7に記載したループとなります。

図7. Host ヘッダーの転送によるループ

図8. CloudFrontにおけるリクエスト先の決定

それではどうするか、という話です。私たちの対応としては、2段目のCloudFrontのOrigin Requestのタイミングで、Lambda@Edgeによって、Host ヘッダーをViewerのアクセスしたドメイン名への書き換えを行いました。(図9参照)これにより、CloudFrontのループ問題を回避しつつ、Originのアプリケーションに対して有効なドメイン名をHost ヘッダーに付与した状態でリクエストを転送できました。

図9. Lambda@Edge によるHost ヘッダーの書き換え

参考までに、Lambda functionのサンプルを以下に記します。Node.jsで動作します。担当プロダクトではDistributionに付与した代替ドメイン名が1つだったため、置き換えるドメイン名はハードコーディングしました。もし複数の代替ドメイン名を紐づけている場合は、1段目にFunctionを追加して、Viewer RequestのHost ヘッダーを任意のヘッダーに格納しておき、2段目のLambda@Edgeで取り出すようなカスタマイズが必要になります。

export const handler = async (event) => {
 
  const request = event.Records[0].cf.request;
  const headers = request.headers;

  // ホストヘッダーを変換
  const newHostHeader = '{変換したいドメイン名}';
  headers['host'] = [{
    key: 'Host',
    value: newHostHeader,
  }];

  console.log(`Request header "Host" was set to "${newHostHeader}"`);

  return request;
};

補足

図8のViewer facing Endpoint および Origin facing Endpoint は説明のための造語で、AWS公式の用語ではありません。ご了承ください。

図7のループについて、著作権のため引用は控えますが、サポートに確認しています。Host ヘッダーを使った配信先の決定や、それに伴うループの発生について回答をいただきました。ループの結果403エラーになる事象についてはAWSの公式ドキュメントに以下のように記載されています。

スタックディストリビューションが 403 エラーを引き起こす場合

オリジンエンドポイントへのリクエストチェーン内に複数のディストリビューションがある場合、CloudFront は 403 エラーを返します。

HTTP 403 ステータスコード (Permission Denied) - Amazon CloudFront

 

Tips 4 CloudFront Functions ? Lambda@Edge ?

CloudFrontのCache Behaviorとして設定できるFunctionには、軽量かつ設定の容易なCloudFront Functions と、より複雑な処理に向いたLambda@Edge の2種類があります。しかし今回のようにOrigin Requestのタイミングで使用できるのはLambda@Edgeだけです。

図10. Cacheに付加できるFunction

③ClientIpベースのWAF ACLルールの修正

1段目のCloudFrontを経由することで、リクエストのClientIpはCloudFrontのOrigin-facing IPに書き換えられます。このため、2段目のWAF ACLにClientIpベースの許可・拒否を定義したルールがある場合には、適切に評価されるように調整が必要です。

ViewerのClientIpを評価するには2つの方法があります。それは1段目のWAFでClientIpベースのルールのみ"COUNT"ではなく"ALLOW"もしくは"BLOCK"する方法と、2段目のWAFで評価する対象をClientIpではなく、CloudFront-Viewer-Address ヘッダーもしくは X-Forwarded-For ヘッダーとする方法です。

どちらを採用するかは、対象ルールのアクションによって変わります。"ALLOW"ルール(IP Allowlist)の場合は2段目で評価する必要があり、"BLOCK"ルール(IP Denylist)の場合は1段目で評価されるように設定するのがより良いと考えられます。(図11参照)

図11. 送信元IPアドレスの評価

WAFにおけるAllowlistは、他の"BLOCK"を定義するルールとの評価順序が重要です。Allowlistに合致したリクエストは、他の優先順位の低いルールをバイパスし、Originに転送されます。Allowlistを1段目のWAF ACLに設定した場合、1段目の他ルールの評価をバイパスしますが、2段目のルールはバイパスしないため、Allowlistに登録されているViewerのリクエストがブロックされる事態が発生し得ます。このため、そもそもAllowlistとして動作させるためには2段目で、評価対象を前出のヘッダーに修正するしかありません。

一方でDenylistには、世界的に有名な脅威アクターの発信源として知られるIPアドレスが登録されている場合が多いと思います。これらはどこで評価されようがブロックされるべきもので、1段目でブロックされても問題ありません。Allowlistのように2段目で評価対象のヘッダーを修正することも可能ですが、これは2段構成に移行する際に課題が発生します。2段目のWAFは構成変更の前後で常にリクエストを評価し続けています。評価対象のヘッダーを修正するとなると、作業時間のわずかな間ではありますが、Denylistが機能しない時間が発生します。これは要求事項の①を満たさないので、避けた方がより良いです。

おわりに

いかがだったでしょうか。本記事では

CloudFront採用環境でAWS WAF ACLの刷新をする必要がある場合に、

サービス影響を極力抑えたエージングを行うCloudFront2段構成のご紹介および

2段構成をとる場合の考慮ポイント3点

をお伝えしました。少々ボリュームの大きな記事になってしまいましたが、私たちと同じくクラウドインフラの担当として、日本のどこかで日々奮闘されている方の参考になれば幸いです。最後までお読みいただき、ありがとうございました。

 

 

宮本 太一 Taichi Miyamoto

ITガバナンス本部 ITマネジメント&インフラ・セキュリティ統括部 インフラ部 インフラグループ リードエンジニア

2017年 パーソルホールディングス グループIT本部入社。2019年に発足したグループ全社向け次世代クラウド基盤のNW設計PJTのPMを経て、次世代クラウド基盤企画と、運用チームの管理に従事。2022年4月よりパーソルキャリアへ出向。インフラエンジニアとして、基幹システムのクラウドリフトを担当。現在は事業システムのクラウドインフラエンジニアとして活動中。

※2025年12月時点の情報です。