Amazon S3 Presigned URLのアクセス制御を深堀りする

# はじめに
S3へのアクセスを制御するために様々な機能がありますが、これらはIAMのアクセス制御と混同して考えられることが多く、両方の機能の関連について、質問を非常に多く受けています。
以前に [S3のアクセス制御はまずシンプルに捉えて対応すべき](https://qiita.com/nasuvitz/items/eccdf9449b00966a0c6a) という記事を書きましたが、この記事に書いたように、S3バケットポリシーとIAMポリシーには以下のような違いがあります。

* S3バケットポリシーは、S3側で、エンティティから行われるアクセスの制御
* IAMポリシーの場合、エンティティ側で、S3に対して行うアクセスの制御

このように、両者では設定箇所および制御する操作の矢印の方向が異なります。

基本はこの2つでS3バケットやオブジェクトに対するアクセス制御を行いますが、この2つに加えて、全く異なるアクセス制御の要素が加わることがあります。

その代表例と言える **「Presigned URL」** を今回は取り上げます。

# Presigned URLとは
**誰でも一時的にS3オブジェクトの操作を可能にするためのURLです。**

URLを知っていれば、誰でも、インターネット経由でS3オブジェクトの操作が可能となります。

最も基本的な使い方として、クラウドストレージのBoxのように、URLを知っている人に期限付きでファイルをダウンロード(s3:GetObject)させることができます。
さらに、URLの発行の仕方によっては、ダウンロード以外の操作も可能にします。

Presigned URLは、オブジェクトの一時ダウンロードURLを発行する用途として広く認識されていますが、その本質は **「URLを発行するIAMユーザーまたはロールで可能なS3に対する操作を、URLを介して、期限付きでできるようにする」** です。(詳細は後述)

コマンドの発行方法は2通りあり、AWS CLIの「aws s3 presign」を実行するか、AWS SDKで各言語のコマンド(Pythonの場合「generate_presigned_url」)を実行します。
コマンドを実行する時に、操作名、バケット名、オブジェクトキー、HTTPメソッド (GET または PUT)、および有効期限の日時を指定します。

記事執筆時点でCLIには操作名を指定するオプションがありませんので、「s3:GetObject」以外の操作をできるようにしたい場合はSDK一択となります。

# Presigned URLの発行とアクセス許可
AWS CLIでURLを発行する場合、暗黙的に「s3:GetObject」の操作が指定されます。

AWS SDKでURLを発行する場合、クエリの記述方法は以下の通りで、1つの任意の操作を`ClientMethod`パラメータで指定できます。
ここに `ClientMethod='put_object'` と書けば「s3:PutObject」の操作を指定できます。

```python:usage (Python)
url = s3_client.generate_presigned_url(
            ClientMethod=client_method,
            Params=method_parameters,
            ExpiresIn=expires_in
        )
```

```python:sample query (Python)
s3_client = boto3.client('s3')
client_action = 'put_object'
url = s3_client.generate_presigned_url(
        s3_client, client_action, {'Bucket': args.bucket, 'Key': args.key}, 86400)
```

# IAMポリシー/S3バケットポリシーと「Presigned URL」の関係
前述の方法でURLを発行できますが、このURLを介して何でも操作が可能になるわけではなく、**「URLを発行したIAMユーザーが可能なS3の操作」が「URLにつき1つ」発行されたURLを介してできるようになります。**

ここで「URLを発行したIAMユーザーが可能なS3の操作」とは、冒頭に紹介した記事でも取り上げている、以下のフローで評価されて決まります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/05d19d51-96cf-7a93-6dc0-c868b0df1399.png)
引用: [IAM Policies and Bucket Policies and ACLs! Oh, My! (Controlling Access to S3 Resources)](https://aws.amazon.com/jp/blogs/security/iam-policies-and-bucket-policies-and-acls-oh-my-controlling-access-to-s3-resources/)

また、URLを介して実行可能なコマンドには、Presigned URLの仕様上の制限があり、全てのコマンドが実行できるわけではありません。
参考: [Presigned URLs](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-presigned-urls.html)

# URLを介したコマンド実行までの間にAWS側でアクセス制御の変更が行われる場合
通常「IAMユーザーがURLを発行」することと「誰かがURLを介してコマンドを実行」することは連続しておらず、必ず両方の操作の間にいくらか時間があります。

URL発行当時のアクセス制御が、コマンドが実行されるまでの間に変更された場合、コマンド実行時点の(変更された)アクセス制御が有効となります。

例えば、URLを発行する時にIAMユーザーが「s3:PutObject」の権限を持っていたとしても、誰かがURLを介してコマンドを実行するまでの間に「s3:PutObject」がIAMポリシーまたはS3バケットポリシーでDenyされた場合、URLを介して「s3:PutObject」を実行できなくなります。

見方を変えれば、IAMユーザーは自身の権限を超えた操作を可能とするURLを発行できないと言えますし、自身の権限を(Presigned URLの仕様上の制限内で)誰にでも渡すことができるとも言えます。

# まとめ
以上の内容をまとめると、以下の図のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/f57a636c-d363-72ea-53d2-7fd59ff5f365.png)
Presigned URLを知る者なら誰でもS3オブジェクトに対する操作が可能となりますが、その操作はURLを発行したIAMユーザーの権限で可能な操作の中から、暗黙または明示的に指定された1つの操作となります。

「一時ダウンロード用途」として良く使われるPresigned URLですが、このアクセス制御の仕組みが分かると、開発の幅が広がりますし、トラブルシューティングにも役立つと思われます。

一つ不便な点として、現状はAWS SDKを使用しない限り「s3:GetObject」以外の操作を許可できない点がありますが、Presigned URLでは誰にでもアクセス権限を渡すことができますから、発行できる前提条件が限られているほうが、セキュリティの観点で良いとも言えます。

# 参考文献
* [オブジェクトをアップロードするための署名付き URL の生成](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/PresignedUrlUploadObject.html)
* [presign](https://docs.aws.amazon.com/cli/latest/reference/s3/presign.html)
* [Presigned URLs](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-presigned-urls.html)