boto3 を用いて MinIO でユーザの追加と Security Token Service(STS) による一時認証情報での署名済み URL を発行する

ある環境で AWS S3 の署名済み URL を用いて感動しました。
そこで，MinIO on Docker でマルチユーザ＆署名済み URL によるアップロードやアクセスに対応し，手元の環境でも感動を味わえるようにします。

## 前提条件
* MinIO が使える状態であること
    * 今回の環境は [docker-compose の Volume で NFS を指定して MinIO を立ち上げる - Qiita](https://qiita.com/y_k/items/2a262387bc844e592881) で構築したものになります

## MinIO クライアント
### インストール
[MinIO | MinIO Client Quickstart Guide](https://docs.min.io/docs/minio-client-quickstart-guide.html) を参考にクライアントツールをインストールし，`mc` コマンドを使えるようにします。

docker を用いる場合は `docker pull minio/mc` で macOS の方で Homebrew を導入済みの方は `brew install minio/stable/mc` でコマンドで MinIO クライアントを導入できます。

docker を用いる際は，以降の mc コマンドを用いるところは `docker run -it --entrypoint=/bin/sh minio/mc` で環境を立ち上げてからコマンドを入力してください。


### セットアップ
サーバへの接続情報を設定します:

```console
$ # mc config host add <ALIAS> <YOUR-S3-ENDPOINT> <YOUR-ACCESS-KEY> <YOUR-SECRET-KEY>
$ mc config host add minio http://127.0.0.1:9000 BKIKJAA5BMMU2RHO6IBB V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12
```

実際に動作確認としてすでにあるバケットの一覧を取得します:

```console
$ # mc ls <ALIAS>
$ mc ls minio
```

## ユーザ作成
全権アカウントとは別にバケット毎の作業用アカウントを作成します。
まずはバケットを作成します:

```console
$ mc mb minio/my-bucketname
```

作成した `my-bucketname` バケットに対して操作可能なアカウントを作成しましょう。

### ポリシーの作成
新しく作成するアカウントに適応するポリシーを事前に追加します。
ポリシーは json で設定ファイルを作成し，mc コマンドで実際にサーバに登録します。

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

設定ファイルを作成したら，実際にサーバにポリシーを追加しましょう:

```console
$ mc admin policy add minio my-bucketname my-bucketname.json
```

```Added policy `my-bucketname` successfully.``` と表示されれば成功です。

### ユーザの作成
新しくユーザを作成し，上で作成したポリシーを適用します。

```console
$ mc admin user add minio newuser newuser123  # 新しくユーザを作成
$ mc admin policy set minio my-bucketname user=newuser  # 作成したユーザにポリシーを設定
```

### Security Token Service(STS) による一時アカウントの発行と STS を用いた署名済み URL の発行
#### 前準備
まずは Web 画面から事前に `my-bucketname` バケットにファイルを保存します。
今回は `test.txt` という名前で保存したものとして，以降進めます。

#### ソースコード
今回は AWS の SDK である boto3 を用います。

```console
$ pip install boto3  # boto3 をインストール
```

実際にプログラムを作成して署名済み URL を取得・表示します。
下のプログラムでは，MinIO への認証情報も直接書いていますが，実際に利用する際は profile を作成したり，環境変数で設定しましょう。

```python
import json

import boto3
from boto3.session import Session
from botocore.config import Config

endpoint = 'http://127.0.0.1:9000'  # MinIO サーバの URL

my_config = Config(
    signature_version = 'v4',
)
# 一時アカウントに設定するポリシーを定義
policy = {
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:s3:::my-bucketname/*"
      ],
      "Sid": "Stmt1"
    }
  ]

}

# boto3 を事前に作成したアカウントでクライアントを用意
client = boto3.client('sts',
                      aws_access_key_id='YOUR_ACCESS_KEY',  # アクセスキーを設定する
                      aws_secret_access_key='YOUR_SECRET_KEY',  # シークレットキーを設定する
                      config=my_config, endpoint_url=endpoint)

# 一時アカウントの取得
response = client.assume_role(
    RoleArn='arn:xxx:xxx:xxx:xxxx',  # AWS との互換のために適当に値を入れる
    RoleSessionName='anything',  # 同上
    Policy=json.dumps(policy, separators=(',',':')),  # ポリシーは JSON の文字列で渡す ※空白は付与しない
    # 一時アカウントの有効期間 (900秒から43200秒の範囲で設定する
    DurationSeconds=900,
)

# 上で取得した一時アカウントのうち，署名済み URL 発行に必要な分を取り出す
sts = {
    'aws_access_key_id': response['Credentials']['AccessKeyId'],
    'aws_secret_access_key': response['Credentials']['SecretAccessKey'],
    'aws_session_token': response['Credentials']['SessionToken']   
}

# 一時アカウントで接続する準備
session = Session(**sts)

# 一時アカウントでクライアントを用意
s3 = session.client('s3', config=Config(signature_version='s3v4'), endpoint_url=endpoint)

# 署名済み URL を取得 ()
url = s3.generate_presigned_url(
    ClientMethod='get_object',  # 許可するAPIメソッド（今回は get_object: ファイルの取得）
    Params={'Bucket': 'my-bucketname', 'Key': 'test.txt'},  # ファイル情報
    ExpiresIn=60,  # URL の有効期間 (デフォルトは 3600秒)
    HttpMethod='GET')  # 許可する HTTP メソッド（今回は GET）

print(url)  # 署名済み URL を出力
```

出力された URL にアクセスすると事前にアップロードしておいた `test.txt` ファイルの中身が確認できると思います。

# 参考
- [MinIO | Learn how to configure MinIO for multiple long term users](https://docs.min.io/docs/minio-multi-user-quickstart-guide.html)
- [minio/assume-role.md at master · minio/minio](https://github.com/minio/minio/blob/master/docs/sts/assume-role.md)
- [MinIO | Upload files from browser using pre-signed URLs - Cookbook/Recipe](https://docs.minio.io/docs/upload-files-from-browser-using-pre-signed-urls.html)
- [Boto3 documentation — Boto3 Docs 1.14.20 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)