ファイルや環境変数を読むように Cloud Functions から Secret Manager にアクセスしてみた。

2022.03.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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

以前、Secret Manager API 経由で Cloud Functions 関数から Secret Manager に保存したシークレットにアクセスする方法を確認してみましたが、

他にも、シークレットをボリュームとしてマウントしてファイル同等に読み込んだり、環境変数と同じようにアクセスできたりするそうな。

ということで。

やりたいこと

  • Cloud Functions からファイル扱いでシークレットにアクセスする方法が知りたい
  • Cloud Functions から環境変数扱いでシークレットにアクセスする方法が知りたい
  • Cloud Functions から Secret Manager にアクセスする場合のベストプラクティスが知りたい

前提

Google Cloud SDK(gcloud コマンド)の実行環境は準備済みであるものとします。 本エントリでは、Cloud Shell を使用ました。

シークレットを作成

Cloud Functions からアクセスするシークレット&バージョンを作成します。 合わせて、シークレットにアクセスするサービスアカウントも作成しておきます。

まずは以下のコマンドでシークレットを作成します。

gcloud secrets create my-secret-sample

シークレットに保存する以下のサンプル文字列を、secret_sample.txt というファイル名で保存しました。

秘密だよ!

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

gcloud secrets versions add my-secret-sample --data-file="./secret_sample.txt"

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

gcloud iam service-accounts create secret-access-sample
gcloud secrets add-iam-policy-binding my-secret-sample \
    --member="serviceAccount:secret-access-sample@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" \
    --role="roles/secretmanager.secretAccessor"

ファイルとしてシークレットにアクセス

まずは、ファイルにアクセスするのと同じコードで、Cloud Functions からシークレットにアクセスしてみます。

以下の python コードを、main.py という名前で保存しました。

def secret_access_as_file(request):
    file_path = '/etc/secrets/sample/latest'
    with open(file_path, 'r', encoding='utf-8') as f:
        secret = f.read()
        print(secret)

file_path で指定したパスのファイルを read するのと全く同じコードです。 確認のため、read した中身をプリントしてみます。 なお、本物の機密情報をご使用の場合は、漏洩につながる危険があるため、プリントしないよう充分ご注意ください。

以下のコマンドで関数をデプロイします。 最終行の --set-secrets オプションで、read 対象のパスとシークレット情報を指定します。

gcloud functions deploy secret_access_as_file \
    --region asia-northeast1 \
    --runtime python37 \
    --trigger-http \
    --allow-unauthenticated \
    --service-account secret-access-sample@cm-da-mikami-yuki-258308.iam.gserviceaccount.com \
    --set-secrets /etc/secrets/sample:latest=my-secret-sample:latest

正常にデプロイできました。

管理コンソールからテスト実行してみます。

ログから、期待通りシークレットに保存した文字列が取得できていることが確認できました。

環境変数としてシークレットにアクセス

次に、環境変数を取得するのと同じコードで、シークレットにアクセスできるか確認してみます。

以下の python コードを、main.py という名前で保存しました。

import os

def secret_access_as_env(request):
    secret = os.getenv('MY_SECRET', None)
    print(secret)

以下のコマンドで関数をデプロイします。 こちらも最終行の --set-secrets オプションで、変数名とシークレット情報を指定します。

gcloud functions deploy secret_access_as_env \
    --region asia-northeast1 \
    --runtime python37 \
    --trigger-http \
    --allow-unauthenticated \
    --service-account secret-access-sample@cm-da-mikami-yuki-258308.iam.gserviceaccount.com \
    --set-secrets MY_SECRET=projects/797147019523/secrets/my-secret-sample:latest

正常にデプロイできたことを確認して、

管理コンソールからテスト実行してみると

こちらも、無事シークレットにアクセスできることが確認できました。

ベストプラクティス

シークレットのアクセス設定とデプロイ時の指定で、Cloud Functions コードからシークレットに簡単にアクセスできることが確認できましたが、公式ドキュメントによると、やはり Secret Manager API 経由でのアクセスがベストプラクティスのようです。

ファイル システムでシークレットにアクセスできると、攻撃者がシークレット マテリアルを読み取ることができるため、ディレクトリ トラバーサル攻撃などのアプリケーションの脆弱性が深刻化する可能性があります。

環境変数を介してシークレットが使用されると、デバッグ エンドポイントの有効化やプロセス環境の詳細をログに記録する依存関係などの構成ミスにより、シークレットが漏洩する可能性があります。

こうした理由から、可能であれば、Secret Manager API を直接使用することをおすすめします

実行環境やシークレットの機密性など考慮の上、どの方法でアクセスするか、慎重に検討した方が良さそうです。

まとめ(所感)

Cloud Functions の環境変数として保存していたシークレットを Secret Manager に移動する場合や、これまでサーバ上で実行していたローカルファイルに保存したシークレットにアクセスするようなコードを Cloud Functions に移行する場合など、ケースによってはコード修正不要で Secret Manager にアクセスできるので、開発者には嬉しい機能ではないかと思います。

ただしご利用の際には、前述の通りベストプラクティスも合わせてご検討ください。

いずれにせよ、Cloud Functions でシークレットを扱う場合は、めんどくさがらずに Secret Manager を使いましょう!

参考