【AWS】S3バケットへシークレットキーを渡さずにファイルをアップロードしてもらう方法

# こんな時どうする？

### ケース1
:::note info
自分が管理するS3バケットに対して、他の人にファイルをアップロードしてもらいたい
:::
- 相手からファイルをもらって自分でアップロードする？
- その人のために新しくアクセスキーとシークレットキーを発行する？

### ケース2
:::note info
SPAなどでフロントエンド側からS3バケットへファイルを直接アップロードしたい
:::
- フロントエンドからバックエンドへ一旦ファイルをアップロードして、サーバーで受け取ったファイルをS3へアップロードする？

サーバーサイドのバリデーションが必要な場合は有効な方法ですが、サーバーに負荷がかかってしまいます。

---

どちらのケースでも他の人やフロントエンド側にシークレットキーを渡さずにアップロードしたいものです。


# 注意
:::note warn
この記事で紹介している方法は、署名バージョン2です。
2014年1月30日以降の新しいリージョンでは署名バージョン4のみがサポートされるため、それらのリージョンへのリクエストは署名バージョン4で行う必要があります。
[Authenticating Requests (AWS Signature Version 4)](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html)
:::


# やり方

:::note info
S3の「署名付きURL」という仕組みを使います。
:::

**ファイルアップロード用の「署名付きURL」を作るにはプログラムが必要です。**
（表示用の署名付きURLであればAWSコンソールから取得することができます）

### 流れ
1. アクセスキーとシークレットキーを使って署名付きURLを生成します
2. 相手側に生成した署名付きURLを送ります
3. 署名付きURLに向けてファイルをアップロードします

:::note info
署名付きURLを作る時にはシークレットキーが必要ですが、作ったURLを使う時はシークレットキーは必要ありません。
:::
（SPAのケースだと「署名付きURL」の生成はサーバーサイドで行う必要があります）

### 準備
- AWS CLI のインストールと設定が済んでいること
- node のインストールと設定が済んでいること
- S3バケットを作成する
- S3バケットにアクセスできるユーザーを作る
- ユーザーのアクセスキー・シークレットキーを作る
- AWS CLIにユーザーのアクセスキー・シークレットキーを設定する

この辺りの詳細は割愛します。

### 署名付きURLの生成

パッケージインストール

```shell:shell
npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
```

サンプルプログラム

```js:generatePresignedUrl.js
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');

// 署名付きURLを生成する関数
const generatePresignedUrl = async (region, bucketName, objectKey, expires) => {
    const client = new S3Client({ region: region });
    const params = { Bucket: bucketName, Key: objectKey };

    try {
        const url = await getSignedUrl(client, new PutObjectCommand(params), { expiresIn: expires });
        console.log("署名付きURL:", url);
    } catch (error) {
        console.error("エラーが発生しました:", error);
    }
};

// コマンドライン引数の処理
const parseArgs = () => {
    const args = process.argv.slice(2);
    if (args.length < 4) {
        console.error("使用法: node generatePresignedUrl.js <リージョン> <バケット名> <オブジェクトキー> <署名付きURLの有効期限>");
        process.exit(1);
    }
    return {
        region: args[0],
        bucketName: args[1],
        objectKey: args[2],
        expires: args[3]
    };
};

// メイン関数
(async () => {
    const { region, bucketName, objectKey, expires } = parseArgs();
    await generatePresignedUrl(region, bucketName, objectKey, expires);
})();
```

サンプルプログラム実行して「署名付きURL」生成
```shell:shell
node generatePresignedUrl.js リージョン バケット名 ファイル名 有効期限秒数
```


### ここから相手側で行う処理です
署名付きURLを相手側に伝えます
（あるいはサーバーサイドからフロントエンドへURLを送ります）

ファイルをアップロードする
次の例はcurlコマンドを使っていますが他のツールでも構いません。プログラムでアップロードすることも難しくないでしょう。

```shell:shell
curl -X PUT "署名付きURLを貼り付ける" \
-H "Content-Type: image/jpeg" \
-H "x-amz-content-sha256: UNSIGNED-PAYLOAD" \
--data-binary "@ファイル名"
```

**注意**
- 「Content-Type: image/jpeg」はアップロードするファイルに応じて変更してください

- ファイル名の前にある＠は必要です。curlコマンドはファイル名の前に@を付けると「Content-Length」を自動で算出してヘッダーに差し込んでくれます


### シークレットキーを渡さずにファイルのアップロードができました

## 署名バージョン4
今回は「署名バージョン2」についてのサンプルでしたが、今機会があれば「署名バージョン4」についてもお伝えできればと思います。

# 参考

[署名付き URL を使用したオブジェクトのアップロード](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/PresignedUrlUploadObject.html)

[Create a presigned URL for Amazon S3 using an AWS SDK](https://docs.aws.amazon.com/AmazonS3/latest/userguide/example_s3_Scenario_PresignedUrl_section.html)