Secret Manager に保存した機密情報を、Cloud Functions の Python コードから取得してみた。

2021.10.18

こんにちは、みかみです。

やりたいこと

  • Google Cloud でパスワードなどの機密情報を安全に保管したい
  • Google Cloud のシークレット管理サービス Secret Manager を使ってみたい
  • Cloud Functions 関数の Python コードから Secret Manager にアクセスしたい

前提

Google Cloud SDK(CLI コマンド)の実行環境は準備済みです。 本エントリでは、環境設定不要の Cloud Shell を使用して CLI を実行しました。

シークレットを作成

Secret Manager のシークレットとバージョンを作成します。

Secret Manager に保存する以下のパスワードを、credential.txt ファイルとして保存済みです。

database_password

以下のコマンドで、シークレットを作成し、パスワードを保存したファイルを指定してシークレットのバージョンを作成しました。

gcloud secrets create test-mikami
gcloud secrets versions add test-mikami --data-file="./credential.txt"

作成したシークレットにアクセスしてパスワードを取得してみます。

mikami_yuki@cloudshell:~/sample/sm (cm-da-mikami-yuki-258308)$ gcloud secrets versions access 1 --secret="test-mikami"
database_password

Secret Manager に保存されたパスワードにアクセスできました。

念のため、管理コンソールからも確認してみます。

test-mikami シークレットのバージョン 1 に、database_password が保存されていることが確認できました。

シークレットにアクセスするサービスアカウントを追加

プログラムから Google Cloud の各種リソースにアクセスする場合、サービスアカウントを作成し、サービスアカウントに対して必要な権限を設定することが可能です。

Cloud Functions 関数を実行するサービスアカウントは、デフォルトでは Google 管理の App Engine デフォルトサービスアカウントが使用されますが、今回は Secret Manager へのアクセスを制限したいため、新しいサービスアカウントを作成してシークレットへのアクセス権限を付与します。

以下のコマンドで、シークレットにアクセスするためのサービスアカウントを作成しました。

gcloud iam service-accounts create sa-test-mikami

作成したアカウント名を取得します。

mikami_yuki@cloudshell:~/sample/sm (cm-da-mikami-yuki-258308)$ gcloud iam service-accounts list | grep sa-test-mikami
EMAIL: sa-test-mikami@cm-da-mikami-yuki-258308.iam.gserviceaccount.com

以下のコマンドで、サービスアカウントにシークレットへのアクセスに必要な権限(roles/secretmanager.secretAccessor)を付与しました。

gcloud secrets add-iam-policy-binding test-mikami \
    --member="serviceAccount:sa-test-mikami@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

管理コンソールからも、新しく作成したサービスアカウントに「シークレットアクセサー」の権限が付与されたことが確認できました。

シークレットを取得する Cloud Functions 関数を作成して実行

下記 Python コードを、main.py ファイルとして保存しました。

from google.cloud import secretmanager
import os

def access_secret_version(project_id, secret_id, version_id='latest'):
    # 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}"

    # Access the secret version.
    response = client.access_secret_version(request={"name": name})

    # Print the secret payload.
    payload = response.payload.data.decode("UTF-8")
    print("Plaintext: {}".format(payload))

def get_credential(event, context):
    GCP_PROJECT = os.getenv('GCP_PROJECT')
    SECRET_ID = os.getenv('SECRET_ID')

    access_secret_version(GCP_PROJECT, SECRET_ID)

Cloud Functions の環境変数に指定したシークレット名を取得して、Secret manager の該当シークレットの最新バージョンの値を取得して print します。 今回は動作確認用途なので取得したシークレットをログ出力していますが、本番環境など実際の運用環境ではシークレットを露出しないようご注意ください。

シークレットにアクセスするコードは、下記公式ドキュメントのサンプルを参考にさせていただきました。

プログラム内で使用するライブラリの情報を記載した requirements.txt ファイルも作成しました。

google-cloud-secret-manager>=2.7.2

以下のコマンドで Cloud Functions 関数をデプロイします。

gcloud functions deploy get_credential \
--region asia-northeast1 \
--runtime python37 \
--trigger-resource test-exec \
--trigger-event google.pubsub.topic.publish \
--service-account sa-test-mikami@cm-da-mikami-yuki-258308.iam.gserviceaccount.com \
--set-env-vars SECRET_ID=test-mikami

--service-account オプションで先ほど作成したシークレットアクセス用のサービスアカウントを指定し、--set-env-vars オプションでシークレット名を環境変数に設定します。 なお、今回は手動実行で動作確認するつもりなので、関数実行のトリガー情報の --trigger-event--trigger-resource には、適当な既存の Cloud Pub/Sub トピックを指定しています。

正常にデプロイされたようなので、管理コンソールから Cloud Functions 関数を手動実行して動作確認してみます。

期待通り、Cloud Function 関数コードから、Secret Manager に保存したパスワードを取得することができました。

料金

Secret Manager では、アクティブなシークレットバージョンの個数とアクセス数にしたがって課金が発生します。 料金は月単位で、アクティブなシークレットバージョン1つにつき0.06ドル、10,000アクセスごとに0.03ドルと安価な上に、1か月あたり6個のアクティブシークレットバージョン、10,000アクセスまでは無料枠内で利用できるそうなので、利用しやすいサービスではないかと思います。

まとめ(所感)

Secret Manager を使って、Google Cloud 環境で利用するシークレットを安全に管理することができます。ローテーション期間や顧客管理キーによる暗号化など、要件によってより強固なシークレット管理が可能です。

今回は Python コードからアクセスしてみましたが、公式ドキュメントには Ruby や Node.js など他の言語のサンプルコードも記載されているので、環境に合わせて必要なコードで簡単に利用開始することができるのではないかと思います。

複数のシステムとの連携が必要な場合など、各種クレデンシャルの管理は特に気をつけなければいけない最重要課題だと思います。もしクレデンシャルの漏洩などのセキュリティインシデントが発生してしまうと、開発者個人ではもうどうにもできないほどの重大な責任問題に発展してしまいます。

開発中にちょっとプログラムを試してみたい時でも、クレデンシャルはコードにベタ書きなどせず、Secret Manager のようなマネージドサービスで安全に管理する必要があると思います。またそのためには、開発者が必要な時にすぐにシークレットを作成できる権限管理や運用管理も必要だと改めて思いました。

参考