こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。
Terraform で Amazon RDS インスタンス/クラスターを作る時に、password
または master_password
属性に指定したマスターユーザーのパスワードが tfstate
ファイルに平文で残ってしまう問題がありました。
しかしこれも過去の話。Terraform AWS Provider v4.61.0 からこの問題を解消する方法が提供されているので、それについてご紹介します。
- RDS と Secrets Manager の統合
- manage_master_user_password を使わない場合
- manage_master_user_password を使った場合
- aws_rds_cluster にも利用できる
- KMSキーを指定する
- 既存の Secrets Manager シークレットを利用する場合
- 既存のDBに使ったらどうなるか
- スナップショットからリストアする時の挙動
- さいごに
RDS と Secrets Manager の統合
事の始まりは 2022年12月に発表された Amazon RDS と AWS Secrets Manager の統合というアップデートでした。DB 作成時に、RDS への API コールにてマスターユーザーのパスワードを Secrets Manager で作成・保存してくれるようになりました。
Terraform AWS Provider でもこれへの対応として、2023年3月にリリースされた v4.61.0
で manage_master_user_password
属性が aws_rds_cluster
と aws_db_instance
で利用できるようになりました!
実際にリソースを作成して試してみます。
manage_master_user_password を使わない場合
以下のように平文で password
を指定し、 aws_db_instance
リソースを作成してみます。(BADプラクティスです)
resource "aws_db_instance" "my-db-1" { allocated_storage = 10 engine = "mysql" engine_version = "5.7" instance_class = "db.t3.micro" username = "master" # password に平文でパスワードを記述する悪い例 password = "MyPassword123" vpc_security_group_ids = [aws_security_group.db.id] skip_final_snapshot = true db_subnet_group_name = "db-subnet-group" }
デプロイ後の terraform.tfstate
ファイルには、次のとおり平文でパスワード文字列が記録されてしまっています。
例のように .tf
ファイルに直接 password
を書くのは最悪ですが、他の回避策(Variablesを利用して apply
時にターミナルで指定する、Secrets Manager シークレットに先に登録してから Datasource で取得する、etc)をとったところでどうしても tfstate
ファイルに記録されてしまうのは問題でした。
manage_master_user_password を使った場合
password
属性を削除し、manage_master_user_password
属性に true
を設定します。
resource "aws_db_instance" "my-db-2" { allocated_storage = 10 engine = "mysql" engine_version = "5.7" instance_class = "db.t3.micro" username = "master" # manage_master_user_password を利用する manage_master_user_password = true vpc_security_group_ids = [aws_security_group.db.id] skip_final_snapshot = true db_subnet_group_name = "db-subnet-group" }
デプロイすると、Secrets Manager にシークレットが作成されていることがわかります。
シークレットのキーには username
と password
が登録されており、password
は RDS が生成したランダムな値になっていました。また 7日間の間隔でシークレットローテーションも設定されていました。
デプロイ後の terraform.tfstate
ファイルでは、password
は null
となっており、実際のパスワード文字列はどこにも記録されていません。
aws_rds_cluster にも利用できる
aws_rds_cluster
リソースに対しても、従来の master_password
の代わりに manage_master_user_password
を利用できるようになっています。
KMSキーを指定する
master_user_secret_kms_key_id
属性を利用することで、デフォルトキーではなく指定した KMS キーでシークレットの暗号化をすることもできます。
(参考)
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance#managed-master-passwords-via-secrets-manager-specific-kms-key
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster#rdsaurora-managed-master-passwords-via-secrets-manager-specific-kms-key
既存の Secrets Manager シークレットを利用する場合
今回は試していませんが、 master_user_secret
の設定ブロックを使うと、既存の Secrets Manager シークレットをマスターユーザーのパスワードにできるようです。
既存のDBに使ったらどうなるか
既に password
や master_password
を使って作成した DB に対して、manage_master_user_password
を利用する方法に切り替えることは可能です。DB は再作成されませんが、マスターユーザーのパスワードは新規に作成され(現在のパスワードは引き継がれずに) Secrets Manager に新しいシークレットとして保存されるようです。既存の DB で manage_master_user_password
を使う方法に切り替える場合は、アプリケーションの動作や運用に影響がないか慎重に確認しましょう。
スナップショットからリストアする時の挙動
manage_master_user_password
を利用して作成した DB のスナップショットからリストアする場合、マスターユーザーのパスワードは少し特殊な状態になるようです。次のように manage_master_user_password = true
を使ってスナップショットから DB インスタンスを作成しても、Secrets Manager にパスワードは統合されず、RDS がパスワードを管理している状態で DB が復元しました。パスワードはスナップショット取得時点のものでした。
data "aws_db_snapshot" "my-snapshot" { db_snapshot_identifier = "snapshot1" } resource "aws_db_instance" "my-db-from-snapshot" { allocated_storage = 10 engine = "mysql" engine_version = "5.7" instance_class = "db.t3.micro" username = "master" # snapshot_identifier を利用してリストアする時、 # manage_master_user_password 属性を使っても # パスワードが Secrets Manager で管理されない。 manage_master_user_password = true skip_final_snapshot = true db_subnet_group_name = "db-subnet-group" # スナップショットから復元する snapshot_identifier = "${data.aws_db_snapshot.my-snapshot.id}" }
この現象は GitHub の Issue にも報告されており、記事執筆時点では未解決です。ただし tfstate
ファイルに記録された password
は null
だったため、Terraform AWS Provider のバグというよりは RDS の制約なのかもしれません。
リストアした DB のパスワードも Secrets Manager で管理するためには、今のところの次のワークアラウンドが良さそうです。
manage_master_user_password
を付けずに Terraform でスナップショットをリストア(apply
)- スナップショット取得時点のパスワードを、RDS が管理している状態でリストアされる
manage_master_user_password = true
を付けてもう一度apply
する- マスターユーザーのパスワードは新規に作成されて Secrets Manager に保存される
data "aws_db_snapshot" "my-snapshot" { db_snapshot_identifier = "snapshot1" } resource "aws_db_instance" "my-db-from-snapshot" { allocated_storage = 10 engine = "mysql" engine_version = "5.7" instance_class = "db.t3.micro" username = "master" # リストア時は manage_master_user_password を付けずに apply # リストア後 manage_master_user_password を付けて再度 apply # manage_master_user_password = true skip_final_snapshot = true db_subnet_group_name = "db-subnet-group" snapshot_identifier = "${data.aws_db_snapshot.my-snapshot.id}" }
さいごに
Terraform において tfstate
ファイルに平文の DB パスワードが残ってしまう問題を簡単に解消する manage_master_user_password
属性についてご紹介しました。今後新たに Terraform で RDS インスタンス/クラスターを作る時には、ぜひ利用を検討しましょう。ただしスナップショットからリストアする時の動きは(記事執筆時点で)少し特殊なので要注意です。
お読みいただいてありがとうございました。
私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。
セキュリティエンジニア執筆:@kou.kinyo、レビュー:寺山 輝 (@terayama.akira)
(Shodoで執筆されました)