はじめに
Japan AWS Jr. Champions Advent Calendar 12日目の投稿です!
今回はSPA(シングルページアプリケーション)でのCloudFront FunctionsでCloudFrontからのレスポンスヘッダー付与で起きた問題、解決策について紹介します。
起きた問題
タイトル通りSPAアプリケーションをCloudFront・S3で配信してる時にCloudFront Functionsでレスポンスヘッダーを付与されていなかったという問題がありました。
さらにセキュリティヘッダーがルートパスアクセス時のみ付与されており、ルートパス以外はセキュリティヘッダーがレスポンスに含まれていない状況でした。
構成
下記が簡単な構成図です。
SPAアプリケーションをS3・CloudFrontで配信しています。
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;
}
ルートパスでアクセスした際のレスポンス
$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が実行されないためにレスポンスヘッダーが付与されていませんでした。
公式ドキュメントにも記載があります。
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が実行されセキュリティヘッダーが付与されていました。
SPAアプリケーションをS3・CloudFrontで配信する場合は、/home
などでアクセスしてもS3上にそのファイルがなく403や404でレスポンスされエラーページを設定しておかないと下記のようなエラーになってしまいます。
SPAアプリケーションをS3・CloudFrontから配信する場合は、下記のようにS3(オリジン)から403や404が返された場合にS3の/index.html
を返すようにエラーページを設定します。
解決策
結論としてはCloudFrontのレスポンスヘッダーポリシーを活用しましょう。
既に用意されているものを使用するか、自分でもポリシーを作成することができカスタマイズ可能です。
CloudFrontからのViewerへのレスポンスに常にレスポンスヘッダーポリシーで付与されるようになります。
以下は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
まとめ
CloudFrontのレスポンスヘッダーポリシーは2021年のアップデートで使用できるようになっています。
アップデート情報をしっかりと追えてなく解決までにやや時間がかかってしまったことへの自戒の念をこめて書かせていただきあました。
やはり情報を追って日々知識をアップデートして引き出しを増やしておくのは、解決までのスピードを早めていく上でとても大事だなと思いました。
re:inventでの情報もしっかりとキャッチアップしていきます!