cloudfront のオリジンに指定したs3に空ファイルがいると悪さをする

これは [ユニークビジョン株式会社 Advent Calendar 2021](https://qiita.com/advent-calendar/2021/uniquevision) の記事です。

# はじめに
以前書いた以下の記事に追記と称して自分の twitter だけ張り付けた事象をちゃんと記事にしてみた。
[s3からフォルダのようなkeyが帰ってくる。](https://qiita.com/Takayuki_Nakano/items/716362baf8ffe31d632f)
cloudfront のオリジンにしているときに s3に空ファイルがあると妙な現象が起こる。

空ファイルなんて作るやつが馬鹿なのよ。という方も前記事を見ていただけるとなぜ空ファイルが生えるかわかります。

# 現象
悪さといっても具体的に何なのか。
まず最初に現象を見るのが早いでしょう。

s3 をアクセス先とした cloudfront を作成してブラウザでアクセスします。
遷移先にブラウザが解釈可能なオブジェクトが存在すればブラウザはその内容を表示してくれるはずです。

ちょっとこの後のタネが割れるのですが以下のようなパスにアクセスします。
(URLはイメージです。distribution ID は伏せているのでアクセスしても何もありません。)

`https://XXXXXXXXXXX.cloudfront.net/test2/`

するとこうなります。

![スクリーンショット 2021-12-02 175656.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/732fce8c-c928-4063-b633-b085c45071bb.jpeg)

なんかダウンロードしました。
空ファイルのようです。

# 実験
## s3 の用意
以下の通り s3 を用意します

```
test-bucket
├── test1
│   └── index.html
└── test2
    └── index.html
```

ディレクトリを2つ作成し、それぞれに index.html を配置しました。
AWS s3 コンソール上での見かけは同じですが、作成手順が異なります。

test1 の方は index.html の入った test1 というフォルダをアップロードしました。
一方 test2 はコンソールから test2 というフォルダを作成し、index.html だけをその中にアップロードしました。

## cloudfront の作成
先の test-bucket をオリジンに設定した cloudfront を作成します。

オリジンアクセスアイデンティティとかなんとか s3 オブジェクトにアクセスできるようにする設定は適宜行いましょう。

それだけ。


## index.html にアクセス
それぞれの index.html にアクセスしてみます。

1. `https://XXXXXXXXXXX.cloudfront.net/test1/index.html`
2. `https://XXXXXXXXXXX.cloudfront.net/test2/index.html`

![スクリーンショット 2021-12-02 182709.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/49db4c19-4f01-c193-2b95-a49e3ba3474b.jpeg)

それぞれアクセスできました。

## index.html を削る
それぞれ以下の URL にアクセスしてみます。

1. `https://XXXXXXXXXXX.cloudfront.net/test1/`
2. `https://XXXXXXXXXXX.cloudfront.net/test2/`

結果は以下の通り、挙動が異なりました。

![スクリーンショット 2021-12-02 185015.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/5b0b2de6-60b0-d5d4-6ccb-c84535873a3f.jpeg)

# 何が起こっているのか
## s3 について簡単におさらい
詳細は概要の前記事で

### 空ファイル
フォルダの作成を使うと空のオブジェクトが生成されます

つまり先ほど挙げたこのAWSコンソールでの見かけのディレクトリ構造は誤りであり、

```:誤
test-bucket
├── test1
│   └── index.html
└── test2
    └── index.html
```

以下が本来の姿と考えられます。

```:正
test-bucket
├── test1
│   └── index.html
└── test2
    ├── (空ファイル)
    └── index.html
```

### フォルダはない
s3 は単純な Key-Value ストアであり、本来フォルダというものはありません。

つまり上記のディレクトリ構造すら正確には誤りであり、s3 の真に正しい姿は以下の通りになります。


```
<test-bucket>
 test1/index.html
 test2/
 test2/index.html
```

実際、list_objects などで確認するとそのような返答が来ます。

## cloudfront が実際にアクセスしているオブジェクト
つまり、`https://XXXXXXXXXXX.cloudfront.net/test1/index.html`にアクセスしたときには、

`https://XXXXXXXXXXX.cloudfront.net/test1/` にある `index.html` ではなく、
`https://XXXXXXXXXXX.cloudfront.net/` = `<test-bucket>` にある `test1/index.html` を呼び出していると考えるべきでしょう。

よって、`https://XXXXXXXXXXX.cloudfront.net/test2/` にアクセスしたときのみ存在する `<test-bucket>` の `test2/` という空ファイルをブラウザは解釈できずダウンロードしてしまうということになっているのでしょう。


ちなみに、そのような理由で空ファイルが降ってくるので
今回の場合末尾にスラッシュのない以下のような URL

1. `https://XXXXXXXXXXX.cloudfront.net/test1`
2. `https://XXXXXXXXXXX.cloudfront.net/test2`

はシンプルに access denied になります。


# どんな時に問題になるのか
単純に s3 の訳の分からんエラー画面が出るのも驚きですが、突如謎の空ファイルが落とされたら当然ユーザーはもっとびっくりするでしょう。

さらにあなたが cloudfront のエラーページの設定で s3 からくる 403 エラーに適切に対処している場合は大きな問題になります。

空ファイルとはいえファイルはファイル。
オブジェクトが存在するので s3 は 200 で返答してきますのでエラーページの設定に引っかかりません。

せっかくエラーに対処していたのにエラーも起こさずユーザーに謎のファイルを送り付ける姿はかなり不快感を煽るでしょう。

コンテンツとして公開する s3 にはコンソールからフォルダを作らないことをお勧めします。