【AWS】S3でバケットを公開せずHTTPSのみで静的サイトを公開する方法

## はじめに

あなたはクラウドサービスに不慣れなチームで、AWSでの構築の担当者になりました。  
あなた自身もあまりAWSに慣れ親しんでいるわけではないものの、新しく、また需要の伸びているクラウド技術を使った開発ができることにワクワクしていることでしょう。

クラウドつよつよなあなたはきっとこの時点でブラウザバックして問題ありません。  
興味があれば読み物としてお付き合いください。  
また、お急ぎのあなたは[手順までスキップしてください。](#手順)

さて、簡単なランディングページを公開したいという要望がチーム内に出てきました。  
他のプロジェクトでAWS経験のある上司がこう言います。

「S3で簡単に静的サイト公開できるよ」

これは真です。真であるだけに悪魔の導きとなる可能性を多分に秘めている発言だと思います。

これを聞いたあなたは早速調べてみることでしょう。"s3 静的サイト 公開"という感じでしょうか。
検索上位に以下のAWSの公式ガイドが出てきます。

[チュートリアル: Amazon S3 での静的ウェブサイトの設定](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/HostingWebsiteOnS3Setup.html)

素晴らしい！　いかにも目的を果たすことができそうです。(そして実際に果たせます)

意気揚々とチュートリアルを進め、「できました！」と上司に報告します。  
すると上司はこう言います。

「あれ、httpだけどhttpsにできないの？」  
「バケット公開になってるけど大丈夫？」  

確かに、と思ったあなたはさらに調べるでしょう。"s3 静的サイト https"、"s3 静的サイト バケット 公開しない"...こんな感じでしょうか。  
そして出てくる出てくる大量の記事、分かるような分からないような情報の数々、確実に過ぎていく時間...

何が正しいのかわからなくなって疲弊してきた頃、上司は言います。

「この前の指摘、まだ直らないの？」  

**「わっかんねえよ！全っ然簡単じゃねえよ！！」**　

そう叫びだしたくなってしまうことでしょう。やってくださいよ簡単なんでしょ？と当てこすりたくすらなってしまうかもしれません。

**大丈夫です。できます。**  
**httpsだけ使ってバケットを公開せず、簡単に静的サイトを公開する方法があります。**

## 前提

- この記事で解説すること
  - 全通信をHTTPSで完結させてバケットを公開せず静的サイトを公開する方法
  - S3とCloudFrontの設定内容
- この記事で解説しないこと
  - 独自ドメインの設定方法
  - 静的ウェブサイトに対する認証の設定方法
  - その他「この記事で解説すること」に該当しない事項

## 概要

前置きが長くなりました。構成としては比較的シンプルで、以下の形で実現できます。  
CloudFrontなしで実現する方法はないので頑張ってついてきてください。

![structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/c074cd69-d347-d02d-ee3b-ec0021cb53cd.png)


さて、パッと見はシンプルなのですが、CloudFrontとS3の間の通信がミソです。  
CloudFrontには `Origin Access Control`(以下、OAC) を、S3にはバケットポリシーを設定します。  
はい、少々辛抱してください。バケットポリシーはまだしも、OACって何だよって感じですね。  

非公開のS3バケットは何かしらの方法で認証および認可されたアクセスしか受け付けません。  
あなたがマネジメントコンソールでS3にアクセスできるのは、マネジメントコンソールへのログインで認証され、あなたが持っている権限でS3の情報を参照することを認可されているからです。  

同じように、CloudFrontが非公開のS3にアクセスすることについて、何らかの方法で認証および認可する必要があります。  
そのために必要なのがOACとバケットポリシーです。

まず、OACはS3にアクセスしているのがCloudFrontであると証明するために必要です。厳密には違いますが、S3アクセスに際してCloudFrontというユーザ名で認証しようとしているというイメージでだいたいあっています。  
次に、S3のバケットポリシーです。CloudFrontがバケット内のオブジェクトにアクセスすることを許可するために必要です。こちらは概念的には認可の役割です。

認証の失敗は「誰だテメー」で、認証後の認可の失敗は「誰かは分かった。だが断る」です。  
つまりOACがなければS3はCloudFrontからのアクセスだと識別できず、「誰だテメー」とNGを出します。  
OACがあってバケットポリシーがなければS3は「CloudFrontか。だが断る」とNGを出します。

よって、OACとバケットポリシーを作成する必要があります。

## 手順

### S3バケットを作成する

バケット名だけ任意のものを入力してあとはデフォルトでOKです。  

私は個人では料金が安めで距離的に比較的近いus-west-2を好んで使っていますが、この記事のハンズオンならどこでやっても1円未満で済むはずなので東京リージョンでも全く問題ないです。

![s3001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/8d2dde15-1091-8bf2-47c4-9f68354acb04.png)

---

ブロックパブリックアクセス設定も当然いじりません。  
そのまま下にスクロールして[バケットを作成]を押します。

![S3002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/e36b25f0-688c-095f-6f37-1bf430f8ed64.png)

---

非公開のバケットを作成できたことを確認します。

![S3003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/a0c0f7cd-0801-c6fb-93dc-b52d65557a34.png)

### OACを作成する

OACとは、S3に「誰だテメー」と言われないようにするために作成するものでしたね。  
もう少し具体的には、クライアントからのリクエストに対して、CloudFrontが「私が中継しました」と署名することでCloudFrontからのアクセスであることをS3に証明します。

リソースの作成自体は至って簡単です。  
マネジメントコンソールからCloudFrontのページに遷移します。  
左メニューに[オリジンアクセス]があるのでそれをクリックし、[コントロール設定を作成]を押します。

![CF001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/f9065702-002c-16f7-46eb-821c9d5e8cb3.png)

---

任意の名前だけ入力して、あとはデフォルトのまま[作成]を押します。
オリジンタイプがS3であることは目視で確認しておきます。

![CF002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/d7955ba4-f949-e2b7-bc5e-bfa0a6fe0d9a.png)

---

問題なく作成されたことを確認します。

![CF003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/39401086-b294-988c-b16b-86d591fa185c.png)

### CloudFrontディストリビューションを作成する

続いてCloudFrontのディストリビューションを作ります。  
ディストリビューションって何だよって感じですが、S3といえばバケット、みたいなノリでCloudFrontといえばディストリビューション、と捉えていただければだいたいあっています。  
つまるところCloudFrontのメインとなるリソースですね。

引き続きCloudFrontのページで、左メニューの[ディストリビューション]を選択して[ディストリビューションを作成]を押します。

![CF004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/5f4e6219-884b-8f5b-04c7-1791d50e7ede.png)

---

以下を設定します。

- オリジンドメイン: 先程作成したS3のバケット名を含むドメイン名
- オリジンアクセス: `Origin access control settings (recommended)`
- Origin access control: 先程作成したOACの名前

すぐ下のバケットポリシー欄に、「S3 バケットポリシーを更新する必要があります」と出ますが一旦スルーで大丈夫です。  
コンソールに書いている通り、ディストリビューションが作成されたらAWS側がコピペできるポリシーを提供してくれます。

![CF005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/dfa362b1-a728-af33-340b-8fa938b5d3ae.png)

---

せっかくなのでビューワープロトコルポリシーを `HTTPS only` にしましょう。  
ビューワーとはクライアント、つまりクライアントからのアクセスをHTTPSだけ許可にするということですね。  
これで「HTTPなんだけど？」という指摘をクリアして「HTTPSでしかアクセスできないようにしましたけど？」とカウンターできます。  
HTTPで来る人にも優しくしたい場合はひとつ上の `Redirect HTTP to HTTPS` を選んでもいいです。

![CF006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/dd178bbf-dc0d-cef4-4596-f452d6ff2c19.png)

---

WAFを設定するかだけ必須で入力する必要があるので、[セキュリティ保護を有効にしないでください]を選択します。ランディングページのような不特定多数のアクセスを許可する要件ならば当然設定した方がよいですが、この手順の説明としては範囲外とさせていただきます。  

![CF007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/49802f12-4dd0-8fb8-e5d3-ca3d32b1f3dd.png)

WAFの設定も含め、ログ設定など本来であれば指定した方が良い項目がいくつかありますが、この記事では割愛させていただきます。ご了承ください。  
後からディストリビューションの再作成が必要になるような設定はないのでその点はご安心ください。  

他の設定はデフォルト値のまま最下部までスクロールして[ディストリビューションを作成]を押します。

---

ディストリビューションの作成が完了し、以下のような画面が出ることを確認します。  

![CF008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/1c2218f4-0d80-5cac-7a47-ee003f9cb5c0.png)

黄色い帯の[ポリシーをコピー]をクリックすると、クリップボードに以下のような内容が入ることを確認します。

```txt
{
        "Version": "2008-10-17",
        "Id": "PolicyForCloudFrontPrivateContent",
        "Statement": [
            {
                "Sid": "AllowCloudFrontServicePrincipal",
                "Effect": "Allow",
                "Principal": {
                    "Service": "cloudfront.amazonaws.com"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::20231210tekitobucket--us-west-2/*",
                "Condition": {
                    "StringEquals": {
                      "AWS:SourceArn": "arn:aws:cloudfront::YOUR_ACCOUNT_ID:distribution/ENW7MC67VLORD"
                    }
                }
            }
        ]
      }
```

ここまで確認できたら、黄色い帯の[S3 バケットの権限に移動してポリシーを更新する]を押して、S3バケットのバケットポリシーの編集をしましょう。

### バケットポリシーを編集する

遷移先はS3バケットの[アクセス許可]タブです。バケットポリシーの欄があるので、[編集]を押します。

![S3004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/42e4b947-e871-b7dc-4190-f891a8433ed8.png)

---

ポリシー欄に先程クリップボードにコピーされた内容をそのままペーストして、[変更の保存]を押します。

![S3005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/21c715d1-3f6e-c4af-d503-32a2f08a0b4c.png)

保存するとバケットのアクセス許可タブに遷移するので、バケットポリシー欄に設定したポリシーが入っていることを確認します。

### インデックスページを作成する

作成する、なんていうほどのものでもないです。  
任意の内容を `index.html` としてバケットの直下に格納します。  
ここでは例として以下のものを使います。

```index.html
<!DOCTYPE html>
<html>
<head>
    <title>Secure S3 Page</title>
</head>
<body>
    <p>This is Private Bucket and Accessed with HTTPS!!</p>
</body>
</html>
```

![S3007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/d06ed09f-349d-6858-6e81-a17aa2d029db.png)

### 静的サイトにアクセスする

さて、いよいよです！  
払い出された `cloudfront.net` のドメイン名の `index.html` にアクセスしましょう。

私の場合は以下のURLです。`d1f1n1b7uyneir` の部分をご自分のものに変えてください。  
※記事公開時点では私のディストリビューションは削除しています。ご了承ください。
https://d1f1n1b7uyneir.cloudfront.net/index.html

![CF009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/e18f6d32-784c-3cd7-50c4-d086085bb004.png)

バケットは「パブリックアクセスをすべて ブロック」がオンになっています。

![S3006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199887/458dc888-6817-e5b3-2807-6a6c72d4bf30.png)

勝ちました🙌

### (任意)作成したリソースを削除する

この記事で作った程度のリソースなら残しておいたところで大して課金されることはないはずですが、後で使う予定がない場合は消しておきましょう。

## 理解しておきたいこと

### S3の静的ウェブサイトホスティングの罠

最大にして最凶の罠がS3の「静的ウェブサイトホスティング」機能です。
この機能を使うがゆえに、①httpでしかアクセスできない、②バケットを公開しなければならない　というリスクを抱えてしまいます。  
静的ウェブサイトホスティングの仕様として、HTTPしかサポートしていない、バケットの公開が必須というものがあるのです。これらは[公式](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/WebsiteEndpoints.html)にも記載がある仕様です。

①に関しては、CloudFrontを使うことでまだカバーできます。(ただし、CloudFront←→S3の通信はHTTPです)  
②に関してはどうしようもないです。

「はじめに」で引き合いに出したチュートリアルに、以下の文言があります。

```txt
警告

このステップを完了する前に「Amazon S3 ストレージへのパブリックアクセスのブロック」を読んで、
パブリックアクセスを許可することに伴うリスクを理解し、了承します。
パブリックアクセスブロック設定をオフにしてバケットをパブリックにすると、
インターネット上のだれでもバケットにアクセスできるようになります。
バケットへのすべてのパブリックアクセスをブロックすることをお勧めします。
```

厳しい言い方ですが、とりあえずS3の静的コンテンツをなんとかして公開したいというレベルの人が「パブリックアクセスを許可することに伴うリスク」を正確に理解するのは難しいでしょう。

もちろん、S3に置いたオブジェクトがインターネットに向けて公開されるというのが何か危なそう、ということは分かると思います。  
しかし、パブリックアクセスのブロックを構成する4つの設定がそれぞれどのような制御を意味していますか？　と問われたら、ある程度AWSに慣れた方でも改めてドキュメントを参照しなければ正確に回答するのは難しいのではないでしょうか。

余談ですが、OACは2022年に登場した比較的新しい機能で、静的ウェブサイトホスティングはそれよりもずっと前からある機能です。  
静的ウェブサイトホスティングの情報が圧倒的に多く出てくるのは、この時間差が大いに関係していることと思われます。  

### CloudFrontの料金

CloudFrontを別途入れることについて料金はどうなのかというツッコミが入るかもしれません。  
しかし、料金についてはあまり心配しなくてもよいでしょう。

CloudFrontは存在しているだけでコストがかかるタイプのリソースではありません。  
基本的にリクエスト量およびデータ転送量に対する従量課金です。  
なんなら月あたり1000万件のリクエストと1TBのアウトバウンドデータ転送量が無料です。

当然これを上回るトラフィックが想定されるならば見積もりは必要ですが、走り始めてちょっとのプロジェクトを想定したときに、いきなり大量のアクセスが来るかといえばそうはならない可能性の方が高いのではないでしょうか。

たとえ導入に難色を示されても、「数日様子見してコスト確認させてください！　そんなかかりませんから！」と説得しましょう。  
1週間経ち、1ヶ月も経てばCloudFrontに対するコスト感がチーム内に共有され、そんなにお金がかかるものではなさそうだと分かってもらえると思います。

## おわりに

S3で簡単に静的ウェブサイトを公開できるらしいーー。  
こんな情報が独り歩きした結果、セキュリティリスクを抱えたバケットが今もあちこちにあることと思われます。

CloudFrontとOACを使うことで、通信経路をHTTPSのみにしつつS3バケットは非公開にして、静的コンテンツを公開することができます。

HTTPだけど、バケット公開だけど、いいのだろうか...。そんな思いを悶々と抱える担当者の方はいらっしゃるのではないかと思います。

この記事がそんな方々のサポートになれていれば、とても嬉しく思います。

## 参考

- [CloudFrontからS3への新たなアクセス制御方法としてOrigin Access Control (OAC)が発表されました！](https://dev.classmethod.jp/articles/amazon-cloudfront-origin-access-control/)  
  Classmethodさんの記事で、本記事の設定手順をもっと分かりやすく書いています。  
  設定方法はこの記事を参照と書こうと思いましたが、良心がとがめてやめました。
- [Amazon CloudFront オリジンアクセスコントロール（OAC）のご紹介](https://aws.amazon.com/jp/blogs/news/amazon-cloudfront-introduces-origin-access-control-oac/)  
  公式の記事です。詳しく正確に書いているだけにやや取っつきづらいですが、一次ソースを求められたときに使いやすいと思います。