ユーザーアカウントの権限でGCSの署名付きURLを発行してみた

# 初めに

こんにちは、@yyyimaiです。
今回はGCSにてユーザーアカウントの権限を使って署名付きURLを発行してみたいと思います。
どなたかの参考になれば幸いです。

# TL;DR

- GCSでユーザーアカウントの権限を使ったアップロード及びダウンロード用の署名付きURLを発行
- HMACキーを利用
- 基本的にはサービスアカウントでできることと同じ
- Python3系での実装

# GCSの署名付きURLについて

公式ドキュメントでは署名付きURLを以下のように説明しています。

> 署名付き URL では、特定の Cloud Storage リソースに期限付きでアクセスできます。署名付き URL は、その URL を所有する全員が、Google アカウントを持っているかどうかにかかわらず、URL がアクティブである限り使用できます。

署名付きURLの使い道としてはファイルの「アップロード」もしくは「ダウンロード」になります。
このURLの署名する方法には以下の表のように3つあり、今回利用している方法は**HMAC認証**になります。

| 署名方法 | 利用可能アカウント | 備考 |
| :------ | :---------------- | :-- |
| サービス アカウント認証による V4 署名 | サービスアカウント |  |
| HMAC 認証による署名 | ユーザーアカウント、サービスアカウント |  |
| サービス アカウント認証による V2 署名 | サービスアカウント | 非推奨 |

## HMAC認証について

基本的にGCSの署名付きURLの発行にはサービスアカウントを利用します。
公式ドキュメントでもHMAC認証を利用するときは別のサービス(AWSのS3など)と連携する際に使われるという記述で大半となっていました。

> この認証方法は、他のクラウド ストレージ サービス プロバイダでサポートされています。また、この種類の認証情報は、HMAC 認証情報に対応しているサービス プロバイダ間でデータを移動する場合にも使用されます。

今回は後述のPythonスクリプトによりサービスアカウントで署名付きURLを発行する時と同じように署名付きURLを発行します。

# GCSでHMACアクセスキーを発行

まず、GCSの画面に行き、サイドメニューから設定画面に行き、相互運用性のタブへ移動します。

![blog_hmac1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2324252/ec71073d-08a2-bea0-a659-a245ee5d73fa.png)

次に、下の方へスクロールし、HMAC認証のためユーザーアカウントのアクセスキーを作成します。

![blog_hmac2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2324252/e4e2e8f1-25ca-752b-c5e2-36c50d7c08d2.png)

作成されたアクセスキーとシークレットは後々使うため、コピーして控えておいてください。

![blog_hmac3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2324252/c828b539-50ff-cdae-843e-bb22df3bb9bb.png)

# Pythonスクリプトのコード

以下に載せたスクリプトは[salrashid123さんがGitHub上に挙げられていたもの](https://github.com/salrashid123/gcpsamples/tree/master/gcs_hmac_signedurl)を一部改変したものになります。
salrashid123さんの作成されていたPythonスクリプトはPython2系で作成されているため、今回はPython3系であるPython3.10.7で使えるような変更を加えたことになります。

```Python:hmac_sign.py
import hmac
import time
import urllib
import base64
import hashlib
import datetime
import requests

GCS_API_ENDPOINT = 'https://storage.googleapis.com'
hmac_key = '作成したHMACアクセスキーの文字列'
hmac_secret = b'作成したシークレットの文字列'
BUCKET_NAME = 'GCSバケットの名前'
OBJECT_NAME = 'somefile.txt' # /を入れてフォルダ階層を指定することも可能

# URLの有効期限をsecondsで指定
expiration = datetime.datetime.now() + datetime.timedelta(seconds=3600)
expiration = int(time.mktime(expiration.timetuple()))


def _Base64Sign(url_to_sign):
    digest = hmac.new(
        hmac_secret, url_to_sign.encode('utf-8'), hashlib.sha1).digest()
    signature = base64.standard_b64encode(digest).decode('utf-8')
    return signature


def _MakeSignatureString(verb, path, content_md5, content_type):
    signature_string = ('{verb}\n'
                        '{content_md5}\n'
                        '{content_type}\n'
                        '{expiration}\n'
                        '{resource}')
    return signature_string.format(verb=verb,
                                   content_md5=content_md5,
                                   content_type=content_type,
                                   expiration=expiration,
                                   resource=path)


def MakeUrl(verb, path, content_type='', content_md5=''):
    signature_string = _MakeSignatureString(verb, path, content_md5,
                                            content_type)
    signature_signed = urllib.parse.quote(_Base64Sign(signature_string))

    signed_url = "https://storage.googleapis.com/" + \
        BUCKET_NAME + "/" + OBJECT_NAME + "?GoogleAccessId=" + \
        hmac_key + "&Expires=" + str(expiration) + \
        "&Signature=" + signature_signed

    return signed_url


file_path = '/%s/%s' % (BUCKET_NAME, OBJECT_NAME)


print("PUT:")
u =  MakeUrl("PUT",file_path)
print(u)
r = requests.put(u, data='lorem ipsum')
print("put status_code: " + str(r.status_code))
print('data: ' + r.text)
print("---------------------------------")

print("GET")
u = MakeUrl("GET", file_path)
print(u)
r = requests.get(u)
print("get status_code: " + str(r.status_code))
print('data; ' + r.text)
```

# スクリプトを実行してみた

スクリプトをそのまま実行すると、GCSバケット上にsomefile.txtというファイルがアップロードされた後、そのファイルをダウンロードします。

![blog_hmac4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2324252/29ed289e-7bd4-acc5-ad7e-0da3f8cfafa9.png)

そして、GCS上にも同名のファイルが存在していました。

![blog_hmac5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2324252/0ca095e2-3788-7580-55a6-e18597c4037a.png)

そして、ダウンロード用のURLに期限が切れた後にアクセスすると期限切れであることがわかります。

![blog_hmac6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2324252/1d2cc933-0128-86cf-6a89-d3464c3060e5.png)

このようにして、GCSの署名付きURLをHMAC認証にて発行することができます。

# 最後に

ほとんどの状況でサービスアカウントを使って署名付きURLを発行すると思います。
ただ、今回はユーザーアカウントを使わなければならないという状況があり、そのための署名付きURLの発行方法をお伝えしました。
[salrashid123さんがGitHub上に挙げられていたもの](https://github.com/salrashid123/gcpsamples/tree/master/gcs_hmac_signedurl)が存在しなければイチから考えなければなりませんでした。
勝手に引用させていただいておりますので、この場を借りて感謝いたします。
ありがとうございました。