App Engineで秘匿情報をうまいこと扱う
仕事でGoogle App Engine(スタンダード環境)を使う事がよくあるのだが,秘匿情報の管理がいまいちスマートにできていない気がするので,代替を検討してみた.
この投稿はGO Inc. Advent Calendar 2023の24日目の記事として書かれるはずだった.
前提条件
- Go on Google App Engine(スタンダード環境)
現状
秘匿情報をYAMLのmappingとして,それをCloud KMSで暗号化してリポジトリに保存している.デプロイ時にこれを復号し,app.yamlのenv_variables
要素に設定する事で,アプリケーションからは環境変数として参照している.
理想
- アプリケーションからは透過的に復号された秘匿情報にアクセスできる
- PRで秘匿情報の変更を検知できる
- 参照・変更の権限を分離できる
選択肢
できればGoogle Cloudのマネージドサービスで完結させたいので,とれる選択肢はそんなにない(はず).
- Cloud KMS
- Secret Manager
基本的にはこれらを使う事になる.
Cloud KMS
Cloud KMSはGoogle Cloudにおける鍵管理サービスで,自分で鍵を管理して暗号化・復号を行いたい場合にはファーストチョイスになる.ただし提供されている機能はプリミティブなので,アプリケーションの運用に組み込むにはそれなりに手を掛ける必要がある.
Secret Manager
Secret ManagerはGoogle Cloudの中でも後発のサービスで,秘匿情報をキー値とバージョニングされた値のマッピングで管理するサービスだ.コンソールのUIも明快で使いやすいサービスだが,キー名はプロジェクト内で一意である必要があり,名前空間などは提供されていない.
Berglas
BerglasはSecret Managerが登場する前から存在したツールでありライブラリで,Cloud KMSとCloud Storageをバックエンドとして使って秘匿情報を容易に管理できるようにするためのものだ.なおv0.5からはSecret Managerをバックエンドとして使う事もできる.しかしSecret Managerのラベルを追加するなどの当然ありそうな機能は無い.
結論
とりあえずBerglasを使ってやってみようと思う.ただしBerglasのメンテナはバグフィックスはやる気があるみたいだが機能追加は今は考えていないそうなので,そこは懸念点ではある(PRもrejectされているようだ).
Berglasの基本
Berglasがやっていることは非常に単純で,Cloud StorageバックエンドであればCloud KMSでenvelope encryptionを行い,Cloud Storageに格納する,加えてそれを操作するフロントエンドという作りです.そしてberglas:// reference syntaxというものを定義しており,これをBerglasが秘匿情報を復号したものに置き換える,ということをしている.Go言語向けにはauto/
パッケージというものを提供している.この実装を見るとやっていることは一目瞭然で,環境変数を全てなめて,上述の記法のものを書き換えている.
Berglas CLI(berglas
コマンド)は前述のフロントエンドであり,比較的直観的に作られている.いくつかサブコマンドを拾い上げると,
-
berglas bootstrap
で初期化(Cloud Storageバックエンドの場合のみ) -
berglas create
で追加 -
berglas update
/berglas edit
で修正 -
berglas delete
で削除 -
berglas access
/berglas list
で取得 -
berglas grant
で特定シークレットへのアクセスを特定アカウントに許可する
といった感じだ.ここに上げたのは一部だが,全体でもそんなにコマンドは多くなく,またオプションも本当に必要最低限しか提供されていない.
運用をどうするか
既にauto
パッケージについて説明した通り,何にせよBerglasは既存の環境変数の存在を要求する.したがってapp.yamlのenv_variables
要素に環境変数として利用したい秘匿情報を設定することは必須だ.その上で追加・変更・削除をどうするかという話になる.ここからは,デプロイ先毎にapp.yamlを用意してあり,それぞれのenv_variables
に環境変数として使用する秘匿情報を書くという前提で進める.また,BerglasのバックエンドはSecret Managerとする.
追加
berglas create
でSecretを追加し,app.yamlに前述のreference syntaxで環境変数を追加する.
更新
Secret ManagerはSecretをバージョン管理しており,あるバージョンに紐付いたペイロードはイミュータブルだ.Secret Managerは追加された最新バージョンに latest というエイリアスを付与するが,Googleも述べているようにproduction環境では常にバージョンを指定してアクセスするべきだ.berglas update
でSecretを更新すると,Berglasは自動的に既存のSecretのバージョン
をインクリメントする.とてもえらい.reference syntaxではバージョンを(sm://${PROJECT_ID}/foo#42
のような形で)指定できるので,app.yamlの環境変数には必ずバージョンを明示的に指定する様にすれば,参照する秘匿情報の変更を検知が可能だ.例えば以下の様な形でPRの差分が確認できる.
- FOO: 'sm://my-project-id/foo#42'
+ FOO: 'sm://my-project-id/foo#43'
Berglasのreference syntaxのおかげで,app.yamlのenv_variables
を暗号化して保存する必要もなくなり,PRで変更されたものをレビュアが確認できる.そして実際に変更されたSecretの値は,Secret Manaagerの適切な権限を持っていればコンソール上なりgcloudコマンドを使うなりberglasコマンドを使うなりで確認できる.GitHub Actionsでapp.yamlの変更に対してPRへのコメントでSecret Managerのコンソールへのリンクを貼ったりしたら利便性が上がるかもしれない.
削除
berglas delete
でSecretを削除し,app.yamlからも対応する環境変数を削除する.ただし場合によってはapp.yamlから該当するエントリを削除するだけの場合もあるだろう.
権限周りの管理
権限周り(berglas grant
/berglas revoke
)に限らず,Secretの変更全般はPRからは見えないので,このオペレーションをどう可視化するかは課題になりそうだ.Secretを追加出来る権限が付与されるのは限られた人間になると思うので,手当たり次第追加されたり削除されることは無いと思うが,組織に応じた運用方針の整備は必須だろう.
懸念点
実際に運用してみないことにはわからないこともあると思うが,一旦はこれで運用できそうではある.しかし前述の通りSecret Managerには単一ネームスペース問題があり,BerglasでSecretを追加すると,アプリケーションによってはSecret Managerの中に大量のエントリが作成されて管理しづらくなることもあるだろうし,複数のアプリケーションが同一project内にあると名前の重複も起こりえるので,あまりきれいな解法ではないがアプリケーション毎のprefixを付与するなどして重複を避ける運用が必要になりそうだ.幸い環境変数名とSecret Manager上の名前は分離できるので,アプリケーションからはSecret Manager上でどのような名前で管理されているかを気にする必要はない.
Berglasのauto
パッケージを使うと復号した秘匿情報を環境変数としてexportすることになるが,構成の誤りやデバッグAPIによって秘匿情報が漏洩する可能性が上がることは否定できない.BerglasのAPIを利用したり,CLIを経由して平文を取得して,環境変数を経由せずにアプリケーションで秘匿情報を扱うことで多少なりともこれらのリスクを避けることもできる.
Secretのローテーションもまた大きな課題だ.新しいバージョンのロールアウトと古いバージョンの無効化・破棄に関するポリシー策定は慎重に行う必要がある.古いバージョンがいつまでも残っていては問題だが,一方特定のバージョンでアプリケーションの実行に問題が起きた際に参照しているバージョンが破棄されていたら原因究明の足止めになり得る.取り得る戦略によって対処が変わるため,ここではこれ以上考慮しない.
まとめ
Berglasを用いてApp Engine(スタンダード環境)での運用がうまくできそうなことはわかった.書いていて結局一番大事だと思ったのは組織において秘匿情報をどう扱うかというポリシーを明文化し,それを運用し続けるということだ.難しい.
Discussion