CloudFrontではデフォルトでOriginヘッダ自体の転送をしている話〜Video.jsで発生するCORSエラーのトラブルを例にして
はじめに
清水です。JavaScript製の動画プレイヤーであるVideo.jsなどCORS設定が必要な環境下でCloudFrontを使用する場合、オリジン側でCORS設定をしていても、CloudFront側でOriginヘッダのオリジンへの転送設定を忘れてしまうと思わぬトラブルにつながる場合があります。具体的に私が経験したのは、ライブ動画配信をVideo.js(videojs-http-streaming)で行った場合、テスト配信では問題がなかったのに一般公開した本番配信では一部のブラウザでCORSに起因する再生エラーが発生してしまう、というものです。
原因としては、一般公開した本番配信ではリクエスト時にOrginヘッダを付与しない環境が混在、運悪くこのレスポンスがキャッシュされてしまうとOriginヘッダを付与してリクエストしている環境でAccess-Control-Allow-Originヘッダが付与されたレスポンスを受けられない、ということになります。リクエスト時にOriginヘッダを付与する環境のみであれば、CloudFront側でのOriginヘッダ転送設定を忘れてしまっていてもAccess-Control-Allow-Originヘッダが付与されたレスポンスになるためこの事象に気が付きにくいです。
本エントリでは個人的に同じ失敗を繰り返さないよう、CloudFrontがデフォルト、つまりOriginヘッダをオリジンに転送するよう設定していなくてもOriginヘッダ自体を転送することを確認、また発生したトラブル内容についてもまとめておきたいと思います。
なお、前提とするCloudFrontのCache Behavior Settingsはlegacy cache settingsとしています。2020年7月にアップデートのあったキャッシュポリシー、オリジンリクエストポリシーについては考慮していません。
CloudFrontのOriginヘッダ転送の挙動の確認
まずはCloudFrontのOriginヘッダ転送まわりの挙動を確認しておきましょう。CloudFrontでは2014年にCORSサポートとして、Originヘッダの転送がサポートされました。
- Amazon CloudFront Adds Device Detection, Geo Targeting, Host Header Forwarding, CORS Support, and more!
- [新機能] Amazon CloudFrontがCORSに対応しました | Developers.IO
この前提として、Amazon S3ではCORS機能が仕様通りの実装であるため、Originヘッダが存在しない場合はCORSで必要となるAccess-Control-Allow-Originヘッダを返しません。(詳細についてはこちらのブログエントリもご参照ください。) また同様にAWSのメディア向けに最適化されたストレージサービスであるAWS Elemental MediaStoreについても、仕様通りの実装でOriginヘッダが存在しない場合にCORSで必要となるAccess-Control-Allow-Originヘッダを返しません。
またS3、MediaStoreともにAccess-Control-Allow-Originヘッダを返すか否かはCORS設定として実施しておく必要があります。(本エントリでは、これらオリジンとなるS3、MediaStoreではCORS設定をきちんとしている状況を扱います。)
- CORS(Cross-Origin Resource Sharing)によるクロスドメイン通信の傾向と対策 | Developers.IO
- AWS Elemental MediaStoreでCORSを設定してみた | Developers.IO
Access-Control-Allow-Originヘッダをクライアント側に返すため、CloudFrontディストリビューションのCache Behavior SettingsでOriginヘッダをオリジンに転送するように設定する、というのがCORS対応で必須の設定です。 *1 *2
では実際のこの場合、並びにデフォルト(legacy cache settingsでCache Based on Selected Request HeadersがNoneの場合)とで挙動を確認してみます。
オリジンサーバはAmazon Linux上でApache + PHPを稼働させ、以下のPHPコードにアクセス、リクエスト時のヘッダ情報を表示します。
<?php foreach (getallheaders() as $name => $value) { echo "$name: $value\n"; } ?>
まずはOriginヘッダを転送するよう設定した場合、この場合は当然、オリジンで受け取るリクエストヘッダにOriginヘッダは含まれていますし、Originヘッダごとにキャッシュを持つこととなります。
% curl -H"Origin: https://example1.com" https://d1234567890.cloudfront.net/header_whitelist_origin/ Host: ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com User-Agent: Amazon CloudFront X-Amz-Cf-Id: T3PoHABz-IzbLJMOpVdgz6jKQxLIerL3R6Q0LbPxWY89R7agcx-rQg== Connection: Keep-Alive X-Forwarded-For: XX.XX.XX.XX Via: 2.0 e547c32d3950bb9fc00d08713c96bea4.cloudfront.net (CloudFront) origin: https://example1.com
% curl -H"Origin: https://example2.com" https://d1234567890.cloudfront.net/header_whitelist_origin/ Host: ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com User-Agent: Amazon CloudFront X-Amz-Cf-Id: Tx_CHwONE49dGCTBrXE39yEa29nbESsh0qljenenbJbENijSBXwLeQ== Connection: Keep-Alive X-Forwarded-For: XX.XX.XX.XX Via: 2.0 ec7e029564542f4eb6196ab046d31627.cloudfront.net (CloudFront) origin: https://example2.com
続いて、Originヘッダを転送するよう設定していない場合、つまりデフォルト状態での確認です。実はこの状態でも、オリジンサーバへリクエスト時にOriginヘッダは転送しています。
% curl -H"Origin: https://example1.com" https://d1234567890.cloudfront.net/ Host: ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com User-Agent: Amazon CloudFront X-Amz-Cf-Id: LBCWUonbLXmDLR6RBz4ZyFubtj_cMFIpaqE4Ea5tWULEQxNx44WJhw== Connection: Keep-Alive X-Forwarded-For: XX.XX.XX.XX Via: 2.0 2c0da8c5f883b1712644227b84998e75.cloudfront.net (CloudFront) origin: https://example1.com
Originヘッダを転送するよう設定した場合と異なる点としては、CloudFront側でキャッシュを使用するか否かの判断にOriginヘッダの情報を考慮しません。Originヘッダを転送しないように設定している場合は、後続のリクエストのOriginヘッダの内容が異なっても、キャッシュを利用する、となります。
% curl -H"Origin: https://example2.com" https://d1234567890.cloudfront.net/ Host: ec2-XX-XX-XX-XX.ap-northeast-1.compute.amazonaws.com User-Agent: Amazon CloudFront X-Amz-Cf-Id: LBCWUonbLXmDLR6RBz4ZyFubtj_cMFIpaqE4Ea5tWULEQxNx44WJhw== Connection: Keep-Alive X-Forwarded-For: XX.XX.XX.XX Via: 2.0 2c0da8c5f883b1712644227b84998e75.cloudfront.net (CloudFront) origin: https://example1.com
つまり、CloudFrontでOriginヘッダをオリジンに転送する設定を忘れてしまっていても、Originヘッダ自体はCloudFrontからオリジンへのリクエスト時に付与されている状況であり、CloudFrontでキャッシュ利用の判断にOriginヘッダを使わないだけの状況、となります。
CORSまわりで陥りやすいトラブル〜Video.jsを例にして
CloudFrontで、デフォルト状態でOriginヘッダがオリジンに転送自体はされていることが確認できました。続いて、この事象にうっかりしていたり、Originヘッダの転送をうっかりしていると遭遇してしまう可能性があるトラブルについてです。
JavaScript製の動画プレイヤーであるVideo.jsのvideojs-http-streaming(VHS)を使ってHLSでの動画再生(ライブ配信)をする場合を考えます。配信サーバ(Video.jsからアクセスする先)はCloudFrontで、そのオリジンはAWS Elemental MediaStoreという構成です。MediaStore(つまりオリジン側)ではCORSを設定しており、適切なOriginヘッダが存在していれば、CORSに必要なAccess-Control-Allow-Originヘッダが返ります。
この状態で、CloudFrontではOriginヘッダの転送設定を忘れてしまった、というケースを考えます。
まずはCloudFrontのキャッシュ無効化を行った状態で、Mac版のSafariから再生をしてみます。
問題なく再生できます(動画部分は真っ暗なのは映像を映していないため)。開発者ツールでリクエスト内容についても確認しておきましょう。リクエスト時にはOriginヘッダは付与されていません。そのため、MediaStore側のレスポンスにも、Access-Control-Allow-Originヘッダは含まれません。これでエラーが発生せず再生できているのは、Mac版のSafariではHLS再生にネイティブ対応しており(URLにm3u8のアドレスを入力すると再生が始まる)、JavaScriptを使った再生ではないためではないか、と推測しています。
続いてこちらもCloudFrontのキャッシュ無効化を行った状態で、MacのChromeから再生しています。
こちらも問題なく再生できています。開発者ツールでhls.m3u8のリクエスト、レスポンスヘッダを確認してみるとリクエスト時にOriginヘッダが付与されており、レスポンスヘッダではAccess-Control-Allow-Originヘッダが含まれています。
さて、これがCloudFrontでOriginヘッダをオリジンに転送しない状況、つまりOriginヘッダの内容でキャッシュを使い分けない状況下でどんな具合になるのか考えてみます。
まずChromeのようにOriginヘッダが付与されているリクエストでアクセスがあったあと、SafariのようにOriginヘッダが付与されていないリクエストがある、というパターンの場合です。この場合は初回アクセス時にAccess-Control-Allow-Originヘッダを含むレスポンスが返され、CloudFrontにキャッシュされます。後続のリクエストでもこのキャッシュが使われ、レスポンスにはAccess-Control-Allow-Originヘッダが含まれる、という状況になりますね。これだとCORSでの再生エラーなどは発生しない状況となります。
続いてSafariのようにOriginヘッダが付与されていないリクエストのあとに、ChromeのようにOriginヘッダが付与されているリクエストがあった場合です。この場合は初回アクセス時のレスポンスにAccess-Control-Allow-Originヘッダは含まれません。後続のリクエストもこのレスポンスを使用するため、ChromeなどCORS対応でAccess-Control-Allow-Originヘッダが本来必要になる環境でもAccess-Control-Allow-Originヘッダなしのレスポンスとなり、結果、CORSエラーが発生します。
このCORSエラーが発生する状況を具体的に確認してみした。hls.m3u8ファイルはmax-age=3という環境ですが、CloudFrontでキャッシュの無効化を行った状況で、Safariでの再生開始の直後にChromeでも再生を開始する、というケースです。
Safariでは問題なく再生できます。
その直後に再生を開始しようとしたChromeではキャッシュヒットとなり、そしてCORSエラーが発生して再生不可となります。
この現象はCloudFrontでのCORS設定、Originヘッダのオリジンへの転送を設定しておけばCORSエラーは回避できます。ただトラブルになりやすい点として、設定を忘れてしまっていても、ChromeなどOriginヘッダをリクエスト時に転送する環境だけでの再生であればCORSエラーは発生しません。SafariのようなOriginヘッダをリクエスト時に転送しない環境が入り、かつその環境が先にリクエストを行いキャッシュされる、となって初めてCORSエラーが発生する環境となります。
まとめ
Amazon CloudFrontでデフォルトでもOriginヘッダが転送されることを確認しつつ、この設定を忘れてしまった場合に起こりがちなトラブルについてもまとめてみました。お恥ずかしながら、個人的にも昔々、CloudFrontのOriginヘッダ転送設定を忘れてしまい、しかしChromeを使ったテスト配信では問題がなく、配信本番になってChromeでエラーが出ている、という事象を経験したことがあります。動画プレイヤーなどJavaScript側の仕様を把握しつつ、きちんとCORS設定をオリジンサーバ、そしてCloudFrontに実施することが重要です。そして設定を忘れてしまった場合にこのように気が付きにくいトラブルにつながってしまうため、設定実施の確認は入念に行いましょう。
-
CloudFront の「No 'Access-Control-Allow-Origin' header is present」エラーを解決する
-
CloudFront と AWS Media Services によるライブストリーミングビデオの配信 - Amazon CloudFront
脚注
- 「必須」と言っておきながら、この設定がなくても動作してしまう場合がある、というのが本エントリで扱うトラブルですが。 ↩
- 要件によっては他のヘッダも転送します。CloudFront の「No 'Access-Control-Allow-Origin' header is present」エラーを解決する ↩