CloudFrontのレスポンスヘッダーポリシーでCORSに対応する

CloudFrontのレスポンスヘッダーポリシーでCORSに対応する

curlではなくブラウザで挙動を確認しました。
Clock Icon2024.12.18

こんにちは、なおにしです。

CloudFront+S3の構成でCORS設定を試してみたのでご紹介します。

はじめに

「CORS」(読み方:コルスが一般的のようなので私もそれに倣っています)の仕組みについては解説記事がWeb上に大量にあるのでそちらをご参照いただければと思いますが、MDN Web Docsでは以下のように説明されています。

CORS (オリジン間リソース共有、 Cross-Origin Resource Sharing) は、 HTTP ヘッダーの転送で構成されるシステムであり、ブラウザーがオリジンをまたいだリクエストのレスポンスに、フロントエンドの JavaScript コードがアクセスすることをブロックするかどうかを決めるものです。

言葉どおりではあるのですが、いまいちイメージがつきにくいことから解説記事が増えているのだと思います(本記事もその一つです)。

このため、今回はブラウザからのアクセスで実際に挙動を確認しながら、CloudFrontのレスポンスヘッダーポリシーを使用してCORS設定を行ってみます。なお、レスポンスヘッダーポリシー自体は提供開始から数年経過しているもので、詳細は以下の記事をご参照ください。

https://dev.classmethod.jp/articles/amazon-cloudfront-supports-response-headers-policies/

やってみた

前提

以下のようにCloudFrontのオリジンとして、EC2インスタンスをバックエンドにしたALBと、静的コンテンツを格納したS3バケットをそれぞれ指定する構成で考えてみます。

20241218_naonishi_cors-with-cloudfront-response-header-policy_1.png

EC2インスタンスには以下のテスト用HTMLファイルをドキュメントルートに格納しました。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>CloudFront CORS テスト</title>
</head>
<body>
    <h1>CloudFront と S3 を使用した CORS テストページ</h1>
    <p>このページでは、ALB から提供される HTML と S3 から提供される画像が正しく表示されることを確認します。</p>

    <div class="image-container">
        <h2>imgタグで表示</h2>
        <img src="https://www.example.com/images/sample-image.png" alt="サンプル画像 1">
    </div>

    <div class="image-container">
        <h2>canvasタグとjsで表示</h2>
        <canvas id="myCanvas" width="300" height="300" style="border:1px solid #000000;"></canvas>
    </div>

    <script>
        // 画像をキャンバスに描画して CORS をテスト
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        const img = new Image();
        img.src = "https://www.example.com/images/sample-image.png";

        img.onload = function() {
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
            // 画像データの取得 (CORS 設定が正しくないとエラーになります)
            try {
                const imageData = canvas.toDataURL();
                console.log("画像データ取得に成功");
            } catch (error) {
                console.error("画像データ取得に失敗:", error);
            }
        };

        img.onerror = function(err) {
            console.error("画像の読み込みエラー:", err);
        };
    </script>
</body>
</html>

CloudFrontのビヘイビアでは以下のように/images/配下のファイルを指定したときはオリジンとしてS3を、それ以外の時はALBに振り分けられるように設定しています。

20241218_naonishi_cors-with-cloudfront-response-header-policy_2.png

パスパターン「/images/*」のビヘイビアでは、以下のようにキャッシュ無効でオリジンリクエストポリシーとレスポンスヘッダーポリシーも未設定の状態です。後ほどCORSに対応するために設定を変更していきます。

20241218_naonishi_cors-with-cloudfront-response-header-policy_3.png

また、オリジンのS3は静的Webサイトホスティングは無効で、OACを用いた接続になっています。

20241218_naonishi_cors-with-cloudfront-response-header-policy_4.jpg

CloudFrontでCORS関連の設定をしていない場合

まず、CloudFront経由(構成図のwww.example.com)でアクセスした場合は以下のとおりです。

20241218_naonishi_cors-with-cloudfront-response-header-policy_5.jpg

画像が2枚表示されています。S3に格納されている画像ファイル自体は1つですが、上部の画像はimgタグで表示したもので、下部の画像はjavascriptを用いて描写したものになります。CloudFrontではキャッシュを無効に設定しているので、Chrome DevToolsで表示されているとおり今回はS3から画像を取得することができています。

続いてALB経由(構成図のalb.example.com)でアクセスした場合は以下のとおりです。

20241218_naonishi_cors-with-cloudfront-response-header-policy_6.jpg

下部の画像だけ表示されませんでした。Statusにも「CORS error」と表示されています。javascript内のエラーハンドリングによって出力された内容は以下のとおりです。

20241218_naonishi_cors-with-cloudfront-response-header-policy_7.jpg

Access to image at 'https://alb.example.com/images/sample-image.png' from origin 'https://alb.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

CORSのポリシーによって画像ファイルへのアクセスがブロックされた旨が出力されていることを確認できました。

それではCloudFrontの設定を変更することで、上記のCORSによってブロックされた画像が表示できるように修正していきます。

CloudFront ビヘイビアの修正

まず、オリジンリクエストポリシーを設定します。オリジンリクエストポリシーではマネージドポリシーとして「CORS-S3Origin」が準備されています。詳細はドキュメントをご確認ください。「CORS-S3Origin」ポリシーでは、以下3つのヘッダーをオリジンリクエストに含めます。各ヘッダーの解説はMDN Web Docsから引用しました。

  • Origin

    • どのオリジンからアクセスしているかを示します。
  • Access-Control-Request-Headers

    • プリフライトリクエストを発行する際に、実際のリクエストを行う際に使用される HTTP ヘッダーをサーバーに知らせるために使用します。
  • Access-Control-Request-Method

    • プリフライトリクエストを発行する際に、実際のリクエストを行う際に使用される HTTP メソッドをサーバーに知らせるために使用します。

つまり、「CORS-S3Origin」ポリシーではプリフライトリクエストに使用されるヘッダーが対象となっています。具体的にどのようなリクエストが行われるのかについてはMDN Web Docsをご参照ください。プリフライトリクエスト自体は以下のように解説されています。

CORS のプリフライトリクエストは CORS のリクエストの一つであり、サーバーが CORS プロトコルを理解していて準備がされていることを、特定のメソッドとヘッダーを使用してチェックします。
これは OPTIONS リクエストであり、 Access-Control-Request-Method,Access-Control-Request-Headers, Origin の 3 つの HTTP リクエストヘッダー使用します。
プリフライトリクエストはブラウザーが自動的に発行するものであり、通常は、フロントエンドの開発者が自分でそのようなリクエストを作成する必要はありません。これはリクエストが "to be preflighted" と修飾されている場合に現れ、単純リクエストの場合は省略されます。

続いて、レスポンスヘッダーポリシーを設定します。レスポンスヘッダーポリシーについてはAWSマネジメントコンソールでポリシーを作成しようとすると「クロスオリジンリソース共有 (CORS) -オプション」として各CORSヘッダーの設定を行うことができます。

20241218_naonishi_cors-with-cloudfront-response-header-policy_8.png

オリジンリクエストポリシーと同様にレスポンスヘッダーポリシーでもマネージドポリシーとして「CORS-With-Preflight」が準備されています。詳細はドキュメントをご確認ください。「CORS-With-Preflight」ポリシーはマネジメントコンソール上では以下のように設定されていることが確認できます。

20241218_naonishi_cors-with-cloudfront-response-header-policy_9.png

ドキュメントに記載のある「オーバーライドオリジンですか。:いいえ」についてはマネジメントコンソール画面上で表示されていない=いいえという意味のようなので、念のためCLIで確認したところ、確かに「"OriginOverride": false」となっていました。

$ aws cloudfront get-response-headers-policy --id 5cc3b908-e619-4b99-88e5-2cf7f45965bd

{
    "ETag": "E23ZP02F085DFQ",
    "ResponseHeadersPolicy": {
        "Id": "5cc3b908-e619-4b99-88e5-2cf7f45965bd",
        "LastModifiedTime": "1970-01-01T00:00:00+00:00",
        "ResponseHeadersPolicyConfig": {
            "Comment": "Allows all origins for CORS requests, including preflight requests",
            "Name": "Managed-CORS-With-Preflight",
            "CorsConfig": {
                "AccessControlAllowOrigins": {
                    "Quantity": 1,
                    "Items": [
                        "*"
                    ]
                },
                "AccessControlAllowHeaders": {
                    "Quantity": 0,
                    "Items": []
                },
                "AccessControlAllowMethods": {
                    "Quantity": 7,
                    "Items": [
                        "GET",
                        "HEAD",
                        "PUT",
                        "POST",
                        "PATCH",
                        "DELETE",
                        "OPTIONS"
                    ]
                },
                "AccessControlAllowCredentials": false,
                "AccessControlExposeHeaders": {
                    "Quantity": 1,
                    "Items": [
                        "*"
                    ]
                },
                "OriginOverride": false
            }
        }
    }
}

したがって、MDN Web Docsに記載がある9つのCORSヘッダーが、オリジンリクエストポリシーとレスポンスヘッダーポリシーによって全て設定できることが確認できました。一旦はレスポンスヘッダーポリシーとして「CORS-With-Preflight」ポリシーを設定して、ビヘイビアの設定としては以下のとおりで再度ブラウザアクセスを確認してみます。

20241218_naonishi_cors-with-cloudfront-response-header-policy_10.png

ALB経由のアクセスでも表示できるようになりました。URLをぼかしているためスクリーンショットが本当にALB経由のアクセスなのかどうか違いが分かりづらいかと思いますが、とりあえずCORSのレスポンスヘッダーが存在することは確認できます。

20241218_naonishi_cors-with-cloudfront-response-header-policy_11.jpg

オリジンを制限する場合

「CORS-With-Preflight」ポリシーでは「Access-Control-Allow-Origin:*」となっているため、今回の構成でいうと「alb.example.com」以外からもCORSによって制限されずにアクセスできてしまいます。「alb.example.com」からのアクセスのみ応答できるようにするには、レスポンスヘッダーポリシーでカスタムポリシーを作成する必要があります。「CORS-With-Preflight」ポリシーを参考に、例えば以下のように作成できます。

20241218_naonishi_cors-with-cloudfront-response-header-policy_12.png

以下のように「Access-Control-Allow-Origin」で指定したオリジンが返ってくるようになります。なお、上記の「alb.example.com」を「hoge.example.com」のような別ドメインに設定すると、もちろん下部の画像はCORSエラーになって表示されませんでした。

20241218_naonishi_cors-with-cloudfront-response-header-policy_13.jpg

S3バケットのCORS設定について

ここまでの確認では、CORS設定がレスポンスヘッダーポリシーで完結しているため、以下のとおりS3バケット自体のCORS設定は何も行っていません。

20241218_naonishi_cors-with-cloudfront-response-header-policy_14.png

今回の検証ではS3バケットで静的ウェブサイトホスティングを有効にしていませんが、上記の設定が機能するのか確認してみます。

S3バケットへのCORS設定については以下のドキュメントに記載されています。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ManageCorsUsing.html

上記のドキュメントを参考に、S3バケットにCORS設定を行いました。

20241218_naonishi_cors-with-cloudfront-response-header-policy_15.jpg

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET",
            "HEAD"
        ],
        "AllowedOrigins": [
            "https://alb.example.com"
        ],
        "ExposeHeaders": []
    }
]

併せて、CloudFront のビヘイビアではレスポンスヘッダーポリシーを無効化します。

20241218_naonishi_cors-with-cloudfront-response-header-policy_16.jpg

ALB経由のアクセスを再度ブラウザから確認したところ、以下のように表示できました。ちなみにS3バケットのCORS設定を削除すると下部の画像のみ表示できなくなりました。

20241218_naonishi_cors-with-cloudfront-response-header-policy_17.jpg

「Access-Control-Allow-Credentials」ヘッダーについてはS3バケットのCORS設定では制御できなさそうでしたが、実際にはtrueとなっている挙動はドキュメントに記載の内容と一致しています。

A Boolean that determines if the server allows CORS requests to contain credentials. If the Access-Control-Allow-Origin request header is set to '*' then the Access-Control-Allow-Credentials response header will be omitted, else it is set to true when CORS evaluation is successful.

以上より、CORS設定はCloudFrontのレスポンスヘッダーポリシーとS3バケットのどちらで設定しても良いことが分かりました。逆にいうと、明確な意図が無い限りは両方を設定する必要はないかと思います。両方を設定していた場合、レスポンスヘッダーポリシー内のオリジンのオーバーライド有無でも挙動が異なるため、イメージしづらくなる可能性もあります。

まとめ

CORS に関する色々な記事を読んでいる際、CloudFront+S3構成の場合はS3バケットでCORSを設定していたりしていなかったり、curlによるアクセスで挙動を確認しているため私の理解力ではいまいちイメージが湧きにくかったりしたため、実際にブラウザを使用してCORSの挙動を確認してみました。

本記事がどなたかのお役に立てれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.