S3とCloudFrontでwebサイトを公開する際のバケット設定はどうすべきか?
公式のナレッジセンターの記事「CloudFront を使用して Amazon S3 でホストされた静的ウェブサイトを公開する」 で紹介されているwebサイト公開手法、 複数あるけど結局どれを採用すれば良いのか?という判断基準を整理したく、纏めてみました。
外部・viewer -> CloudFront -> S3 バケット(公開物はここに配置)
という通信経路を想定しています。
論点
- S3 × CloudFront という組み合わせでwebサイトを公開する場合の、バケットの作成方法の選択肢2通り
- webサイトホスティング
- エンドポイント = webサイトエンドポイント
- 非・webサイトホスティング
- エンドポイント = REST API エンドポイント
- 参考: webサイトエンドポイントとREST APIエンドポイントの主な違い
- webサイトホスティング
- 上記2通りのバケット作成方法のメリット・デメリットと、作成方法選択時の判断基準は何なのか?
- 機能要件的な観点=外部・viewerからサイトにアクセスするユーザー観点では、上記2通りでどんな違いが生じうるのか?
- 非機能要件的な観点では、上記2通りでどんな違いが生じうるのか?
- webサイト公開が目的でも「非・webサイトホスティング を選ぶべき場合」があるなら、それはどういう場合なのか?
先に結論まとめ
- 外部・viewerからサイトにアクセスするユーザー観点
- 一部 webサイトホスティングでのみ可能な機能 も存在しており、これらを利用したいかどうか?が判断材料になり得る
- 非機能要件的な観点
- 例えば構築するシステムや開発組織のセキュリティ対応の為、非・webサイトホスティング を選ばざるを得ないケース がある
- 例:
CloudFront -> S3
間の通信を、何らかの事情で HTTPS にしなければならない- webサイトホスティングのバケットへの通信を HTTPS にする事はできないので、非・webサイトホスティング一択となる
- 例: 何らかの事情でバケットにパブリックアクセスを許可する事が出来ない
- webサイトホスティングのバケットはパブリックアクセスの許可が前提となるので、非・webサイトホスティング一択となる
- 例:
- etc
- 例えば構築するシステムや開発組織のセキュリティ対応の為、非・webサイトホスティング を選ばざるを得ないケース がある
検証内容と前提条件
- 構築&検証用の環境のパターンは以下の通り
- S3(webサイトホスティング) × CloudFront
- S3(非・webサイトホスティング) × CloudFront
- ※ S3バケットのみ というパターンはこの記事では触れない
- http methodは参照系のみを想定
- つまり、POST, PUT, DELETE 等の更新系methodは想定しない
- CloudFrontでは、独自ドメインは利用しない
- ユーザーがアクセスするURLは
https://自動生成ドメイン文字列.cloudfront.net
- ユーザーがアクセスするURLは
外部・viewer -> CloudFront
間の通信は、(公式のサポートサイトでも触れられているが)当然 HTTPS を必須とするCloudFront -> S3バケット
間の通信を HTTP とするか HTTPS とするかは、バケットの制約に準拠する- webサイトエンドポイント = HTTP しか使えない
- 非・webサイトエンドポイント = バケットを S3オリジン として扱い、HTTPS を必須化
- 動作確認で使用するアプリ
- Vite & Vue 3 の雛形 にエラー画面用htmlを追加しただけのもの
- これをビルド → S3に展開する
検証環境の構成
検証環境構築にはterraformを利用しました。※動作確認アプリ&検証環境構築用terraformはこちらのリポジトリにupしておきました。
S3(webサイトホスティング) × CloudFront の設定
S3は下記の通りwebサイトホスティング前提の内容でbucket、public_access_block、bucket_policyを定義しておきます。
acl = "public-read"
設定website
を定義- ※定義はするものの、バケットの前段にCloudFrontを配置する構成だとバケット直アクセスは想定しなくなり、この設定が実際に利用される場面はなくなる
public_access_block
は クラスメソッドさんのこちらの記事を参考に、ACLに対して設定- バケットポリシーは
"aws:UserAgent" = "Amazon CloudFront"
に対するAllowを設定- 逆に「
"aws:UserAgent" = "Amazon CloudFront"
以外を Deny」する設定は行わない。これを設定すると、“Amazon CloudFront” 以外に該当する範囲全て(それこそ普段AWSコンソールにログインしているIAMユーザーなど)にも影響が波及し、作業に支障をきたす恐れがある為 - webサイトホスティング = パブリックアクセスを許可するバケットで
s3:ListBucket
Actionを許可するのはセキュリティ上のベストプラクティスでは無いと公式でも言及されており、s3:GetObject
のみを許可しておく
- 逆に「
server_side_encryption_configuration
でAES256
なサーバー側暗号化を有効にしておく- ※サーバーサイド暗号化したS3をwebサイトホスティングで公開するとアクセス時にエラーになるという言及を見掛けましたが、
Principal: "*"
に対してs3:GetObject
を許可するバケットポリシーを設定した上でhttp://該当バケットのwebサイトエンドポイント/存在するhtmlファイル
というURLでアクセスしても、手元ではエラーにはならず正常表示されました
- ※サーバーサイド暗号化したS3をwebサイトホスティングで公開するとアクセス時にエラーになるという言及を見掛けましたが、
- これら以外は任意で設定
- CORS設定、アクセスログ設定、etc
そして、このバケットの前段に配置するCloudFrontを定義します。
default_root_object
を定義してhttps://cloudfrontドメイン
へのアクセス時はindex.htmlを表示する- origin の
domain_name
はbucket_domain_name
でもbucket_regional_domain_name
でもなくwebsite_endpoint
を指定する。これによりhttps://cloudfrontドメイン/subdir/
のようなフォルダへのアクセスでもhttps://cloudfrontドメイン/subdir/index.html
というindexドキュメントの表示が可能となる - origin のconfigは
custom_origin_config
として定義し、origin_protocol_policy
は(httpのみが利用可能なwebサイトホスティングなので)"http-only"
を指定 custom_error_response
により、エラー時にS3が(デフォルトで)返してくるxmlエラーをhtmlに変換するようにしておく- これら以外は任意で設定
- cache behavior
- 証明書
- 地域制限設定
- アクセスログ設定
- etc
そして対象バケット(の直下)に以下を配置しておきます。
|
|
S3(非・webサイトホスティング) × CloudFront
S3バケットは、webサイトホスティングとは異なりパブリックアクセスは許可しない前提の内容で定義しておきます。
acl = "private"
設定website
を定義 しないpublic_access_block
は全てtrue- バケットポリシーは Origin Access Identityに対するAllowと、https以外(=http)を拒否するDenyを設定
- Allow側ポリシーで許可するAction, Resourceはwebサイトホスティング用と同様
server_side_encryption_configuration
でサーバー側暗号化を有効にしておく- webサイトホスティング用と同様
- これら以外は任意で設定(webサイトホスティング用と合わせておけばOK)
そして、CloudFrontをS3 Origin&Origin Access Identityの組み合わせで定義します。
- Origin Access Identity を定義
- origin のconfigは
s3_origin_config
として定義し、1 のorigin_access_identity
を指定 - これら以外は任意で設定(webサイトホスティング用と合わせておけばOK)
検証してみた
正常系 or 異常系でアクセス時の挙動が揃っているか?揃っていない場合はそれは何か?を検証します。
1. https://自動生成ドメイン文字列.cloudfront.net/存在するファイル
へのアクセス
これは(当然ですが)webサイト or 非・webサイト共に同じ結果が表示されます。
初回以降のアクセスではCloudFrontでのキャッシュが効いてhttp statusは304になります。
2. https://自動生成ドメイン文字列.cloudfront.net/
へのアクセス
webサイト or 非・webサイトに関わらず、ドメイン名のみのアクセス時はCloudFrontの default_root_object
設定が有効であることを確認したい、という意図でしたが、これも想定通りでした。
3. https://自動生成ドメイン文字列.cloudfront.net/subdir/存在するファイル
へのアクセス
これもwebサイト or 非・webサイト共に同じ結果が表示されます。フォルダ配下であっても存在するファイルのフルパスURLを指定すれば挙動に違いはありません。
4. https://自動生成ドメイン文字列.cloudfront.net/subdir/
へのアクセス
フォルダへのアクセスは、webサイトの場合のみ、フォルダ配下のindexドキュメント表示が機能します。これはCloudFrontのorigin設定で domain_name
にS3バケットの website_endpoint
プロパティを設定する事で実現できています。
逆に 非・webサイトではエラー(= 403)となり、webサイトの場合と挙動に差異が発生します。
5. https://自動生成ドメイン文字列.cloudfront.net/存在しないファイル名
でアクセス
存在しないファイルへのアクセスは、バケットポリシーで s3:ListBucket を許可していないと403エラーとなります。
デフォルトではその結果はhtmlではなくxmlで返ってくるので、CloudFrontの custom_error_response
設定でhtmlを返すよう設定してあります。
デフォルトの挙動とcustom_error_response
によるエラーのカスタマイズについては、webサイト or 非・webサイトに関わらず同様です。つまり 存在しないファイルへアクセスされた際のエラー処理も、webサイトホスティング or 非・webサイトホスティングで同じ機能を実現できる という事です。
補足: custom_error_response
の設定が無いとエラー表示はどうなるのか?
webサイトホスティング or 非・webサイトホスティングどちらのケースでも、custom_error_response
設定がない状態で存在しないページにアクセスした場合、s3:ListBucket バケットポリシーの有無でエラー表示の挙動が変わります
custom_error_response
設定がない、且つ、バケットポリシーで s3:ListBucket が許可されていない- 「S3がデフォルトで返す、AccessDenied を表すxml」が表示されます。http statusは 403
custom_error_response
設定がない、且つ、バケットポリシーで s3:ListBucket が許可されている- 「S3がデフォルトで返す、NoSuchKey を表すxml」が表示されます。http statusは 404
- つまり、エラーの内容から「ファイルが無い」という事に気付く事が出来る
- 「S3がデフォルトで返す、NoSuchKey を表すxml」が表示されます。http statusは 404
一方で、「webサイトホスティングで s3:ListBucket アクセスを許可する」事には注意が必要です。公式では、
パブリック s3:ListBucket アクセスを有効にすることは、セキュリティ上のベストプラクティスではありません。 パブリック s3:ListBucket アクセスを有効にすると、ユーザーはバケット内のすべてのオブジェクトを表示およびリスト化できます。 これにより、ユーザーがオブジェクトをダウンロードするアクセス許可を持っていない場合でも、オブジェクトのメタデータの詳細 (キーやサイズなど) がユーザーに公開されます
と言及されており、オブジェクトの存在を推測されるようなエラー表示を抑止したければ、webサイトホスティングに於いて s3:ListBucket を許可するのはプラクティスとしてはよろしくなく、避けた方が無難です。
※ちなみに、「custom_error_response
設定がある、且つ、バケットポリシーで s3:ListBucket が許可されている」という場合であれば、custom_error_response
で404エラーを独自エラーに変換(して詳細を隠蔽する)という手段も可能と言えば可能です。
補足: webサイトホスティングで http://バケットのエンドポイント/存在するファイル名
でバケットへの直アクセスを試みるとどうなるか?
このパターンのアクセスを試してみると、“S3がデフォルトで返す、403を表すHTML” が表示され、アクセス拒否自体は成功している事が分かります。
今回構築したwebサイトホスティング用のバケットでもカスタムエラードキュメントを設定していたものの、バケットポリシーでCloudFrontからのアクセスだけを許可していると、バケットへの直アクセス時はカスタムエラードキュメントへのアクセスも拒否される事になり、結果としてS3のデフォルトエラー画面が表示される という挙動になります。
改めて結論まとめ
- webサイトホスティング or 非・webサイトホスティングどちらを選んでも差が無い観点
- S3バケットへの直接アクセスを抑止し、CloudFrontからのみアクセス可能にする
- webサイトホスティング:User-AgentがCloudFrontの場合だけAllowするバケットポリシーを設定
- 非・webサイトホスティング: Origin Access Identityの活用
外部(viewer) -> CloudFront
間の通信で HTTPS を必須化- webサイトホスティング or 非・webサイトホスティングに関わらず、同じ手順で設定可能
- S3バケットへの直接アクセスを抑止し、CloudFrontからのみアクセス可能にする
- webサイトホスティングでのみ可能な機能 も存在しており、これらを利用したいかどうか?という観点
- 非・webサイトホスティング を 選ばざるを得ない 観点
CloudFront -> S3
間の通信を、何らかの事情で HTTPS にしなければならない- webサイトホスティングのバケットへの通信を HTTPS にする事はできないので、非・webサイトホスティング一択となる
- 「何らかの事情」とは、例えば組織のセキュリティポリシー等
- 何らかの事情でバケットにパブリックアクセスを許可する事が出来ない
- webサイトホスティングのバケットはパブリックアクセスの許可が前提となるので、非・webサイトホスティング一択となる
- publicに公開したくないコンテンツが含まれている
- (これは言わずもがなですが、一応書いておきました)
セキュリティを重視したい場合は非・webサイトエンドポイントの方が有利 と言えるので、「webサイトエンドポイント or 非・webサイトエンドポイントどちらを選ぶか決め手に欠けて、webサイトエンドポイントでしか実現できない機能を使いたいという訳でもない…」ようなケースであれば、非・webサイトエンドポイントを選択するのが無難かもしれません。
ブックマーク
- Amazon S3 + Amazon CloudFrontでWebサイトを構築する際にS3静的Webサイトホスティングを採用する理由 | DevelopersIO
- CloudFrontのオリジンサーバによる機能の違い | DevelopersIO
- CloudFormation で OAI を使った CloudFront + S3 の静的コンテンツ配信インフラを作る | DevelopersIO
- CloudFront×S3で403 Access Deniedが出るときに確認すべきこと - Qiita
- Production deploy of a Single Page App using S3, CloudFront, and CloudFormation | by Joe Crobak | Medium
- CloudFront に S3 bucket のサブディレクトリパスのコンテンツを参照させる - Qiita