boto3を使用したS3の署名付きURL生成メモ

## 概要
パブリックアクセスを無効化しているS3に保存しているオブジェクトに対して、一時的にアクセスを許可したい場合がある。

例えば、認証されたユーザーのみにファイルのダウンロードを許可したい場合など。

そんなときは、S3の署名付きURL(presigned URLs)を使用する。

## 署名付きURL(presigned URL)とは
> 署名済み URL は、セキュリティ認証情報を使用してオブジェクトをダウンロードするアクセス許可を期限付きで付与します。署名付き URL をブラウザに入力するか、プログラムで使用してオブジェクトをダウンロードできます。署名付き URL で使用される認証情報は、URL を生成した AWS ユーザーのものです。

参考：[名付き URL を使用したオブジェクトの共有](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html)


## boto3を使用して署名付きURLを生成する
`generate_presigned_url`メソッドで署名付きURLを生成できる。
渡してるパラメーターは以下。

- **ClientMethod (string)**
事前署名するクライアントメソッド。
`get_object`を指定するとダウンロード用のURLになる。
`put_object`を指定するとアップロード用のURLになる。

- **Params (dict)**
ClientMethodに渡されるパラメータ。
`ResponseContentDisposition`: ファイルをWEBページとして表示するか、ダウンロードさせるか等を指定するためのヘッダー。`attachment; filename="{object_key}"`を指定することで、ブラウザに対してS3のオブジェクト名でファイルをダウンロードさせることができる。

- **ExpiresIn (int)**
署名済みURLの有効期間を指定する。

以下のコードで署名付きURLを生成

```py:create_presigned_url.py

import boto3
from botocore.exceptions import NoCredentialsError

def lambda_handler(event, context):
    s3_client = boto3.client('s3')
    bucket_name = 'xxxxxxxxxxxxxxxx'
    object_key = 'xxx.txt'

    # 署名付きURLを生成
    try:
        url = s3_client.generate_presigned_url(
            'get_object',
            Params = {
                'Bucket': bucket_name,
                'Key': object_key,
                'ResponseContentDisposition': f'attachment; filename="{object_key}"'  # object_keyをダウンロード時のファイル名として指定
            },
            ExpiresIn=3600
        )
        return {
            'statusCode': 200,
            'body': url
        }

    except NoCredentialsError:
        return {
            'statusCode': 403,
            'body': 'Credentials are not available.'
        }
    except Exception as e:
        return {
            'statusCode': 500,
            'body': str(e)
        }

```

上記コードを実行すると以下のURLが生成される。
(AWSAccessKeyId等は伏字にしてます)
```sh
https://bucket-name.s3.amazonaws.com/xxx.txt?response-content-disposition=attachment%3B%20filename%3D%22xxx.txt%22&AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXX&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&x-amz-security-token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&Expires=1741614318
```
見やすいように改行したものが以下。
```sh
https://bucket-name.s3.amazonaws.com/xxx.txt?
response-content-disposition=attachment%3B%20filename%3D%22xxx.txt%22
&AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXX
&Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&x-amz-security-token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
&Expires=1741614318
```
generate_presigned_urlメソッドで指定した`response-content-disposition`や`Expires`がクエリパラメーターとして含まれていることが分かる。

## 署名付きURLが持つ権限について
署名付きURLが持つ権限は、署名付きURLを発行したIAMロール・IAMユーザーに依存する。
上記のPythonコードはLambdaで実行しているので、LambdaにアタッチしたロールがS3バケットに対する権限を持たない場合は、署名付きURLにアクセスしてもAccessDeniedとなる。
バケットポリシーでIP制限など、細かなアクセス制御が可能。

## 参考
- [署名付き URL を使用したオブジェクトのダウンロードおよびアップロード](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/using-presigned-url.html)
- [IAM ポリシー未設定の IAM ユーザーが生成した S3 署名付き URL (presigned URL) を使ってオブジェクトを Get できるのはなぜか](https://dev.classmethod.jp/articles/iam-and-presigned-url/)