(解決済み)【Ruby】aws-sdk-s3のGemを更新したら署名付きURL経由のPUTリクエストが失敗するようになった
TL;DR
3行解説
- aws-sdk-s3のバージョンを上げたら、今までできていた署名付きURL経由でのPUTリクエストが失敗するようになった(GETリクエストは可能)
- aws-sdk-s3のv1.141.0からv1.146.0までの間、リクエストヘッダの
Content-Type
を指定しなくても署名に含まれるようになった - aws-sdk-s3のv1.147.0からは
Content-Type
が署名に含まれなくなった
経緯
業務でAWS公式のS3 SDKであるaws-sdk-s3のバージョンを更新する機会があったので上げてみました。
発生した事象
ユーザがファイルをS3へアップロードする機能を提供するために、S3の署名付きURLを利用して実現していたのですが、aws-sdk-s3のバージョンを更新したところ、ファイルのアップロードに失敗するようになってしまいました。
一方で、ユーザがS3上のファイルをダウンロードするための機能を提供するためにもS3の署名付きURLを利用していたのですが、こちらに関してはGemのバージョンを更新した後でも問題なく機能しました。使っているコード
以下、ファイルのアップロード機能で使っているコードを簡略化したコードです。
resource = Aws::S3::Resource.new(config)
bucket = resource.bucket('bucket_name')
object = bucket.object('object_name')
object.presigned_url(:put, expires_in: 300)
この処理を実行すると下記のようなURLが発行されます(一部変更を加えています)
https://bucket_name.s3.ap-northeast-1.amazonaws.com/object_name?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXXXXX%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20241217T104026Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=XXXXXXXXXX
このURLに対してPUTリクエストをすることでS3に対してファイルをアップロードすることができていました。
更新前に使っていたGemのバージョン
aws-sdk-s3 v1.96
調査
以下、調査の方針と結果についてまとめます。
方針
サッとaws-sdk-s3のCHANGELOGを読んでも分からなかったので、バージョンを更新->URLの発行->ファイルのアップロードの作業を地道に繰り返してどのバージョンから失敗するようになったのかを調査することにしました。
結果
差分のあった部分だけまとめます。
- v1.96.1(更新前) -> ✅アップロード成功
- v1.140.0 -> ✅アップロード成功
- v1.141.0 -> ❌アップロード失敗(SignatureDoesNotMatch)
- v1.146.0 -> ❌アップロード失敗(SignatureDoesNotMatch)
- v1.147.0 -> ✅アップロード成功
v1.141.0を境にしてファイルのアップロードが失敗することがわかったのですが、v1.147.0以降は再びファイルのアップロードに成功することもわかりました。
追加調査
ひとまずaws-sdk-s3のバージョンをv1.147.0以上に更新すれば今まで通りの処理が動きそうだったのですが、原因についてわからなかったので追加で調べてみました。
まず、v1.147.0の変更履歴をあらためて確認しました。
Issue - Omit ContentType plugin when generating presigned url.
署名付きURL周りの更新があったことが確認できました。
v1.147.0がリリースされたあたりのIssueを探してみたところ、署名付きURLに関するものが見つかりました。
最新のバージョンでは署名付きURLを発行する際、リクエストヘッダからContent-Type
を削除するようになったようです。
逆に考えてみると、今までは署名付きURLを発行する際にContent-Type
を含めて発行していたことになりますが、上述で紹介したファイルのアップロード処理のコードでは署名付きURLを発行する時にContent-Type
を指定していません。また、PUTリクエストを送信する際もContent-Type
を指定せずに送信していました。
おそらくこの辺りの変更が原因なのだろうと考えたので、各種バージョンで発行される署名付きURLを比較してみました。
※ 一部加工しています。長いので便宜上URLのクエリ文字列で改行します
"https://bucket_name.s3.ap-northeast-1.amazonaws.com/object_name?
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXXXXX%2Fap-northeast-1%2Fs3%2Faws4_request
&X-Amz-Date=20241217T104026Z
&X-Amz-Expires=300
&X-Amz-SignedHeaders=host
&X-Amz-Signature=XXXXXXXXXX"
"https://bucket_name.s3.ap-northeast-1.amazonaws.com/object_name?
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXXXXX%2Fap-northeast-1%2Fs3%2Faws4_request
&X-Amz-Date=20241217T104810Z
&X-Amz-Expires=300
&X-Amz-SignedHeaders=content-type%3Bhost // <- ここ
&X-Amz-Signature=XXXXXXXXXX"
"https://bucket_name.s3.ap-northeast-1.amazonaws.com/object_name?
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXXXXX%2Fap-northeast-1%2Fs3%2Faws4_request
&X-Amz-Date=20241217T105331Z
&X-Amz-Expires=300
&X-Amz-SignedHeaders=host
&X-Amz-Signature=XXXXXXXXXX"
ファイルのアップローが失敗するバージョンと成功するバージョンで発行されたURLを確認してみると、コード上で指定していないパラメータであるContent-Type
が含まれていることがわかります。
どうやらv1.141.0からv1.146.0までの間は、署名付きURLを発行する際に問答無用でContent-Type
が署名に含まれるようになっていたみたいです。
v1.141.0にて、署名付きURLを発行する際にContent-Type
を明示的に指定して、PUTリクエスト時にもContent-Type
を明示的に渡して送信した場合はファイルがアップロードできることも確認ができました(ちなみにContent-Type
を空値にしてリクエストをした場合は失敗しました)。
結局、v1.147.0で改めてContent-Type
が署名から外れたので今まで通りの処理で動くようになったみたいです。
まとめ
今回はaws-sdk-s3のGemをバージョンアップしたところ、特定のバージョン内で署名付きURL経由のPUTリクエストが失敗する事象について調査しました。
バージョンアップ時に書名付きURLの仕様が2回変わって結局元に戻ったので、コードの修正は不要でした✌️
Discussion