Cloud Functionで署名付きURLを生成する

はじめに

クラウドサービスなどを用いたサーバレスなアプリケーションの開発が可能となった現代において,クラウド上でスクリプトを走らせるのはもはや一般的となっている.
今回はGoogleのCloud Functionで署名付きURLを生成するのに手間がかかったので記事にする.
cloud.google.com

署名付きURL

署名付きURLとはGoogle Cloud Strage (GCS)上のリソースに対して,GCSのクライアントライブラリが生成する期限付きのURLである.このURLはGoogleアカウントの有無に関わらず,URLがアクティブである限り使用できる.
このURLの生には,GCSにおいてリソースを管理することのできるアカウント(サービスアカウント)による認証が必要になる.そこでGoogleはサービスアカウントの認証の手段として,サービスアカウントキーというローカルで使用するためのjsonファイルの提供を行っている.
cloud.google.com
このjsonファイルに秘密鍵が含まれるので,リクエストを投げる際に併用することでサービスアカウントの認証ができるというわけ.

GCS上で動作するCloud Functionでは,こうしたサービスアカウントキーは必要ないはず.そもそもCloud Functionはサービスアカウントにより動作するためだ.
サンプルでも特にそうしたjsonファイルの云々は記述されていない.
cloud.google.com

ところが,Cloud Function上で署名付きURLは生成できない.

原因と対処

下の記事はこの問題に対処した記事となっている.私もこの記事を参考にこれを解決した.どうやら原因はservice_account_emailが空であることのようだ.
qiita.com
実はGoogleはこうした問題が起こっても大丈夫なように(?)生でURLを署名するためのアルゴリズムを公開している.Cloud Function上でどうしても署名付きURLが生成したいときには,参考にするしかないようだ.
cloud.google.com
上のQiitaの記事はこれに準拠したアルゴリズムとなっているが,1つだけ問題がある.それは,実行するスクリプトjsonファイルが必須であることだ.Cloud Function上でサービスアカウントの認証情報が参照できない以上,サービスアカウントキーが必要になるのは仕方がないが,どのようにしてこれを指定するか.
GCS上のリソースを参照するライブラリでは,通常のファイルを扱うときのようにパスをとって…ということはできない.従って,Qiitaの記事で使用されているメソッド

ServiceAccountCredentials.from_json_keyfile_name()

は使用できない.

そこで予めリソースとしてサービスアカウントキーファイルをCloud Strage上に置いておき,Cloud Functionのライブラリでアクセス,

ServiceAccount.from_json_keyfile_dict()

を使用することでこの解決を図る.

参考ドキュメント:oauth2client.service_account module — oauth2client 4.1.2 documentation

以上をまとめるとこうなる.key_filenameがGCS上のサービスアカウントキーファイル.

        # generate signed mp3 url on GCS
        blob = bucket.blob(key_filename)
        json_string = blob.download_as_string()
        key_file_dict = json.loads(json_string)
        credentials = ServiceAccountCredentials.from_json_keyfile_dict(key_file_dict)

        gcs_filename = '/%s/%s' % (bucket_name, filename)
        content_md5, content_type = None, None

        google_access_id = credentials.service_account_email

        expiration = datetime.now() + timedelta(seconds=60)
        expiration = int(time.mktime(expiration.timetuple()))
        
        signature_string = '\n'.join(['GET', 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))

おかたづけアシスタントをつくったはなし - 地底人newbieの気まぐれより引用
サービスアカウントキーファイルをStringとして読み込んでおき,それを署名付きURLの生成のための情報としてもたせる.

まとめ

Google Cloud Strage上でCloud Functionを走らせるとき,現在署名付きURLの生成は不可能となっている.そこで,Cloud Functionで署名付きURLを生成する際にはGoogleが提供する署名アルゴリズムを実装しなければならない.