電通総研 テックブログ

電通総研が運営する技術ブログ

Terraform で AWS に DB を構築するとき manage_master_user_password を使っていますか?

こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。

Terraform で Amazon RDS インスタンス/クラスターを作る時に、password または master_password 属性に指定したマスターユーザーのパスワードが tfstate ファイルに平文で残ってしまう問題がありました。

(参考)
https://speakerdeck.com/harukasakihara/sekiyuanaterraformfalseshi-ifang-ji-mi-qing-bao-wokodonihan-mezuhuan-jing-gou-zhu-surunihadousitaraiifalse

しかしこれも過去の話。Terraform AWS Provider v4.61.0 からこの問題を解消する方法が提供されているので、それについてご紹介します。

RDS と Secrets Manager の統合

事の始まりは 2022年12月に発表された Amazon RDS と AWS Secrets Manager の統合というアップデートでした。DB 作成時に、RDS への API コールにてマスターユーザーのパスワードを Secrets Manager で作成・保存してくれるようになりました。

Terraform AWS Provider でもこれへの対応として、2023年3月にリリースされた v4.61.0manage_master_user_password 属性が aws_rds_clusteraws_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 ファイルには、次のとおり平文でパスワード文字列が記録されてしまっています。

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 にシークレットが作成されていることがわかります。

作成されたシークレット1
作成されたシークレット2

シークレットのキーには usernamepassword が登録されており、password は RDS が生成したランダムな値になっていました。また 7日間の間隔でシークレットローテーションも設定されていました。

デプロイ後の terraform.tfstate ファイルでは、passwordnull となっており、実際のパスワード文字列はどこにも記録されていません。

tfstateファイルにはパスワードが記録されていない

aws_rds_cluster にも利用できる

aws_rds_cluster リソースに対しても、従来の master_password の代わりに manage_master_user_password を利用できるようになっています。

(参考)
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster#rdsaurora-managed-master-passwords-via-secrets-manager-default-kms-key

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 シークレットをマスターユーザーのパスワードにできるようです。

(参考)
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance#master_user_secret

既存のDBに使ったらどうなるか

既に passwordmaster_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 ファイルに記録された passwordnull だったため、Terraform AWS Provider のバグというよりは RDS の制約なのかもしれません。

リストアした DB のパスワードも Secrets Manager で管理するためには、今のところの次のワークアラウンドが良さそうです。

  1. manage_master_user_password を付けずに Terraform でスナップショットをリストア(apply
    • スナップショット取得時点のパスワードを、RDS が管理している状態でリストアされる
  2. 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で執筆されました