API Gateway を使って S3 へ直接ファイルをアップロードする

# はじめに
s3 へ外部からファイルをアップロードする口を作りたい。

でも put 用の presigned url を発行するは認証情報を保持する何らかのアプリケーションサーバーを用意しなければならない。
HTTPのAuthorizationフィールドにシークレットアクセスキーを使用して計算した認証情報をセットして S3 の REST API を叩く方法もあるらしいがこれも認証情報の計算自体はクライアントでできないことは変わりない。
そして今はサーバーとか lambda とかを用意することすら面倒くさい。

なので何とか直接 S3 へファイルを PUT できるようにしよう。
そういう話。

まぁ、この記事の参照情報を見れば公式の手引きがあるが、AWSのこういう資料は項目名が古いままだったりするし、現在の情報を反映させるという事でひとつ。

# 1. IAM 作成
API で Amazon S3 のアクションを呼び出すための IAM アクセス許可を設定する

## ロールの作成
まずは API Gateway へ設定する用の IAM ポリシーを作成します。

- AWS IAM > ロールで「ロールを作成」
- AWSのサービス > API Gateway を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/b704fe3c-ce84-b11a-93c2-c98016660c8e.png)

- そのまま「次へ」を選択
API Gateway が cloud watch logs にログを書き込むためのポリシーが表示されているが、s3 用のアクセス許可は後で追加する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/21264c9e-638e-ef70-a16b-919371587eaf.png)


- 名前を付けて「ロールを作成」
名前と説明の入力欄、ステップ1, 2 の確認、タグの設定があるので名前と説明、タグを任意に設定して作成する。


## ポリシーをロールにアタッチ
先ほどのロールに s3 への put object 権限を付与します

- IAM > ロールから先ほど作成したロールを探して選択
- 「許可を追加」から「インラインポリシー作成」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/0ddbc11b-9e8e-f2bb-71d4-771fc623f80c.png)
- ポリシーを作成
ビジュアルポリシーエディタで作成してもいいですが、今回は json を用意しました。
特定のバケット全体への put 権限のみを持たせます。
また、バケットは既にある想定です。プライぺートアクセスはブロックしていて構いません。

JSON ポリシーエディタに入力して「次へ」を選択します。

```json
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": "s3:PutObject",
			"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
		}
	]
}
```

最後に、ポリシー名を入力して「ポリシーの作成」をします。

これでロールにポリシーがアタッチされました。

# 2. API Gateway 作成
次に、S3 へと向いた API Gateway を作成します。

## リソース作成
- API Gateway コンソールで「APIの作成」を選択します。
- API タイプは REST API を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/b10b9dd0-5079-1fc3-dac8-fb3f9f3635ef.png)

- API 名を入力して作成
その他項目は理由が無ければそのままで良い

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/d6f66c46-06e6-25f5-aa28-e6b80af173c2.png)

- リソースを追加する
リソースの / を選択しアクションから「リソースの作成」を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/d65bb167-2a50-bd61-c302-3226367b37ae.png)

今回はアップロードするファイルの名称をパスパラメーターで与えようと思うので、案内に従いリソースパスは {} で囲んでおく。

これで作成。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/2d140a26-9633-e560-f24b-4bee960f92d1.png)


## メソッド作成


- PUT メソッドを作成
作成した /{object} リソースで PUT メソッドを選択して確定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/cf009e4e-bbb9-cab2-ddfa-a2b1fe2a523d.png)

以下のように設定する
- 統合タイプ: AWS サービス
- AWS リージョン: S3 バケットのリージョン
- AWS サービス: S3
- AWS サブドメイン: 空欄
- HTTP メソッド: PUT
- アクションの種類: パスの上書き
- パス上書き: YOUR_BUCKET_NAME/{object}
- 実行ロール: 先ほど作成した IAM ロールの Arn
- コンテンツの処理: パススルー

参照に貼ったチュートリアルや re:Post ではバケット名もパスパラメーターから得ているが、今回はバケットは固定で作っているのでバケット名を直接書く。

パスパラメーターの実際のマッピングはこの後やります。

これで保存

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/7c622b1c-d0ba-4262-3658-410292343b2e.png)

## パラメーターマッピング
- 作成した PUT メソッドの統合リクエストを選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/8e6a2597-53ac-1ab7-740d-c36c186fa6d5.png)


- パスパラメータを設定
同じ値にしてしまったのでわかりにくいですが、左がパス上書きの値と、右がリソースのパスの値とそれぞれ対応します。
マッピング元の方には `method.request.path.**` と設定しましょう。

チェックマークを押下して確定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/3570aeab-ec68-9542-4d17-3aa321ff1cf7.png)

## バイナリメディアタイプの設定
- サイドバーからリソースの設定を選択します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/eeb7595a-099a-cf37-9096-b8527a6fb32d.png)

- 最下部、バイナリメディアタイプに */* と入力して設定の保存

制限を設けたい場合は image/jpeg など、任意の mime type を設定してください。
今回はとりあえずすべて受け入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/afe942c3-51e2-026c-ecff-3f6f3ee15cb2.png)

## デプロイ
- リソースのアクションからデプロイを選択
- [新しいステージ]を選択して、適当にステージ名を付けてデプロイ

これで API 呼び出しのための URL ができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/0eb51b75-b065-698d-3ff7-8ecba5a408f3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/df3fcd7c-5486-7e4c-897c-350797eb7676.png)


# 3. アップロードしてみる
これで S3 へ PUT したファイルをパスする API が出来ました。
最後に呼び出してみて、完成とします。

sample.jpg というファイルを upload_test.jpeg としてアップロードします。
(バイナリメディアタイプを */* 以外で登録した場合は、Content-Type ヘッダーを付ける必要があるようです。)

```
curl -i --location --request PUT 'https://API_ID.execute-api.us-east-1.amazonaws.com/STAGE_NAME/upload_test.jpeg' --data-binary '@sample.jpg'
```

作成がうまくいっていれば、バケットにファイルがアップされているはずです。
ダウンロードしてみて、ちゃんと画像ファイルとして扱われているか確認してください。

# 以上
認証情報を取り扱うことなく s3 へファイルをアップロードできるようになりました。

その分 presigned url を違って、このURLが漏れだしてしまうと永続的に第3者からのアップロードを許してしまう事態になる可能性もあるため、API Gateway の API キーやリファラチェックなどの手段を講じておくことをお勧めします。

[API Gateway(REST API) のアクセス元を CloudFront のみに許可するには
](https://blog.grasswake.me/tech-posts/1646529783)


# 参照
1. [チュートリアル: API Gateway で REST API を Amazon S3 のプロキシとして作成する](https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/integrating-api-with-aws-services-s3.html#api-as-s3-proxy-create-resources)
1. [API ゲートウェイを使用して Amazon S3 に画像や PDF ファイルをアップロードするには、どうすればよいですか?](https://repost.aws/ja/knowledge-center/api-gateway-upload-image-s3)
1. [API Gateway(REST API) のアクセス元を CloudFront のみに許可するには
](https://blog.grasswake.me/tech-posts/1646529783)