✍️

S3 Presign を完全に理解する

に公開

presign upload とは

そもそも何のためのものか

ユーザのファイルを S3 にアップロードしたいが、アップロードの権限はアプリサーバ内にある。順当に考えれば、ブラウザ => アプリサーバ => S3 という経路にせざるを得ないが、 presign という手法を使うことで

  • filename, content-type をサーバに送信
  • サーバが presigned url を発行
  • presigned url にファイルをアップロード

という手順でアプリサーバを経由せずにブラウザからファイルをアップロードできる。

Content-Type の presign は重要

S3 の bucket を CNAME で自身のドメインから配信する場合、 Content-Type: text/html のアップロードを許すと自身のドメインで不正な Javascript を実行され、ログイン情報その他漏洩の危険が生じる。これを防ぐため、 Content-Type を presign に含めることが出来る。アプリサーバで Content-Type をチェックしてから presign するのが望ましい。

S3 の仕様上は Content-Type の presign は必須になっていない。(各言語のSDKでは必須にしているものもあるが、例えば Ruby の S3 SDK では必須ではない)

POST と PUT

(REST では POST が create(IDの発行)、 PUT が overwrite だが、 S3 upload の文脈では予め key (つまりID) を決めてアップロードするので REST 的な意味での create は存在しない)

presign POST と presign PUT があり、以下のように仕様が異なる。

presign POST

presign に関わる情報を multipart/form-data で送る。ファイルコンテンツはマルチパートの file として送る。
分割アップロードなど、 POST でしか出来ない機能がいくつかある。

presign PUT

サーバ側で URL 文字列を生成する。 presign の情報は全て url末尾の ? 以降 (いわゆる query string) に入る。ファイルコンテンツをそのままその URL へ PUT method で送れば良い。

Content-Type を presign した場合、PUT では sign 時に指定したのと同じものを request header に付与する必要がある。
POST の場合はヘッダではなく multipart の1つとして送る。

see also

https://zenn.dev/p0n/articles/3a6139cce9fa17

presign get

有効期限つきの get url を作れる

ブラウザに S3 の url を直接貼る場合、基本的にアクセス制御が出来ない。 (ユーザ認証に cognito とかを使ってログインユーザが AWS IAM と紐付くなら可能)
url を推測不能にすればそうそう漏洩しないが、アプリサーバがレスポンスを生成してからブラウザがそのURLからデータを引っ張るまでせいぜい10秒なので、「10秒だけ有効なURL」を作れると安心感が増す。
bucket の acl に public read を付与せずに、サーバで presign get url を作ることで有効期限つきのアクセスを実装できる。

有効期限は最大7日、とドキュメントにあるが、 presign に用いる secret token の寿命に等しい。(これが実質7日、ということっぽい?)

Discussion