S3を使うプロジェクトをローカル環境でテストしたいことないでしょうか?
そんな時には、MinIOが便利です。
MinIOはS3 APIのmockとして使える上に、コンソールまで提供してるので今のバケットの状態をGUIで見れて便利です。
さて、MinIOからpresigned URLを発行しようとした際に詰まったので残しておこうと思います。
MinIOの設定
MinIOは公式のdocker imageがあるのでそれを使用します。
services:
minio:
image: quay.io/minio/minio:RELEASE.2024-05-28T17-19-04Z
container_name: minio
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
# ここがないと後述の問題になる
MINIO_DOMAIN: ${MINIO_DOMAIN}
ports:
- 9000:9000
- 9001:9001
restart: unless-stopped
command: ['server', '/data', '--console-address', ':9001']
volumes:
- minio_data:/data
volumes:
minio_data:
上のcomposeのファイルはどっかから拾ってきたものを編集したものです。
ポートが9000番と9001番二つ空いているのは、minioはAPI用とブラウザのコンソール用で二つ使うからです。
環境変数については後で触れます。
presigned URL
presigned URLとは、S3のオブジェクトをGETやPUTする際に、APIサーバーなどに直接データを経由させることなく、S3とクライアントでデータのやり取りができる仕組みです。
具体的には、GETの場合、APIサーバーがアクセスされた際に、一時的に使えるURLを発行し、
そのURLにリダイレクトさせたりします。
PUTの場合は、PUTする前にpresigned URLを発行してそこに対してPUTさせるようにしたりします。
presigned URLには期限を設けることができて、その期間を過ぎたリクエストを無効化してくれたりします。
presigned URLを取得してみる
さて、presigned URLを発行するAPIを叩いてAPIを発行します。
自分はRustのsdk経由で発行してます。
// clientの作成
let config = aws_config::defaults(BehaviorVersion::v2024_03_28())
.load()
.await;
let client = aws_sdk_s3::Client::new(&config);
// urlの取得
let response = client.get_object()
.bucket(bucket_name)
.key(object_key)
// 変更されていなかったらHTTPステータスコードの304を返却してくれる
.set_if_none_match(get_if_none_match(&headers))
// presigned Requestを行う。
// presigned_configはExpireを設定したコンフィグを返す独自定義の関数
.presigned(presigned_config())
.await?;
あとは、urlを返却します。axumでサーバーを立てた時のは以下の様に返却します
match response {
Ok(response) => {
let uri = response.uri();
// etagとかをheaderで返しておくと if-none-matchで使える
Ok((AppendHeaders(out.headers()), Redirect::temporary(uri)))
}
Err(e) => {
Err(e)
}
}
presigned URLが無効...?
困ったこととして、presigned URLを発行したときに、正しく取得したはずなのに、
そのURLを使ってもGETできないということがありました。
The specified bucket is not valid.
コンソールなどで確認しても確かにバケットがあることが確認できます。
なんでこのようなエラーになるのでしょうか?
S3のpresigned URLの形式
得られたpresigned_urlのドメインはindex.localhost
になってました。
確かに得ようと思ったバケット名はindex
です。なぜサブドメインがこのように設定されているのでしょうか。
これはS3のpresigned_urlの形式に由来してます。
S3は2019年以降、仮想ホスト形式というURLの形式に移行していて、
http://<bucket-name>.s3.amazonaws.com
という形式になっています。
ただそれ以前はパス形式というhttp://s3.amazonaws.com/<bucket-name>
のようにパスにバケット名を入れる方式を利用していたそうです。
Rustのsdk clientにはforce_path_styleという設定があり、これを使うと問題なく取得できました。
ただ記事にある通りこの形式では本番のS3にアクセスできないので、どうにかしたいです。
MinIOの設定
MinIOはデフォルトではパス形式を使うので、仮想ホスト形式に切り替えないといけないです。
解決策は、MinIOにMINIO_DOMAIN
の設定を渡すだけでした。これで仮想ホスト形式でも、
MinIOがそのURLでアクセスしてObjectの取得ができました。
終わりに
原因がわかりづらくて調査がつらかった…。
そもそも5年前に廃止された方式がなぜ今もデフォルトなのか…。