Google Cloudで動作するアプリケーションに秘密情報を渡すには - 2024年5月版 -
かつて秘密情報は環境変数に追い出せと言われた
- ハードコードすんな、設定ファイルもダメだ
- これは OSS 化する際には絶対に必要な作法だが、認証情報の更新などの際に実際の動作には何も変更がないのにコード側を修正、再デプロイが必要になるという点でもイマイチ
- III. 設定 - The Twelve-Factor App (日本語訳)
Googleさんは環境変数に秘密情報をセットするなと言う
これは昔からそうで、
- 環境変数はプロジェクトの Read 権限さえあれば確認できたり
- 環境変数にセットする処理そのものがログに残ってしまう
ため。まぁ簡単に言うと Google のインフラの設計がそうなっているから。
昔からそうなんだけど、明言されているドキュメントは見つけにくい。改めて確認したところ、Cloud Run のドキュメントには書かれていることを発見できた。
注意: 環境変数をシークレットの保存と利用に使用しないでください。環境変数は、プロジェクト閲覧者以上の権限を持つユーザーに表示されます。代わりに、シークレットの使用ページの説明に沿って、Cloud Run で Secret Manager を使用してください。
環境変数を使用する | Cloud Run のドキュメント | Google Cloud
そうは言うけどさ
そうは言うくせに、実は Google Cloud には長らく Secret Manager というサービスが存在しなかった1。
お前は何を言っているんだ?
っつー話ですよ2。もう一つ、
Secret Manager を利用しない開発環境のこと考えてる?
って話でもある。環境変数であれば local でもクラウドインフラでも同じように扱えるじゃないか。
じゃあどうすれば?
Cloud Run公式機能
実は Cloud Run には(いつからかは分からないが)環境変数の設定の中に Secret Manager を参照する機能がある。
シークレットを使用する | Cloud Run のドキュメント | Google Cloud
これで環境変数から秘密情報を取得すること自体が悪なのではなく、環境変数に秘密情報が生々しく固定的にセットしてあることで誰でも参照可能になってしまうこと、またセットする過程でログに秘密情報が生々しく記録されてしまうことがダメ、ということが明確になった。
言い換えると、この秘密情報を展開して記録される部分がなければ環境変数に秘密情報をセットすること自体は問題ないことを意味する。
そう、環境変数をアプリケーションへの秘密情報の受け渡しに利用してもよいのだ。
gcloud SDK を利用する場合も
gcloud run deploy | Google Cloud CLI Documentation
リンク先にあるように gcloud run deploy
からも env-vars から secrets を参照するようにコマンドを組み上げることができる。
Cloud Functions公式機能
シークレットを構成する | Google Cloud Functions に関するドキュメント
Cloud Functions では
- 環境変数
- 特別なボリューム
の二つの方法があるらしい。
他に方法はないか?
上記の機能はそれぞれサービス固有の方法であり、例えば App Engine の deploy には同様の機能は存在しない。ではどうすればよいか?
deploy の際に image を作成するタイプであればコンテナイメージの中に秘密情報を閉じ込める方法もなくはないが、それだと image を scan すれば普通に漏洩してしまう。では、
起動時に Secret Manager を参照して環境変数にセットする方法はどうだろうか?
Cloud Run の場合は Cloud Run の特別な機能で定義済みの環境変数が素の環境変数ではなく、実は Secret Manager を参照してましたというギミックだが、アプリケーション起動時の処理に差し込みができるなら擬似的にこれを再現できるのではないだろうか。
要は起動時に
gcloud secrets versions access <version> --secret <name>
を叩いて、その結果を環境変数に入れることができればよい。
Cloud Run の場合は Procfile でプロセスを定義する際に起動の処理を自由に記述できるので、その中で gcloud を実行することができれば可能かもしれない。つまりこう言う感じだ。
cli: SECRETS=`gcloud secrets versions access <version> --secret <name>` bundle exec ruby app.rb
これでアプリケーションからは通常の環境変数と同じように見えて、中身は Secret Manager に保存されている秘密情報ということになる。アプリケーションの側からは Twelve Factor App 時代の考え方と変わらない。実際、ローカルの gcloud SDK の存在する環境では期待通りに動作する。
App Engine の場合は entrypoint
の記述で起動処理に差し込みができそうだ。
Ruby ランタイム環境 | Google App Engine スタンダード環境のドキュメント | Google Cloud
gcloud SDKはフツーrun用のimageに存在しない
問題は
実行用の image にはフツー gcloud SDK なんか入ってない
ってことだ。
これについては Secret Manager の API から read するだけのシンプルなコードを自分で書いてアプリケーションのコードの中に追加するのが手っ取り早そう。read だけならそれほど手間でもない。何個も書くのがアレなら
GoogleCloudPlatform/berglas: A tool for managing secrets on Google Cloud
を使うのがよさそうだ。Berglas はもともと Google KMS ( Key Management Service ) をターゲットに作られたツールだが、今は Secret Manager も扱うことができる。
適切な権限さえ設定してあれば、以下のように
cli: SECRETS=`berglas access sm://${PROJECT_ID}/SECRET#VERSION` bundle exec ruby app.rb
secret を読み取って環境変数にセットすることができる。
Cloud Native Buildpacks を利用する場合は以下のようにして run image に入れてあれば、ビルド時に別途インストールしたりする必要もない。
ただし App Engine の Standard Env. の場合は事前に run image をカスタマイズするのは難しそうなので、build 時に別途 install することになりそうだ。こういう工夫の余地が少ないから Cloud Run にあるような環境変数から Secret Manager を参照する機能は App Engine にこそ必要なのではないかと思うのだが、今のところ App Engine には app.yaml で記述できるという特徴のおかげなのか、Cloud Run に比べてずいぶんコンソールは非力だし、この辺が整っていくかどうかはなんとも言えない。
つまり
GCP への deploy は普通 Google Cloud Build を使うとして、
- 事前に準備する run image の中にセットしておく or Cloud Build 上で berglas を workspace に配置
- この状態で build && deploy
- 起動時にこの berglas を利用して Secret Manager を読み取って環境変数へセット
という流れがよさそう。Cloud Run や Cloud Functions には専用の機能があるのでそれを利用してもよい。というか、Functions は起動処理に差し込みができるのかあやしいので専用の機能に頼らざるを得ないかも。
ちなみに自分はサーバは disposable にしたいので GCE を使うことはないが、GCE でもコンテナを利用することで同じようなことはできそう。
コンテナを実行する際のオプションの構成 | Compute Engine Documentation | Google Cloud