9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Google Cloud Storageの署名付きURL作成時にContent-Type を指定せずPUT時にContent-Type を含めると403エラーになる

Last updated at Posted at 2018-12-05

前回、Google Cloud Storage(GCS)の署名付きURLが生成できることを確認したのですが、実際にURLを利用してファイルをアップロードした際にハマりました。

PythonでGoogle Cloud Storageの署名付きURLを作成する
https://qiita.com/kai_kou/items/45c34576604ec8f6a3d1

再現してみる

前回、署名付き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 で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"

はい。無事にアップロードできました。
次にcurlContent-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

9
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?