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
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