RDSのマスターユーザーパスワードを自動ローテーションさせる
この記事は terraform Advent Calendar 2023 の 20日目(シリーズ2)の記事です。
実施のきっかけ
マスターユーザーのパスワードをローテーションさせるというチケットが長く塩漬けになっていたところに、この記事に出会いました。
この記事により、Secrets Managerへの保存とローテーションを自動化できるようになっていた(2022/12)ことを知りました。
Amazon RDS は、RDS データベースインスタンスのマスターユーザーパスワードの管理方法を合理化するため、AWS Secrets Manager との統合をサポートするようになりました。
AWS provider v4.61.0
v4.61.0(2023/05/31)以降のAWS providerでSecrets Managerとの統合が利用できるようになります。
- resource/aws_db_instance: Add
manage_master_user_password,master_user_secretandmaster_user_secret_kms_key_idarguments to support RDS managed master password in Secrets Manager (#28848)- resource/aws_rds_cluster: Add
manage_master_user_password,master_user_secretandmaster_user_secret_kms_key_idarguments to support RDS managed master password in Secrets Manager (#28848)
リリースノートからのコピペですが、master_user_secretは正しくはargumentではなくattributeです(後で使います)。
AWS provider v5.22.0
v5.22.0(2023/10/20)ではスナップショットまたはポイントインタイムリカバリからの復元によって作成したaws_db_instanceでmanage_master_user_passwordが効かない問題が修正されています(aws_rds_clusterではこの問題は発生していません)。
既存のaws_rds_clusterでmanage_master_user_passwordを有効にする
マスターユーザーはアプリケーションや運用で(一つの例外を除き)使っていないため、さくっと有効化します。
有効化の前にtfstateを覗く
master_passwordに平文でパスワードが書かれていました。

有効化する
master_password argumentを削除してmanage_master_user_password = trueを追加するだけです。
有効化した後にtfstateを覗く
最初からmanage_master_user_password = trueで作成するとmaster_passwordはnullになるようですが、途中で切り替えるとtfstateにはパスワードが書かれたままになります。
すでに新しいパスワードがSecrets Managerに保存されているため、このパスワードはもう使えません。

Secrets Managerを確認
RDSコンソールのConfigurationタブからSecrets Managerに飛べます。

7日でローテーションするようになっています。

ローテーションの周期
Aurora はシークレットの設定を管理し、デフォルトではシークレットを7日ごとにローテーションします。ローテーション スケジュールなど、一部の設定を変更できます。
ModifyDBClusterなどのAPIにはこれを制御するパラメータはないため、「一部の設定を変更」はSecretを直接編集することによって可能になります。
Secrets Managerに保存されたパスワードをMySQL providerで使う
唯一の例外としてMySQL providerの動作にはマスターユーザーを利用していました。
HashiCorpのMySQL providerがアーカイブされてからは非公式forkを利用しています。
以前はパラメータストアにマスターユーザーのパスワードを保存し、dataで参照していました。
自前で用意したパラメータのためnameで簡単にdataを取得できていましたが、RDSとの統合により自動作成されたSecretの名前やARNは事前には予測できません。
そのため、aws_rds_clusterのattributeからSecretのARNを入手することになります。
その際master_user_secret.secret_arnではなく master_user_secret[0].secret_arn とする必要があります。
provider "mysql" {
endpoint = "${local.mysql_host}:${var.mysql_port}"
username = jsondecode(data.aws_secretsmanager_secret_version.master_password.secret_string)["username"]
password = jsondecode(data.aws_secretsmanager_secret_version.master_password.secret_string)["password"]
}
variable "mysql_port" {
type = number
}
data "aws_rds_cluster" "this" {
cluster_identifier = local.cluster_identifier
}
data "aws_secretsmanager_secret" "master_password" {
arn = data.aws_rds_cluster.this.master_user_secret[0].secret_arn
}
data "aws_secretsmanager_secret_version" "master_password" {
secret_id = data.aws_secretsmanager_secret.master_password.id
}
AWS API的には複数存在することはなさそうですが、
attributeのtypeはlistになっているため、master_user_secret[0]とする必要がありました。
これはどうも型の選択肢の都合でそうなっているみたいです。
(typeObjectは非公開)
Discussion