Cloud Functions 2nd Genにおけるsecret keyの管理
GCPのFaaS(Functions-as-a-Service)Cloud Functionsは最近第二世代がリリースされ、(まだbeta期間中ですが)インフラ周りがけっこう強化されました。Cloud StorageとBigQueryにある大規模データを処理するために、HTTPトリガーのアップタイムが60分までに、イベントトリガーも10分まで伸びました。
筆者はこの数週間ちょうどCloud Functionsを利用して承認システムを開発しています。Cloud Functions 2nd Genにおけるsecret keyの管理について少し困っていたので、調べたものを共有します。
何が問題か
secret keyなどを明文でCloud Functionsの環境変数に保存するは流石に危ないので、他の方法が必要です。
we recommend that you review the best practices for secret management. Note that there is no Cloud Functions-specific integration with Cloud KMS.
Cloud Functions公式ドキュメントによると、secret keyを管理するベストプラクティスはSecret Managementを利用することです。また、Cloud Functions特有のCloud KMSは存在しません。
1st genの場合はデプロイする際に-set-secrets
というオプションでSecret Management上保存しているキーを追加すれば良いですが、2nd genで同じようなことをやると、-set-secrets
はまだ実装されていない(2022年5月)ので、怒られます。
ERROR: (gcloud.beta.functions.deploy) --set-secrets is not yet supported in Cloud Functions V2.
Cloud KMSを使う
Secret Managementはまだ存在しない時代(と言っても3年前)に書かれた記事を読んで、参考になりました。
考え方としては、
- Cloud KMSで認証情報(文字列)を暗号化する
- 暗号化した認証情報をCloud Functionsの環境変数に登録する
- Cloud Functionsの関数内で暗号化された環境変数を復号化する
KMS ring, KMS keyの設定、キーを暗号化する方法はかなりわかりやすくまとめているので、基本的にこの記事を参考すればいいです。
気をつけるべきポイントは一つだけです。Cloud KMSのSDKがすべにv2にアップグレードされたなので、復号化コードを少し修正する必要があります。
簡単な例を書いたので、共有します。
from google.cloud import kms
PROJECT_ID = os.environ.get("PROJECT_ID")
LOCATION_ID = os.environ.get("LOCATION_ID")
KMS_KEY_RING_ID = os.environ.get("KMS_KEY_RING_ID")
KMS_KEY_ID = os.environ.get("KMS_KEY_ID")
def decrypt_cipher_key(ciphertext: str) -> str:
client = kms.KeyManagementServiceClient()
key_name = client.crypto_key_path(
project=PROJECT_ID,
location=LOCATION_ID,
key_ring=KMS_KEY_RING_ID,
crypto_key=KMS_KEY_ID,
)
request = kms.DecryptRequest(
name=key_name,
ciphertext=ciphertext,
)
response = client.decrypt(request=request)
return response.plaintext.decode("ascii")
関数decrypt_cipher_key
利用すると、base64でエンコードされたキーをデコードできます。
若干面倒になりましたが、Cloud Functions 2nd Genでもsecret keyを安全に管理できるようになりました。Secret ManagementがCloud Functions 2nd Genに対応するまでに一時的な対策としてはまあまあ使えるでしょう。
Discussion