前回、Google Cloud Storage(GCS)の署名付きURLが生成できることを確認したのですが、実際にURLを利用してファイルをアップロードした際にハマりました。
PythonでGoogle Cloud Storageの署名付きURLを作成する
https://qiita.com/kai_kou/items/45c34576604ec8f6a3d1
再現してみる
前回、署名付きURLを作成する際に、Content-Type
を空にしていたのですが、そのままでcurl
でファイルをアップロードできたものの、Webアプリからアップロードしようとすると403
エラーとなりハマっていました。
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
でContent-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://qiita.com/kai_kou/items/45c34576604ec8f6a3d1
署名付き URL | Cloud Storage ドキュメント | Google Cloud
https://cloud.google.com/storage/docs/access-control/signed-urls