Google Secret Manager で パスワードや API キー などの秘匿情報を管理し Cloud Funcitons から参照する方法

ソフトウェアデベロッパー にとって、 パスワード、 APIキー、 認証情報 といった秘匿情報の管理は頭を悩ませる問題の一つといえます。 開発環境と本番環境では別の情報を用いる必要があることが殆どだと思いますし、 このような秘匿情報を誤って公開 Repository にコミットしてしまうと、 様々な被害を被る危険性があります。 一方で、秘匿情報をローカルで管理すると、 紛失のリスクや、バージョン管理面での不都合などが生じてしまいます。

その問題に対処すべく Google Cloud では Secret Manager を使うことで、 秘匿情報を安全に管理する方法を提供しています。

本記事では、 Secret Manager に サンプルの API キー を格納し、 Python で作成した Cloud Functions から参照する方法を紹介します。

引用元: https://cloud.google.com/secret-manager/


本記事の概要

本記事では以下を実施していきます。

  • シークレット バージョン の作成
  • Python で作成した Cloud Functions 関数から 事前に作成した シークレット バージョン の値を取得

なお、“シークレット バージョン” という呼称は、個人的に少し馴染みが無いため以下のように捉えています。

「パスワードなどの秘匿情報 = (Secret) をバージョン管理したもの」

前提

なお本記事を読みすすめるにあたっては、以下を実施済みであることが前提となっています。

  • Python 開発環境の設定
  • サービスアカウント作成済み

参考記事:


Secret Manager を利用するための準備

Secret Manager API の有効化

まずは、 Secret Manager API を有効化します。この API を有効化することで、後述する Secret Manager を使って シークレット バージョン を管理することができるようになります。 GCP コンソール から以下のように遷移し有効にしていきます。

既存 サービスアカウント への Secret Manager への アクセスロール 付与

次に、Secret Manager にアクセスできるようにしていきます。最初に Secret Manager 関連のロールにどのようなものがあるのかを確認していきます。

Secret Manager 関連ロール

Secret Manager 関連ロールには以下が用意されています。今回は、作成済みの シークレット に Cloud Functions からアクセスし、格納された値を取得しようと思いますので、 “Secret Manager の シークレット アクセサー” ロールを付与していきます。 “Secret Manager 閲覧者” ロールでは格納した値を読み出すことができませんので、注意してください。

ロール説明
Secret Manager 管理者Secret Manager リソースを管理するための完全アクセス権
Secret Manager のシークレット アクセサーシークレットのペイロード(*)へアクセス可能
Secret Manager のシークレット バージョンの追加既存のシークレットへのバージョン追加が可能。シークレットの追加や、バージョンの削除などは不可。
Secret Manager のシークレット バージョンのマネージャーシークレットの追加やバージョンの追加削除などが可能
Secret Manager 閲覧者シークレットのメタデータを表示することが可能
Secret Manager 関連 ロール 一覧
(*) ここでのペイロードとは、格納しているパスワードなど秘匿情報のこと。

詳細は以下を確認するようにしてください。

https://cloud.google.com/iam/docs/understanding-roles?hl=ja#secret-manager-roles

Secret Manager へのアクセスロール付与手順

それでは、実際にロールを付与していきます。今回は、 既存の サービスアカウント ([email protected]) に対して上述のとおり “Secret manager の シークレット アクセサー” ロールを付与していきます。

ロール選択欄には数多く表示されますが、 Filter 欄に secret と入力することで目的の “Secret Manager のシークレット アクセサー” に手早くアクセスすることができます。

保存をクリックすることで、このデフォルトのサービスアカウントに Secret Manager のシークレットアクセサー が付与されます。

詳細な手順については以下を確認するようにしてください。

Manage access to projects, folders, and organizations


Secret の作成

それでは続いて、Secret を作成し、パスワードなどの秘匿情報を格納してみます。
今回は、以下のシークレットを作成します。

  • 名前: my-secret
  • 値: my super secret data

シークレットが作成されると、作成したシークレットが以下のようにリスト表示されます。
試しに、値が確認されているかどうか確認してみます。三点リーダーをクリックして、「Secret の値を表示」 をクリックします。

バージョンの追加

シークレットが作成できたので、合わせてバージョンも作成してみたいと思います。バージョンの追加は必須手順ではありませんので必要に応じて読み飛ばしてください。
三点リーダーから 「新しいバージョンを追加」 をクリックします。

新しいバージョンの値として、”new secret data” を入力し 「新しいバージョンを追加」 をクリックします。

バージョン一覧に新しいバージョンが追加されました。

これで、Secret に 秘匿情報として文字列を格納することができました。それでは、次にこの格納した文字列を Python で記載した Cloud Functions からアクセスして表示してみます。


Python の Cloud Functions から アクセス

Python Client for Secret Manager API のインストール

Secret Manager にアクセスする Cloud Functions 関数を作成する前に Python Client for Secret Manager API をインストールします。これで、Python プログラム上から簡単に Secret Manager にアクセスすることが可能になります。

pip install google-cloud-secret-manager

Python Client for Secret Manager API — google-cloud-secret-manager documentation

Cloud Functions で Secret Manager にアクセスする関数を作成

Secret Manager のサンプル に紹介されているものの中から、シークレット バージョンへのアクセス として以下のコードが紹介されていますので、これを活用して Cloud Functions 関数を作成していきます。

def access_secret_version(project_id, secret_id, version_id):
   """
   Access the payload for the given secret version if one exists. The version
   can be a version number as a string (e.g. "5") or an alias (e.g. "latest").
   """
 
   # Import the Secret Manager client library.
   from google.cloud import secretmanager
 
   # Create the Secret Manager client.
   client = secretmanager.SecretManagerServiceClient()
 
   # Build the resource name of the secret version.
   name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"
   print(name)
 
   # Access the secret version.
   response = client.access_secret_version(request={"name": name})
 
   # Print the secret payload.
   #
   # WARNING: Do not print the secret in a production environment - this
   # snippet is showing how to access the secret material.
   payload = response.payload.data.decode("UTF-8")
   print("Plaintext: {}".format(payload))
 
   return response

この access_secret_version 関数を呼び出す Cloud Functions のエントリーポイントとなる関数は以下のように作成します。

  • プロジェクト ID: sample-for-blog
    なお、今回はプロジェクトID として ‘sample-for-blog’ というプロジェクト名称を指定しましたが、数字による指定も可能です。
  • シークレット ID: my-secret
  • バージョン ID: latest

latest を指定することで最新のバージョンを取得します

def hello_http(request):
    response = access_secret_version('sample-for-blog', 'my-secret', 'latest')
    payload = response.payload.data.decode("UTF-8")
    print("Secret Data: {}".format(payload), type(payload))

    return payload

それでは、早速デプロイして動作を確認してみます。
なお、functions_framework を利用した Local 環境での動作確認には GOOGLE_APPLICATION_CREDENTIALS 環境変数をセットする必要がありますので、Getting started with authentication | Authentication を参考に環境変数をセットするようにしてください。環境変数をセットしていない場合、以下のようなエラーが出力されます。

google.auth.exceptions.DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started

デプロイ〜動作確認

デプロイ

まずは、以下のコマンドでデプロイします。

gcloud functions deploy hello_http --runtime python39 --trigger-http --allow-unauthenticated

デプロイが完了すると、以下のように Cloud Functions に hello_http 関数が表示されます。

テスト

hello_http 関数の詳細を表示し、 トリガーとなるイベント の内容は変更せずそのまま 「関数をテストする」 をクリックしてテストを実施します。

Secret の作成 で作成したシークレットの値が出力されていることが確認できます。

HTTP エンドポイントにアクセスしての動作確認

最後にデプロイした HTTP エンドポイントにアクセスしてみます。

無事 Secret の内容を取得し、その内容を出力できていることが確認できました。


まとめ

  • GCP 内でパスワードなどの秘匿情報は Secret Manager に格納し管理する
  • Secret の値にアクセスするだけなら、 Secret Manager の シークレット アクセサー ロールが適切
  • Python で Secret Manager にアクセスするために Python Client for Secret Manager API をインストールする
  • functions_framework で Secret Manager にアクセスするためには、別途環境変数のセットが必要

関連記事

参照情報

Configuring Secret Manager | Secret Manager Documentation

Manage your Cloud Run secrets securely with Secret Manager

Secret Managerを使ってみる@Cloud Functions編

GCP連載#5【もう鍵なくさない】GCPのSecret ManagerとBerglasで幸せになる | フューチャー技術ブログ

【GCP】Secret Manageを触ってみた


今回使用したの全ソースコード

def access_secret_version(project_id, secret_id, version_id):
   """
   Access the payload for the given secret version if one exists. The version
   can be a version number as a string (e.g. "5") or an alias (e.g. "latest").
   """
 
   # Import the Secret Manager client library.
   from google.cloud import secretmanager
 
   # Create the Secret Manager client.
   client = secretmanager.SecretManagerServiceClient()
 
   # Build the resource name of the secret version.
   name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"
   print(name)
 
   # Access the secret version.
   response = client.access_secret_version(request={"name": name})
 
   # Print the secret payload.
   #
   # WARNING: Do not print the secret in a production environment - this
   # snippet is showing how to access the secret material.
   payload = response.payload.data.decode("UTF-8")
   print("Plaintext: {}".format(payload))
 
   return response

def hello_http(request):
    response = access_secret_version('sample-for-blog', 'my-secret', 'latest')
    payload = response.payload.data.decode("UTF-8")
    print("Secret Data: {}".format(payload), type(payload))

    return payload