前回、Google Cloud Storage(GCS)の署名付きURLが生成できることを確認したのですが、実際にURLを利用してファイルをアップロードした際にハマりました。
PythonでGoogle Cloud Storageの署名付きURLを作成する
https://cloudpack.media/45121
再現してみる
前回、署名付きURLを作成する際に、Content-Type
を空にしていたのですが、そのままでcurl
でファイルをアップロードできたものの、Webアプリからアップロードしようとすると403
エラーとなりハマっていました。
main.py_署名付きURLを作成するスクリプト
import time import urllib from datetime import datetime, timedelta import os import base64 from oauth2client.service_account import ServiceAccountCredentials API_ACCESS_ENDPOINT = 'https://storage.googleapis.com' def sign_url(bucket, bucket_object, method, expires_after_seconds=60): gcs_filename = '/%s/%s' % (bucket, bucket_object) # content_typeを空で生成している content_md5, content_type = None, None credentials = ServiceAccountCredentials.from_json_keyfile_name('[サービスアカウントキーのファイルパス]') google_access_id = credentials.service_account_email expiration = datetime.now() + timedelta(seconds=expires_after_seconds) expiration = int(time.mktime(expiration.timetuple())) signature_string = '\n'.join([ method, content_md5 or '', content_type or '', str(expiration), gcs_filename]) _, signature_bytes = credentials.sign_blob(signature_string) signature = base64.b64encode(signature_bytes) query_params = {'GoogleAccessId': google_access_id, 'Expires': str(expiration), 'Signature': signature} return '{endpoint}{resource}?{querystring}'.format( endpoint=API_ACCESS_ENDPOINT, resource=gcs_filename, querystring=urllib.parse.urlencode(query_params)) if __name__ == '__main__': url = sign_url('[バケット名]', 'hoge.json', 'PUT') print(url)
再現すると、こんな感じです。署名付きURLを生成します。
> python main.py https://storage.googleapis.com/xxxxx/hoge.json?GoogleAccessId=xxxx%40xxxxx.iam.gserviceaccount.com&Expires=1542800155&Signature=xxx長いxxx
curl
でCoontent-Type
指定しないままPUTでファイルアップロードしてみます。
> curl -i -X PUT https://storage.googleapis.com/(略) \ --upload-file "[アップロードするファイルパス]" HTTP/1.1 100 Continue HTTP/1.1 200 OK X-GUploader-UploadID: AEnB2Urlfu1tTz2Hm49qPwusYhRdMEYJkTVxVOV0AX21pz5_Ixt4BrD1xspqiVcvQvHYnVDXWn-FZFKd0vk4DDE6IDiVoWh1Uw ETag: "8d7f148d56e423e591ad9a54d46d5cb8" x-goog-generation: 1542800499311073 x-goog-metageneration: 1 x-goog-hash: crc32c=elpi7A== x-goog-hash: md5=jX8UjVbkI+WRrZpU1G1cuA== x-goog-stored-content-length: 42542345 x-goog-stored-content-encoding: identity Vary: Origin Content-Length: 0 Date: Wed, 21 Nov 2018 11:41:39 GMT Server: UploadServer Content-Type: text/html; charset=UTF-8 Alt-Svc: quic=":443"; ma=2592000; v="44,43,39,35"
はい。無事にアップロードできました。
次にcurl
でContent-Type
を指定してPUTしてみます。
> curl -i -X PUT https://storage.googleapis.com/(略) \ --upload-file "[アップロードするファイルパス]" \ -H 'Content-Type: application/json' <?xml version='1.0' encoding='UTF-8'?><Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message><StringToSign>PUT application/json 1542800722 /xxxxx/hoge.json</StringToSign></Error>
はい。does not match the signature you provided
って怒られました。
仕方ないので、署名付きURLを生成するスクリプトでContent-Type
を指定してみます。
一部抜粋
# content_typeを空で生成している content_md5, content_type = None, 'application/json'
> curl -i -X PUT https://storage.googleapis.com/(略) \ --upload-file "[アップロードするファイルパス]" \ -H 'Content-Type: application/json' HTTP/1.1 100 Continue HTTP/1.1 200 OK X-GUploader-UploadID: AEnB2UrSSXsLx_oUYGvy0oToDzBCs_JeSf13NCmc8PdNVpdL-SfwrRkPNxO4f64n7j3uv4UDLpOF8r4zIe3xG8pPQPTd3QvwWg ETag: "8d7f148d56e423e591ad9a54d46d5cb8" x-goog-generation: 1542801014953412 x-goog-metageneration: 1 x-goog-hash: crc32c=elpi7A== x-goog-hash: md5=jX8UjVbkI+WRrZpU1G1cuA== x-goog-stored-content-length: 42542345 x-goog-stored-content-encoding: identity Vary: Origin Content-Length: 0 Date: Wed, 21 Nov 2018 11:50:15 GMT Server: UploadServer Content-Type: text/html; charset=UTF-8 Alt-Svc: quic=":443"; ma=2592000; v="44,43,39,35"
はい。
署名付きURLでContent-Type
を指定すると大丈夫でした。
まとめ
ドキュメントを見る限り必須の指定ではないものの、空文字を含む文字列が一致しないと、受け入れてくれないみたいです。悲しいTT 俺の一日返せぇ
署名付き URL | Cloud Storage ドキュメント | Google Cloud
https://cloud.google.com/storage/docs/access-control/signed-urls
必要に応じて指定。content-type を指定する場合、クライアント(ブラウザ)は、この HTTP ヘッダーを同じ値に設定する必要があります。
参考
PythonでGoogle Cloud Storageの署名付きURLを作成する
https://cloudpack.media/45121
署名付き URL | Cloud Storage ドキュメント | Google Cloud
https://cloud.google.com/storage/docs/access-control/signed-urls
元記事はこちら
「Google Cloud Storageの署名付きURL作成時にContent-Type を指定せずPUT時にContent-Type を含めると403エラーになる」