Edited at

PythonでGoogle Cloud Storageの署名付きURLを作成する

Google Cloud Platform その2 Advent Calendar 2018の3日目の投稿となります。

Google App Engine(GAE)でアプリ開発をしていたのですが、ファイルアップロード機能を実装していると、アップロードできるファイルサイズに制限があったため、Google Cloud Storage(GCS)の署名付きURLを作成して直接アップロードすることにしました。

署名付きURLを作成するのに1日ほどハマったので、まとめておきます。


環境構築

仮想環境を作成して環境を構築します。

ソースはGitHubにアップしていますので、よければご参考ください。

https://github.com/kai-kou/create-cloud-storage-signed-url

> python --version

Python 3.6.6

> python -m venv venv
>. venv/bin/activate

> touch requirements.txt
> touch main.py

Google Cloud Platform(GCP)のサービスアカウントを利用してURLを生成するのに、oauth2clientを利用します。


requirements.txt

oauth2client


実装は下記を参考にさせてもらいました。

authentication - Cloud storage and secure download strategy on app engine. GCS acl or blobstore - Stack Overflow

https://stackoverflow.com/questions/29847759/cloud-storage-and-secure-download-strategy-on-app-engine-gcs-acl-or-blobstore

署名付きURLの作成方法については公式ドキュメントが詳しかったです。

Generating Signed URLs with Your Own Program  |  Cloud Storage  |  Google Cloud

https://cloud.google.com/storage/docs/access-control/signing-urls-manually


main.py

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_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('[バケット名]', '[オブジェクト名(ファイル名)]', 'GET')
print(url)


上記で指定する[サービスアカウントキーのファイルパス]はGCPのサービスアカウントを作成すると取得できるjsonファイルとなります。サービスアカウントには必要なGCSの役割を付与します。

サービスアカウントの作成方法は下記が詳しかったです。

Google Cloud Platform のサービスアカウントキーを作成する | MAGELLAN BLOCKS

https://www.magellanic-clouds.com/blocks/guide/create-gcp-service-account-key/


実行する

実行するとURLが生成されます。URLにアクセスする、指定したGCSのバケットにあるオブジェクトが取得できることが確認できます。

> python main.py

https://storage.googleapis.com/[バケット名]/[オブジェクト名]?GoogleAccessId=xxxxx%40xxxxx.iam.gserviceaccount.com&Expires=1542687588&Signature=xxxxxxxxxx


ハマりポイント


GCPサービス上でGoogleCredentials.get_application_default() が使えない

Google App Engine(GAE)やGoogle Cloud Functions(GCF)だと、実行に利用しているサービスアカウントの情報が取得できるので、GoogleCredentials.get_application_default()Credentials を利用しようとしたのですが、service_account_email が空で、sign_blob メソッドも利用できませんでした。


まとめ

AWSだと署名付きURLの作成はもう少し簡単だった記憶があったので、GCPでも同じ感覚でいたら、ひどい目にあいました。自前でURLを組み立てる必要があったので、手間がかかりましたが、作成方法さえわかれば、あとは、GAEやGCFでの利用も簡単そうです。


参考

authentication - Cloud storage and secure download strategy on app engine. GCS acl or blobstore - Stack Overflow

https://stackoverflow.com/questions/29847759/cloud-storage-and-secure-download-strategy-on-app-engine-gcs-acl-or-blobstore

Generating Signed URLs with Your Own Program  |  Cloud Storage  |  Google Cloud

https://cloud.google.com/storage/docs/access-control/signing-urls-manually

Google Cloud Platform のサービスアカウントキーを作成する | MAGELLAN BLOCKS

https://www.magellanic-clouds.com/blocks/guide/create-gcp-service-account-key/