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を利用します。
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
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/