LoginSignup
15
0

SPAだとCloudFront Functionsでレスポンスヘッダー付与できない!?

Last updated at Posted at 2023-12-11

はじめに

Japan AWS Jr. Champions Advent Calendar 12日目の投稿です!
今回はSPA(シングルページアプリケーション)でのCloudFront FunctionsでCloudFrontからのレスポンスヘッダー付与で起きた問題、解決策について紹介します。

起きた問題

タイトル通りSPAアプリケーションをCloudFront・S3で配信してる時にCloudFront Functionsでレスポンスヘッダーを付与されていなかったという問題がありました。
さらにセキュリティヘッダーがルートパスアクセス時のみ付与されており、ルートパス以外はセキュリティヘッダーがレスポンスに含まれていない状況でした。

構成

下記が簡単な構成図です。
SPAアプリケーションをS3・CloudFrontで配信しています。

image.png
CloudFrontからレスポンスを返す際にCloudFunctionsでセキュリティヘッダーを付与して返すようにしていました。

function handler(event) {
    var response = event.response;
    var headers = response.headers;

    // Set HTTP security headers
    // Since JavaScript doesn't allow for hyphens in variable names, we use the dict["key"] notation 
    headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubdomains; preload'}; 
    headers['content-security-policy'] = { value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; frame-ancestors 'none'"}; 
    headers['x-content-type-options'] = { value: 'nosniff'}; 
    headers['x-frame-options'] = {value: 'DENY'}; 
    headers['x-xss-protection'] = {value: '1; mode=block'};
    headers['referrer-policy'] = {value: 'same-origin'};
    
    // Return the response to viewers 
    return response;
}

amazon-cloudfront-functions

ルートパスでアクセスした際のレスポンス

$curl --verbose https://xxxxxxxx.cloudfront.net  
< HTTP/2 200
< content-type: text/html
< content-length: 990
< date: Sat, 09 Dec 2023 05:51:43 GMT
< last-modified: Sat, 09 Dec 2023 04:38:05 GMT
< etag: "5f0bac55f798ed9bd5de8f36e707607d"
< x-amz-server-side-encryption: AES256
< accept-ranges: bytes
< server: AmazonS3
< via: 1.1 e8888b4ce0d0032a21220ed1f337571c.cloudfront.net (CloudFront)
< age: 1147
< content-security-policy: default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; frame-ancestors 'none'
< referrer-policy: same-origin
< strict-transport-security: max-age=63072000; includeSubdomains; preload
< x-content-type-options: nosniff
< x-frame-options: DENY
< x-xss-protection: 1; mode=block
< x-cache: Hit from cloudfront
< x-amz-cf-pop: NRT20-P1
< x-amz-cf-id: gWhJacYoKccYL-ljoQDGPKIKIjjurwOlPLzWvE8G2aeH16-I_K4-nA==

ルートパス以外でアクセスした場合のレスポンス

$curl --verbose https://xxxxxxxx.cloudfront.net  
< HTTP/2 200
< content-type: text/html
< content-length: 990
< date: Sat, 09 Dec 2023 05:51:43 GMT
< last-modified: Sat, 09 Dec 2023 04:38:05 GMT
< etag: "5f0bac55f798ed9bd5de8f36e707607d"
< x-amz-server-side-encryption: AES256
< accept-ranges: bytes
< server: AmazonS3
< x-cache: Error from cloudfront
< via: 1.1 eb2281d04aecdff9b5230922e2a3cec6.cloudfront.net (CloudFront)
< x-amz-cf-pop: NRT20-P1
< x-amz-cf-id: vO0h02QNUeLreBwtw14gEQ1_Kgb9g0Fx8MP3G3QcXaVS_hD_DfH95g==
< age: 1673

上記のようにパスを追加してリクエストするとレスポンスにCloudFront Functionsで付与さえるはずのstrict-transport-securityなどのレスポンスヘッダがありません。

原因

SPAをCloudFront・S3で配信する時にオリジンからのレスポンスで400系のエラーが返されると、CloudFront Functionsが実行されないためにレスポンスヘッダーが付与されていませんでした。

image.png

公式ドキュメントにも記載があります。

CloudFront does not invoke edge functions for viewer response events when the origin returns HTTP status code 400 or higher.

なぜルートパスとルートパス以外で違いが出たのか

CloudFrontのルートパスにアクセスした際はindex.htmlを返す設定にしてあり、ルートパスの/でアクセスするとindex.htmlをステータスコード200で返すためCloudFront Functionsが実行されセキュリティヘッダーが付与されていました。

image.png

SPAアプリケーションをS3・CloudFrontで配信する場合は、/homeなどでアクセスしてもS3上にそのファイルがなく403や404でレスポンスされエラーページを設定しておかないと下記のようなエラーになってしまいます。
image.png

SPAアプリケーションをS3・CloudFrontから配信する場合は、下記のようにS3(オリジン)から403や404が返された場合にS3の/index.htmlを返すようにエラーページを設定します。
image.png

解決策

結論としてはCloudFrontのレスポンスヘッダーポリシーを活用しましょう。
既に用意されているものを使用するか、自分でもポリシーを作成することができカスタマイズ可能です。
CloudFrontからのViewerへのレスポンスに常にレスポンスヘッダーポリシーで付与されるようになります。
image.png

以下はSecurityHeadersPolicyを使用した結果になります。

$curl --verbose https://xxxxxxxx.cloudfront.net  
< HTTP/2 200
< content-type: text/html
< content-length: 990
< date: Sat, 09 Dec 2023 05:51:43 GMT
< last-modified: Sat, 09 Dec 2023 04:38:05 GMT
< etag: "5f0bac55f798ed9bd5de8f36e707607d"
< x-amz-server-side-encryption: AES256
< accept-ranges: bytes
< server: AmazonS3
< x-cache: Hit from cloudfront
< via: 1.1 f76b4c0eb6c4658feb5d2183e218bcee.cloudfront.net (CloudFront)
< x-amz-cf-pop: NRT20-P1
< x-amz-cf-id: L9ECsNOCgMpw03bFLVcQPcgYK2QipcOvqF59gKGVoVE1ebt0pS28xg==
< age: 2666
< x-xss-protection: 1; mode=block
< x-frame-options: SAMEORIGIN
< referrer-policy: strict-origin-when-cross-origin
< x-content-type-options: nosniff
< strict-transport-security: max-age=31536000

image.png

まとめ

CloudFrontのレスポンスヘッダーポリシーは2021年のアップデートで使用できるようになっています。
アップデート情報をしっかりと追えてなく解決までにやや時間がかかってしまったことへの自戒の念をこめて書かせていただきあました。
やはり情報を追って日々知識をアップデートして引き出しを増やしておくのは、解決までのスピードを早めていく上でとても大事だなと思いました。
re:inventでの情報もしっかりとキャッチアップしていきます!

15
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
0