この内容は、社内のエンジニア勉強会で話した内容です。
speakerdeck.com
みなさん。プロダクション環境のシークレット情報をどう扱っていますか?
クラウドネイティブなアプリケーション開発において、DBのパスワードや外部APIキーといったシークレットの管理は、セキュリティを確保する上で避けては通れない課題です。
この記事では、アプリケーションとインフラそれぞれの視点から、クラウド環境におけるシークレット管理のアンチパターンとベストプラクティスを探っていきます。
ここで言うシークレットとはDBのパスワードやAPIキーなどの秘匿すべき情報のことを指します。
アプリケーション側の視点
まずは、アプリケーションがどのようにシークレットを扱うべきかを見ていきましょう。
管理のアンチパターン
最初に管理方法のアンチパターンとしては以下のものがあります
- ソースコードに直接記述
- 設定ファイルに平文で記述
- 環境変数に平文で記述(Dockerfileや.envファイルでgit管理するなど)
- base64エンコードして保存
ソースコードに記述すれば、すべての環境で同じ値しか使えず柔軟性がありません。設定ファイルや環境変数に平文で記述し、それをGitで管理してしまうと、何かのミスでリポジトリが流出した際にシークレットも漏れてしまいます。
また、隠しているつもりでBase64エンコードするのも同様に危険です。Base64は暗号化ではなく、誰でも簡単に元の文字列に戻せるため、平文で保存しているのと大差ありません。
KMSによる暗号化の検討
次に考えられるのが、暗号鍵を使った暗号化です。AWSのKMSやGoogle CloudのCloud KMSといった鍵管理サービスを利用する方法が考えられます。
フローとしては以下のようになりますでしょうか
- アプリケーション起動時にKMSのから鍵を取得
- 取得した鍵を利用して暗号化されたシークレットを復号
- 平文のシークレット情報をアプリで利用する
一見これで良さそうですが、復号処理をアプリケーションの責務にすると、コードが複雑になるだけでなく、KMSの復号権限をアプリケーション自体に付与する必要があり、管理の懸念点が増えてしまいます。
クラウド側のシークレットストアの利用
そこで推奨されるのが、クラウドが提供するシークレット管理の仕組みを利用することです。
- AWS
- AWS Secrets Manager
- AWS Systems Manager Parameter Store(SecureString)
- Google Cloud
これらのサービスは、ECS FargateやCloud Runなどのコンテナ実行環境と統合されています。コンテナの起動時に、これらのストアに保存されたシークレットを、自動的に環境変数やファイルとしてマウントしてくれるのです。
これによりアプリケーション側では、シークレットがどこで管理されているかを意識することなく、従来通り環境変数やファイルから値を読み込むだけで済むようになり、責務をシンプルに保つことができます。
インフラ側の視点
さて、アプリケーションの課題は解決しました。次に、インフラ側で、そのシークレットストアをどう管理するかという課題に移りましょう。
取れる手段としては主に以下ものが考えられます。
- 手動でコンソールから設定
- シークレットの値を平文でIaC管理(tfvarsファイルをgit管理から外す)
- シークレットの値を暗号化してIaCで管理
- シークレットストアをIaCで管理、値は手動設定
まず手動で管理ですが、これはこれでありだと思ってます。ただし、扱うシークレットの数が増えてきたときに作業が煩雑であったり、手作業がゆえに起こるリソースタグなどの付け間違いなどのミスが発生しうるので、規模が大きくなると現実的ではありません。
2つ目ですが、シークレットの値だけあつかるtfvarsファイルをgitignoreしてあげることでレポジトリが漏れてもシークレットの値が漏れないことになります。が、うっかりシークレットの値を人為的なミスでコミットしうるので完全に安全とはいいにくいです。
3つ目ですが、これはsops providerを利用するパターンです。これを使うことでKMSキーを利用して暗号/復号がterraformとシームレスに統合できます。一見これで良さそうですが、2点課題があります。
- KMSリソースを余計に管理なくてはならない
- Stateには平文で保存される
前者は必要経費としていいとして、後者は課題となります。Terraformにおいてはstateを見る権限がある人にはシークレットも見れてしまうという懸念があります。
シークレットのリソースと値を分離する
この方法の利点は、IaCでシークレット リソースが存在することは管理しつつ、その実際の値はGitの管理下から完全に分離できる点です。初回適用後にコンソールから実際のシークレット値を設定すれば、それ以降 terraform apply を実行しても値がダミー値で上書きされることはありません。
これにより、コードレビューなどで誤ってシークレットが漏洩するリスクを原理的に防ぐことができ、非常にバランスの取れた管理方法と言えます。
以下サンプルコードです。
resource "aws_ssm_parameter" "db_password" {
type = "SecureString"
name = "/test/db_password"
value = "Dummy"
lifecycle {
ignore_changes = [value]
}
}
まとめ
現時点でのクラウドにおけるシークレット管理のベストプラクティスは、以下のようにまとめることができるでしょう。
- アプリケーション
- クラウドのシークレットストア(Secrets Managerなど)と実行環境(ECS, Cloud Runなど)の統合機能を使い、環境変数またはファイルとしてシークレットを読み込む。
- インフラ(IaC)
- クラウドのシークレットストアのリソース自体はTerraformで管理する。
- 実際のシークレットの値は
ignore_changes を活用して手動で設定し、Gitの管理から分離する。
もちろん要件によって取りうる手段は変わるとは思います。他になにか良い方法をご存知でしたら教えて下さい。
それでは良いシークレットライフを!
関連ページ
blog.masasuzu.net