署名付き URL で Amazon S3 にアップロードしてみた

# はじめに

Amazon Simple Storage Service (Amazon S3) は、高いスケーラビリティ・可用性・パフォーマンスなどを提供するオブジェクトストレージサービスです。Web サービスで画像データをアップロードしたいときに、S3 の署名付きURL を活用することが出来ます。

画像データをアップロードする際に、アプリケーションサーバーなどのバックエンドサーバーを経由する方法がありますが、CPU やネットワーク、メモリなどのリソース消費が気になります。アップロードの頻度が多く容量が大きいファイルだと、パフォーマンスへの影響が無視できません。そこで、ブラウザから直接 S3 にアップロードする方法を採用できます。

一方、セキュリティの観点で、S3 Bucket は Private にしておきたいです。Private のまま一時的にアップロードのための URL を生成する機能が、S3 署名付きURL です。

今回の記事では、ファイルをアップロードするための、S3 署名付きURL を確かめる記事になります。



# S3 Bucket を作成

まず、署名付きURL を使ってアップロードするための S3 Bucket を作成します。Create Bucket を押します

![image-20220223173702258.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/bedb30fa-9f06-8568-e3a1-11c65da0e7f0.png)



適当に Bucket 名を付けます

![image-20220223173731619.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/24897fe7-0413-7cf3-121b-aaf2a28d5a73.png)



それ以外はデフォルトのまま作成をします。S3 Bucket は Private Bucket で作成しています。

![image-20220223173817035.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/edd6511e-def9-04e9-281c-2ea24f6123a1.png)



Create を押します

![image-20220223173844945.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/9e6379d6-8b9c-00e4-887c-dcd6014740c3.png)



S3 Bucket が作成されました

![image-20220223173905126.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/9c728f26-b80d-cb32-8df6-a72139972bfb.png)



# Pythonで署名付きURLを生成

次に、ファイルをアップロードするための、署名つきURLを生成します。AWS SDK を使って生成していきます。(AWS CLI では、アップロード用の署名付きURLは生成できません)

Python プログラムを作成するための、作業用ディレクトリを作ります。

```
mkdir ~/python/s3-create-presign-upload
```



Python ファイルを作成します

```
touch ~/python/s3-create-presign-upload/s3-create-presign-upload.py
```



動作確認用ソースコードです。エラーハンドリングはしていないので、本番環境では気を付けましょう。

- Params に、アップロード先のバケット名と、オブジェクト名を入れて、署名付きURL を生成している
- Bucket を作成した直後だと、次の URL にあるとおりエラーになる可能性がある。そのため、署名付きURL の `s3.amazonaws.com` を `s3.ap-northeast-1.amazonaws.com` に置換している
  - https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-http-307-response/

```python
import boto3

s3_client = boto3.client('s3', region_name='ap-northeast-1')

url = s3_client.generate_presigned_url(
    ClientMethod='put_object',
    Params={'Bucket': 's3-presigned-test01', 'Key': 'this-is-filename.txt'},
    ExpiresIn=3600)

url = url.replace('s3.amazonaws.com', 's3.ap-northeast-1.amazonaws.com')

print(url)
```



Python プログラムの実行例です。S3 の URL に、Accesskey や署名や、有効期限がパラメータとして付与されています。

```
> python s3-create-presign-upload.py
https://s3-presigned-test01.s3.ap-northeast-1.amazonaws.com/this-is-filename.txt?AWSAccessKeyId=xxxxxxxxxxxxx&Signature=yyyyyyyyyyy&Expires=1645609221
```





# curl でテキストファイルをアップロード

それでは、curl コマンドで実際にアップロードをしていきましょう。

適当なテキストファイルを生成します

```
echo "Hi! I am Test File with S3 Presigned URL!" > test.txt
```



curl でアップロードします

```
url=$(python s3-create-presign-upload.py)

curl -X PUT \
--upload-file test.txt \
$url
```



S3 Bucket にアップロードされています。Download をして、中身を確認してみましょう

![image-20220223175950782.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/6cf7eb27-e2f6-1995-3d1f-9623220cec34.png)



正常にアップロードされています！

![image-20220223180022344.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/71a22e26-fc28-d199-cc44-6117240abc3d.png)



# 検証を通じてわかったこと

- 生成方法の違いについて
  - S3 事前署名付き URL は、オブジェクトのダウンロード用とアップロード用の2種類がある
  - ダウンロード用は、マネージメントコンソール・AWS CLI・AWS SDK から生成が可能
  - 一方、アップロード用は、AWS SDK のみ生成が可能
  - https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/PresignedUrlUploadObject.html
- アップロード用署名付き URL は、アップロード先のオブジェクト名を指定可能
  - アップロード元のファイル名は、アップロード先のオブジェクト名にならない
  - オブジェクト名は、署名付きURLを生成する側でコントロール出来るため、便利に活用できる



# 参考URL

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/PresignedUrlUploadObject.html